@zaly/tui 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/assets/schemas/theme.schema.json +899 -0
- package/assets/themes/LICENSE-tokyonight.txt +201 -0
- package/assets/themes/NOTICES.md +69 -0
- package/assets/themes/ayu-dark.json +119 -0
- package/assets/themes/catppuccin-frappe.json +124 -0
- package/assets/themes/catppuccin-latte.json +124 -0
- package/assets/themes/catppuccin-macchiato.json +124 -0
- package/assets/themes/catppuccin-mocha.json +124 -0
- package/assets/themes/dracula-soft.json +119 -0
- package/assets/themes/dracula.json +119 -0
- package/assets/themes/everforest-dark.json +118 -0
- package/assets/themes/everforest-light.json +118 -0
- package/assets/themes/github-dark.json +123 -0
- package/assets/themes/github-light.json +123 -0
- package/assets/themes/gruvbox-dark-medium.json +112 -0
- package/assets/themes/kanagawa-dragon.json +115 -0
- package/assets/themes/kanagawa-wave.json +115 -0
- package/assets/themes/material-theme-palenight.json +114 -0
- package/assets/themes/min-dark.json +55 -0
- package/assets/themes/min-light.json +59 -0
- package/assets/themes/monokai.json +116 -0
- package/assets/themes/night-owl.json +116 -0
- package/assets/themes/nord.json +101 -0
- package/assets/themes/one-dark-pro.json +110 -0
- package/assets/themes/one-light.json +108 -0
- package/assets/themes/poimandres.json +123 -0
- package/assets/themes/rose-pine.json +98 -0
- package/assets/themes/solarized-dark.json +116 -0
- package/assets/themes/solarized-light.json +116 -0
- package/assets/themes/synthwave-84.json +100 -0
- package/assets/themes/tokyonight-day.json +157 -0
- package/assets/themes/tokyonight-moon.json +157 -0
- package/assets/themes/tokyonight-night.json +157 -0
- package/assets/themes/tokyonight-storm.json +157 -0
- package/assets/themes/vesper.json +103 -0
- package/assets/themes/vitesse-dark.json +126 -0
- package/assets/themes/vitesse-light.json +126 -0
- package/dist/ansi-Clq0ozyA.mjs +206 -0
- package/dist/ansi-Clq0ozyA.mjs.map +1 -0
- package/dist/ansi.d.mts +48 -0
- package/dist/ansi.d.mts.map +1 -0
- package/dist/ansi.mjs +2 -0
- package/dist/autocomplete-Dfw-yQDa.d.mts +100 -0
- package/dist/autocomplete-Dfw-yQDa.d.mts.map +1 -0
- package/dist/box-DyaByEiG.mjs +386 -0
- package/dist/box-DyaByEiG.mjs.map +1 -0
- package/dist/builder-D49ECBEf.mjs +385 -0
- package/dist/builder-D49ECBEf.mjs.map +1 -0
- package/dist/callbacks-CNjGNxVw.mjs +279 -0
- package/dist/callbacks-CNjGNxVw.mjs.map +1 -0
- package/dist/client-D6C_uczF.mjs +142 -0
- package/dist/client-D6C_uczF.mjs.map +1 -0
- package/dist/clipboard.d.mts +104 -0
- package/dist/clipboard.d.mts.map +1 -0
- package/dist/clipboard.mjs +404 -0
- package/dist/clipboard.mjs.map +1 -0
- package/dist/flex-Bguo-fUe.mjs +202 -0
- package/dist/flex-Bguo-fUe.mjs.map +1 -0
- package/dist/image-Cm5T-uw-.mjs +108 -0
- package/dist/image-Cm5T-uw-.mjs.map +1 -0
- package/dist/image.d.mts +123 -0
- package/dist/image.d.mts.map +1 -0
- package/dist/image.mjs +2 -0
- package/dist/index.d.mts +64 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +222 -0
- package/dist/index.mjs.map +1 -0
- package/dist/input-B5Vy-iaa.d.mts +141 -0
- package/dist/input-B5Vy-iaa.d.mts.map +1 -0
- package/dist/inspect-D1d9t-ni.mjs +101 -0
- package/dist/inspect-D1d9t-ni.mjs.map +1 -0
- package/dist/inspect-D6FW_QUo.d.mts +67 -0
- package/dist/inspect-D6FW_QUo.d.mts.map +1 -0
- package/dist/kitty-JbCJC0iQ.mjs +673 -0
- package/dist/kitty-JbCJC0iQ.mjs.map +1 -0
- package/dist/markdown.d.mts +24 -0
- package/dist/markdown.d.mts.map +1 -0
- package/dist/markdown.mjs +2 -0
- package/dist/md.bun.d.mts +14 -0
- package/dist/md.bun.d.mts.map +1 -0
- package/dist/md.bun.mjs +28 -0
- package/dist/md.bun.mjs.map +1 -0
- package/dist/md.node.d.mts +14 -0
- package/dist/md.node.d.mts.map +1 -0
- package/dist/md.node.mjs +126 -0
- package/dist/md.node.mjs.map +1 -0
- package/dist/node-BZ7f6HzU.mjs +316 -0
- package/dist/node-BZ7f6HzU.mjs.map +1 -0
- package/dist/picker-D8OLb6kZ.d.mts +36 -0
- package/dist/picker-D8OLb6kZ.d.mts.map +1 -0
- package/dist/picker-ndVVt6qF.mjs +621 -0
- package/dist/picker-ndVVt6qF.mjs.map +1 -0
- package/dist/queries-DH3bj74K.d.mts +1963 -0
- package/dist/queries-DH3bj74K.d.mts.map +1 -0
- package/dist/reactive-f7NpAi69.mjs +623 -0
- package/dist/reactive-f7NpAi69.mjs.map +1 -0
- package/dist/registry-U654j61Y.mjs +356 -0
- package/dist/registry-U654j61Y.mjs.map +1 -0
- package/dist/render-DxVLqqB9.mjs +74 -0
- package/dist/render-DxVLqqB9.mjs.map +1 -0
- package/dist/renderer-CdLCkJB-.mjs +42 -0
- package/dist/renderer-CdLCkJB-.mjs.map +1 -0
- package/dist/renderer-CxL4xbkl.mjs +2644 -0
- package/dist/renderer-CxL4xbkl.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/search-FvGnswRl.d.mts +49 -0
- package/dist/search-FvGnswRl.d.mts.map +1 -0
- package/dist/select-CbmAZdpt.d.mts +116 -0
- package/dist/select-CbmAZdpt.d.mts.map +1 -0
- package/dist/services/logger.d.mts +2 -0
- package/dist/services/logger.mjs +71 -0
- package/dist/services/logger.mjs.map +1 -0
- package/dist/services/notifier.d.mts +17 -0
- package/dist/services/notifier.d.mts.map +1 -0
- package/dist/services/notifier.mjs +71 -0
- package/dist/services/notifier.mjs.map +1 -0
- package/dist/services/picker.d.mts +36 -0
- package/dist/services/picker.d.mts.map +1 -0
- package/dist/services/picker.mjs +137 -0
- package/dist/services/picker.mjs.map +1 -0
- package/dist/services/prompt.d.mts +30 -0
- package/dist/services/prompt.d.mts.map +1 -0
- package/dist/services/prompt.mjs +83 -0
- package/dist/services/prompt.mjs.map +1 -0
- package/dist/shiki-BrjVUkeh.mjs +423 -0
- package/dist/shiki-BrjVUkeh.mjs.map +1 -0
- package/dist/size-D8JogYLy.mjs +34 -0
- package/dist/size-D8JogYLy.mjs.map +1 -0
- package/dist/style.d.mts +10 -0
- package/dist/style.d.mts.map +1 -0
- package/dist/style.mjs +4 -0
- package/dist/text.d.mts +33 -0
- package/dist/text.d.mts.map +1 -0
- package/dist/text.mjs +111 -0
- package/dist/text.mjs.map +1 -0
- package/dist/theme.config-CU3B50HN.mjs +832 -0
- package/dist/theme.config-CU3B50HN.mjs.map +1 -0
- package/dist/themes.d.mts +113 -0
- package/dist/themes.d.mts.map +1 -0
- package/dist/themes.mjs +2 -0
- package/dist/tree-C28QOVIK.d.mts +23 -0
- package/dist/tree-C28QOVIK.d.mts.map +1 -0
- package/dist/types-B9Eh7k2v.d.mts +212 -0
- package/dist/types-B9Eh7k2v.d.mts.map +1 -0
- package/dist/types-D3jk1rtk.d.mts +140 -0
- package/dist/types-D3jk1rtk.d.mts.map +1 -0
- package/dist/utils-3M4D_AXZ.mjs +40 -0
- package/dist/utils-3M4D_AXZ.mjs.map +1 -0
- package/dist/widgets/autocomplete.d.mts +2 -0
- package/dist/widgets/autocomplete.mjs +165 -0
- package/dist/widgets/autocomplete.mjs.map +1 -0
- package/dist/widgets/box.d.mts +2 -0
- package/dist/widgets/box.mjs +2 -0
- package/dist/widgets/code.d.mts +48 -0
- package/dist/widgets/code.d.mts.map +1 -0
- package/dist/widgets/code.mjs +59 -0
- package/dist/widgets/code.mjs.map +1 -0
- package/dist/widgets/completions/actions.d.mts +41 -0
- package/dist/widgets/completions/actions.d.mts.map +1 -0
- package/dist/widgets/completions/actions.mjs +48 -0
- package/dist/widgets/completions/actions.mjs.map +1 -0
- package/dist/widgets/completions/files.d.mts +29 -0
- package/dist/widgets/completions/files.d.mts.map +1 -0
- package/dist/widgets/completions/files.mjs +42 -0
- package/dist/widgets/completions/files.mjs.map +1 -0
- package/dist/widgets/completions/github.d.mts +64 -0
- package/dist/widgets/completions/github.d.mts.map +1 -0
- package/dist/widgets/completions/github.mjs +137 -0
- package/dist/widgets/completions/github.mjs.map +1 -0
- package/dist/widgets/completions/index.d.mts +4 -0
- package/dist/widgets/completions/index.mjs +4 -0
- package/dist/widgets/diff.d.mts +57 -0
- package/dist/widgets/diff.d.mts.map +1 -0
- package/dist/widgets/diff.mjs +199 -0
- package/dist/widgets/diff.mjs.map +1 -0
- package/dist/widgets/divider.d.mts +11 -0
- package/dist/widgets/divider.d.mts.map +1 -0
- package/dist/widgets/divider.mjs +9 -0
- package/dist/widgets/divider.mjs.map +1 -0
- package/dist/widgets/image.d.mts +37 -0
- package/dist/widgets/image.d.mts.map +1 -0
- package/dist/widgets/image.mjs +77 -0
- package/dist/widgets/image.mjs.map +1 -0
- package/dist/widgets/input.d.mts +2 -0
- package/dist/widgets/input.mjs +497 -0
- package/dist/widgets/input.mjs.map +1 -0
- package/dist/widgets/log.d.mts +2 -0
- package/dist/widgets/log.mjs +130 -0
- package/dist/widgets/log.mjs.map +1 -0
- package/dist/widgets/markdown.d.mts +45 -0
- package/dist/widgets/markdown.d.mts.map +1 -0
- package/dist/widgets/markdown.mjs +124 -0
- package/dist/widgets/markdown.mjs.map +1 -0
- package/dist/widgets/overlay.d.mts +2 -0
- package/dist/widgets/overlay.mjs +18 -0
- package/dist/widgets/overlay.mjs.map +1 -0
- package/dist/widgets/picker.d.mts +2 -0
- package/dist/widgets/picker.mjs +2 -0
- package/dist/widgets/progress.d.mts +48 -0
- package/dist/widgets/progress.d.mts.map +1 -0
- package/dist/widgets/progress.mjs +48 -0
- package/dist/widgets/progress.mjs.map +1 -0
- package/dist/widgets/select.d.mts +2 -0
- package/dist/widgets/select.mjs +195 -0
- package/dist/widgets/select.mjs.map +1 -0
- package/dist/widgets/show.d.mts +55 -0
- package/dist/widgets/show.d.mts.map +1 -0
- package/dist/widgets/show.mjs +85 -0
- package/dist/widgets/show.mjs.map +1 -0
- package/dist/widgets/spinner.d.mts +65 -0
- package/dist/widgets/spinner.d.mts.map +1 -0
- package/dist/widgets/spinner.mjs +147 -0
- package/dist/widgets/spinner.mjs.map +1 -0
- package/dist/widgets/text.d.mts +2 -0
- package/dist/widgets/text.mjs +32 -0
- package/dist/widgets/text.mjs.map +1 -0
- package/dist/widgets/tree.d.mts +2 -0
- package/dist/widgets/tree.mjs +73 -0
- package/dist/widgets/tree.mjs.map +1 -0
- package/dist/widgets/widget.d.mts +49 -0
- package/dist/widgets/widget.d.mts.map +1 -0
- package/dist/widgets/widget.mjs +40 -0
- package/dist/widgets/widget.mjs.map +1 -0
- package/dist/worker-DjHuQSbY.mjs +27 -0
- package/dist/worker-DjHuQSbY.mjs.map +1 -0
- package/package.json +117 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { safeStat } from "@zaly/shared";
|
|
2
|
+
import { isSSH } from "@zaly/shared/env";
|
|
3
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "pathe";
|
|
5
|
+
import { Spawn, spawnText, spawnWithInput, which } from "@zaly/shared/process";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { platform } from "node:process";
|
|
8
|
+
//#region src/input/clipboard.ts
|
|
9
|
+
let cached;
|
|
10
|
+
let cacheResolved = false;
|
|
11
|
+
/** Detect + cache the clipboard provider for this process. Returns
|
|
12
|
+
* `undefined` when no provider is available. */
|
|
13
|
+
function getClipboard() {
|
|
14
|
+
if (cacheResolved) return cached;
|
|
15
|
+
cached = detect();
|
|
16
|
+
cacheResolved = true;
|
|
17
|
+
return cached;
|
|
18
|
+
}
|
|
19
|
+
async function readClipboard(kind) {
|
|
20
|
+
const p = getClipboard();
|
|
21
|
+
if (!p) return void 0;
|
|
22
|
+
try {
|
|
23
|
+
return await p.read(kind);
|
|
24
|
+
} catch {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Reset the cached provider — mostly for tests that want to force a
|
|
29
|
+
* re-probe. Also useful if the environment changes (e.g. Wayland
|
|
30
|
+
* display started after process boot). */
|
|
31
|
+
function resetClipboardCache() {
|
|
32
|
+
cached = void 0;
|
|
33
|
+
cacheResolved = false;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Copy text to the system clipboard.
|
|
37
|
+
*
|
|
38
|
+
* Policy:
|
|
39
|
+
* - **SSH session** → always OSC 52. Native tools on the remote box
|
|
40
|
+
* would write to the remote clipboard, which isn't what the user
|
|
41
|
+
* wants. OSC 52 routes through the local terminal.
|
|
42
|
+
* - **Native tool available** → use it (fast, silent, no prompts).
|
|
43
|
+
* - **Neither** → OSC 52 anyway, fire-and-forget.
|
|
44
|
+
*
|
|
45
|
+
* Resolves to `true` on best-effort delivery; `false` only when we had
|
|
46
|
+
* nothing to try. OSC 52 writes are silent by design — the terminal
|
|
47
|
+
* never confirms receipt — so `true` means "we emitted" not "the
|
|
48
|
+
* user's clipboard definitely has it".
|
|
49
|
+
*/
|
|
50
|
+
async function writeClipboardText(text) {
|
|
51
|
+
if (isSSH) {
|
|
52
|
+
writeOsc52(text);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
const p = getClipboard();
|
|
56
|
+
if (p?.writeText) try {
|
|
57
|
+
if (await p.writeText(text)) return true;
|
|
58
|
+
} catch {}
|
|
59
|
+
writeOsc52(text);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Namespace export for readable call sites:
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* import { clipboard } from "@zaly/tui"
|
|
67
|
+
*
|
|
68
|
+
* await clipboard.read()
|
|
69
|
+
* await clipboard.read("text")
|
|
70
|
+
* await clipboard.write("hello")
|
|
71
|
+
* clipboard.reset()
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
const clipboard = {
|
|
75
|
+
read: readClipboard,
|
|
76
|
+
reset: resetClipboardCache,
|
|
77
|
+
write: writeClipboardText
|
|
78
|
+
};
|
|
79
|
+
/** Emit an OSC 52 clipboard write. Fire-and-forget; some terminals
|
|
80
|
+
* ignore or prompt on this, and there's no response channel. */
|
|
81
|
+
function writeOsc52(text) {
|
|
82
|
+
const b64 = Buffer.from(text, "utf8").toString("base64");
|
|
83
|
+
process.stdout.write(`\x1b]52;c;${b64}\x07`);
|
|
84
|
+
}
|
|
85
|
+
function detect() {
|
|
86
|
+
if (platform === "darwin" && which("pbpaste")) return macos();
|
|
87
|
+
if (process.env.WAYLAND_DISPLAY && which("wl-paste")) return wayland();
|
|
88
|
+
if (process.env.DISPLAY) {
|
|
89
|
+
if (which("xsel")) return xsel();
|
|
90
|
+
if (which("xclip")) return xclip();
|
|
91
|
+
}
|
|
92
|
+
if (which("win32yank.exe")) return wsl();
|
|
93
|
+
if (platform === "win32") return windows();
|
|
94
|
+
}
|
|
95
|
+
function macos() {
|
|
96
|
+
return {
|
|
97
|
+
id: "pbpaste",
|
|
98
|
+
read: async (kind) => {
|
|
99
|
+
if (kind === "image") return macosImage();
|
|
100
|
+
if (kind === "files") return macosFiles();
|
|
101
|
+
if (kind === "text") return macosText();
|
|
102
|
+
const img = await macosImage();
|
|
103
|
+
if (img) return img;
|
|
104
|
+
const files = await macosFiles();
|
|
105
|
+
if (files) return files;
|
|
106
|
+
return macosText();
|
|
107
|
+
},
|
|
108
|
+
writeText: (text) => spawnWithInput("pbcopy", [], text)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async function macosText() {
|
|
112
|
+
const text = await spawnText("pbpaste", []);
|
|
113
|
+
return text === void 0 ? void 0 : {
|
|
114
|
+
kind: "text",
|
|
115
|
+
text
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
async function macosImage() {
|
|
119
|
+
const out = await spawnText("osascript", ["-e", `
|
|
120
|
+
try
|
|
121
|
+
set png to (the clipboard as «class PNGf»)
|
|
122
|
+
set b64 to do shell script "base64" with input (png as string)
|
|
123
|
+
return b64
|
|
124
|
+
on error
|
|
125
|
+
return ""
|
|
126
|
+
end try
|
|
127
|
+
`]);
|
|
128
|
+
if (!out) return void 0;
|
|
129
|
+
return writeTempPng(Buffer.from(out.trim(), "base64"));
|
|
130
|
+
}
|
|
131
|
+
async function macosFiles() {
|
|
132
|
+
const out = await spawnText("osascript", ["-e", `
|
|
133
|
+
try
|
|
134
|
+
set theClipboard to the clipboard as «class furl»
|
|
135
|
+
return POSIX path of theClipboard
|
|
136
|
+
on error
|
|
137
|
+
try
|
|
138
|
+
set theList to the clipboard as list
|
|
139
|
+
set posix to ""
|
|
140
|
+
repeat with item_ in theList
|
|
141
|
+
try
|
|
142
|
+
set posix to posix & (POSIX path of item_) & linefeed
|
|
143
|
+
end try
|
|
144
|
+
end repeat
|
|
145
|
+
return posix
|
|
146
|
+
on error
|
|
147
|
+
return ""
|
|
148
|
+
end try
|
|
149
|
+
end try
|
|
150
|
+
`]);
|
|
151
|
+
if (!out) return void 0;
|
|
152
|
+
const paths = out.split(/\r?\n/).map((s) => s.trim()).filter((s) => s !== "");
|
|
153
|
+
return paths.length > 0 ? {
|
|
154
|
+
kind: "files",
|
|
155
|
+
paths
|
|
156
|
+
} : void 0;
|
|
157
|
+
}
|
|
158
|
+
function wayland() {
|
|
159
|
+
return {
|
|
160
|
+
id: "wl-paste",
|
|
161
|
+
read: async (kind) => {
|
|
162
|
+
if (kind === "image") return waylandImage();
|
|
163
|
+
if (kind === "files") return waylandFiles();
|
|
164
|
+
if (kind === "text") return waylandText();
|
|
165
|
+
const types = await spawnText("wl-paste", ["--list-types"]);
|
|
166
|
+
if (!types) return void 0;
|
|
167
|
+
if (/\bimage\/png\b/.test(types)) {
|
|
168
|
+
const img = await waylandImage();
|
|
169
|
+
if (img) return img;
|
|
170
|
+
}
|
|
171
|
+
if (/\btext\/uri-list\b/.test(types)) {
|
|
172
|
+
const files = await waylandFiles();
|
|
173
|
+
if (files) return files;
|
|
174
|
+
}
|
|
175
|
+
return waylandText();
|
|
176
|
+
},
|
|
177
|
+
writeText: (text) => spawnWithInput("wl-copy", ["--type", "text/plain"], text)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async function waylandImage() {
|
|
181
|
+
const r = await new Spawn("wl-paste", ["--type", "image/png"]).result;
|
|
182
|
+
return r.code === 0 && r.stdout.length > 0 ? writeTempPng(r.stdout) : void 0;
|
|
183
|
+
}
|
|
184
|
+
async function waylandFiles() {
|
|
185
|
+
const paths = parseUriList(await spawnText("wl-paste", ["--type", "text/uri-list"]));
|
|
186
|
+
return paths.length > 0 ? {
|
|
187
|
+
kind: "files",
|
|
188
|
+
paths
|
|
189
|
+
} : void 0;
|
|
190
|
+
}
|
|
191
|
+
async function waylandText() {
|
|
192
|
+
const text = await spawnText("wl-paste", ["--no-newline"]);
|
|
193
|
+
return text === void 0 ? void 0 : {
|
|
194
|
+
kind: "text",
|
|
195
|
+
text
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function xsel() {
|
|
199
|
+
const xclipFallback = which("xclip");
|
|
200
|
+
return {
|
|
201
|
+
id: xclipFallback ? "xsel+xclip" : "xsel",
|
|
202
|
+
read: async (kind) => {
|
|
203
|
+
if (kind === "text") return xselText();
|
|
204
|
+
if (xclipFallback) {
|
|
205
|
+
if (kind === "image") return xclipImage();
|
|
206
|
+
if (kind === "files") return xclipFiles();
|
|
207
|
+
const rich = await xclipRead();
|
|
208
|
+
if (rich && rich.kind !== "text") return rich;
|
|
209
|
+
}
|
|
210
|
+
return xselText();
|
|
211
|
+
},
|
|
212
|
+
writeText: (text) => spawnWithInput("xsel", [
|
|
213
|
+
"--nodetach",
|
|
214
|
+
"-i",
|
|
215
|
+
"-b"
|
|
216
|
+
], text)
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function xclip() {
|
|
220
|
+
return {
|
|
221
|
+
id: "xclip",
|
|
222
|
+
read: (kind) => {
|
|
223
|
+
if (kind === "image") return xclipImage();
|
|
224
|
+
if (kind === "files") return xclipFiles();
|
|
225
|
+
if (kind === "text") return xclipText();
|
|
226
|
+
return xclipRead();
|
|
227
|
+
},
|
|
228
|
+
writeText: (text) => spawnWithInput("xclip", [
|
|
229
|
+
"-quiet",
|
|
230
|
+
"-i",
|
|
231
|
+
"-selection",
|
|
232
|
+
"clipboard"
|
|
233
|
+
], text)
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
async function xclipRead() {
|
|
237
|
+
const targets = await spawnText("xclip", [
|
|
238
|
+
"-selection",
|
|
239
|
+
"clipboard",
|
|
240
|
+
"-t",
|
|
241
|
+
"TARGETS",
|
|
242
|
+
"-o"
|
|
243
|
+
]);
|
|
244
|
+
if (!targets) return void 0;
|
|
245
|
+
if (/\bimage\/png\b/.test(targets)) {
|
|
246
|
+
const img = await xclipImage();
|
|
247
|
+
if (img) return img;
|
|
248
|
+
}
|
|
249
|
+
if (/\btext\/uri-list\b/.test(targets)) {
|
|
250
|
+
const files = await xclipFiles();
|
|
251
|
+
if (files) return files;
|
|
252
|
+
}
|
|
253
|
+
return xclipText();
|
|
254
|
+
}
|
|
255
|
+
async function xclipImage() {
|
|
256
|
+
const r = await new Spawn("xclip", [
|
|
257
|
+
"-selection",
|
|
258
|
+
"clipboard",
|
|
259
|
+
"-t",
|
|
260
|
+
"image/png",
|
|
261
|
+
"-o"
|
|
262
|
+
]).result;
|
|
263
|
+
return r.code === 0 && r.stdout.length > 0 ? writeTempPng(r.stdout) : void 0;
|
|
264
|
+
}
|
|
265
|
+
async function xclipFiles() {
|
|
266
|
+
const paths = parseUriList(await spawnText("xclip", [
|
|
267
|
+
"-selection",
|
|
268
|
+
"clipboard",
|
|
269
|
+
"-t",
|
|
270
|
+
"text/uri-list",
|
|
271
|
+
"-o"
|
|
272
|
+
]));
|
|
273
|
+
return paths.length > 0 ? {
|
|
274
|
+
kind: "files",
|
|
275
|
+
paths
|
|
276
|
+
} : void 0;
|
|
277
|
+
}
|
|
278
|
+
async function xclipText() {
|
|
279
|
+
const text = await spawnText("xclip", [
|
|
280
|
+
"-o",
|
|
281
|
+
"-selection",
|
|
282
|
+
"clipboard"
|
|
283
|
+
]);
|
|
284
|
+
return text === void 0 ? void 0 : {
|
|
285
|
+
kind: "text",
|
|
286
|
+
text
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
async function xselText() {
|
|
290
|
+
const text = await spawnText("xsel", ["-o", "-b"]);
|
|
291
|
+
return text === void 0 ? void 0 : {
|
|
292
|
+
kind: "text",
|
|
293
|
+
text
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function wsl() {
|
|
297
|
+
return {
|
|
298
|
+
id: "win32yank",
|
|
299
|
+
read: async (kind) => {
|
|
300
|
+
if (kind === "image" || kind === "files") return void 0;
|
|
301
|
+
const text = await spawnText("win32yank.exe", ["-o", "--lf"]);
|
|
302
|
+
return text === void 0 ? void 0 : {
|
|
303
|
+
kind: "text",
|
|
304
|
+
text
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
writeText: (text) => spawnWithInput("win32yank.exe", ["-i", "--crlf"], text)
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function windows() {
|
|
311
|
+
return {
|
|
312
|
+
id: "powershell",
|
|
313
|
+
read: async (kind) => {
|
|
314
|
+
if (kind === "image") return windowsImage();
|
|
315
|
+
if (kind === "files") return windowsFiles();
|
|
316
|
+
if (kind === "text") return windowsText();
|
|
317
|
+
const img = await windowsImage();
|
|
318
|
+
if (img) return img;
|
|
319
|
+
const files = await windowsFiles();
|
|
320
|
+
if (files) return files;
|
|
321
|
+
return windowsText();
|
|
322
|
+
},
|
|
323
|
+
writeText: (text) => spawnWithInput("clip", [], text)
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
async function windowsText() {
|
|
327
|
+
const text = await spawnText("powershell", [
|
|
328
|
+
"-NoProfile",
|
|
329
|
+
"-NoLogo",
|
|
330
|
+
"-Command",
|
|
331
|
+
"Get-Clipboard"
|
|
332
|
+
]);
|
|
333
|
+
return text === void 0 ? void 0 : {
|
|
334
|
+
kind: "text",
|
|
335
|
+
text
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
async function windowsImage() {
|
|
339
|
+
const tmp = writeTempPath("zaly-clip-", ".png");
|
|
340
|
+
await spawnText("powershell", [
|
|
341
|
+
"-NoProfile",
|
|
342
|
+
"-NoLogo",
|
|
343
|
+
"-Command",
|
|
344
|
+
[
|
|
345
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
346
|
+
"$img = [System.Windows.Forms.Clipboard]::GetImage()",
|
|
347
|
+
`if ($img) { $img.Save('${tmp.replace(/\\/g, String.raw`\\`)}') }`
|
|
348
|
+
].join("; ")
|
|
349
|
+
]);
|
|
350
|
+
if ((safeStat(tmp)?.size ?? 0) > 0) return {
|
|
351
|
+
kind: "image",
|
|
352
|
+
path: tmp,
|
|
353
|
+
type: "image/png"
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
async function windowsFiles() {
|
|
357
|
+
const out = await spawnText("powershell", [
|
|
358
|
+
"-NoProfile",
|
|
359
|
+
"-NoLogo",
|
|
360
|
+
"-Command",
|
|
361
|
+
"Get-Clipboard -Format FileDropList | ForEach-Object { $_.FullName }"
|
|
362
|
+
]);
|
|
363
|
+
if (!out) return void 0;
|
|
364
|
+
const paths = out.split(/\r?\n/).map((s) => s.trim()).filter((s) => s !== "");
|
|
365
|
+
return paths.length > 0 ? {
|
|
366
|
+
kind: "files",
|
|
367
|
+
paths
|
|
368
|
+
} : void 0;
|
|
369
|
+
}
|
|
370
|
+
function writeTempPng(bytes) {
|
|
371
|
+
const path = writeTempPath("zaly-clip-", ".png");
|
|
372
|
+
writeFileSync(path, bytes);
|
|
373
|
+
return {
|
|
374
|
+
kind: "image",
|
|
375
|
+
path,
|
|
376
|
+
type: "image/png"
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/** Parse a `text/uri-list` blob (RFC 2483): `file://…\r\n` lines, skip
|
|
380
|
+
* blanks and comment lines (`#…`), decode percent-encoding. Returns
|
|
381
|
+
* absolute filesystem paths. Non-`file://` URIs are dropped — we
|
|
382
|
+
* can't hand a remote URL off as an attachment path. */
|
|
383
|
+
function parseUriList(raw) {
|
|
384
|
+
if (!raw) return [];
|
|
385
|
+
const out = [];
|
|
386
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
387
|
+
const s = line.trim();
|
|
388
|
+
if (s === "" || s.startsWith("#")) continue;
|
|
389
|
+
if (!s.startsWith("file://")) continue;
|
|
390
|
+
try {
|
|
391
|
+
let path = decodeURIComponent(s.slice(7));
|
|
392
|
+
if (/^\/[A-Za-z]:\//.test(path)) path = path.slice(1);
|
|
393
|
+
out.push(path);
|
|
394
|
+
} catch {}
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
}
|
|
398
|
+
function writeTempPath(prefix, ext) {
|
|
399
|
+
return join(mkdtempSync(join(tmpdir(), prefix)), `clip${ext}`);
|
|
400
|
+
}
|
|
401
|
+
//#endregion
|
|
402
|
+
export { clipboard };
|
|
403
|
+
|
|
404
|
+
//# sourceMappingURL=clipboard.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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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"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { n as resolveSize, t as clamp } from "./size-D8JogYLy.mjs";
|
|
2
|
+
import { sliceAnsi, stringWidth } from "@zaly/shared/ansi";
|
|
3
|
+
//#region src/layout/flex.ts
|
|
4
|
+
/**
|
|
5
|
+
* Allocate widths to row-direction children with CSS-style flex
|
|
6
|
+
* semantics. Each item starts at its **basis**:
|
|
7
|
+
*
|
|
8
|
+
* - `width: <number | "N%">` → resolved fixed width.
|
|
9
|
+
* - `width: "fill"` → 0 (claim slack, ignore intrinsic).
|
|
10
|
+
* - any other non-fixed spec → `natural` (content size, 0 when not
|
|
11
|
+
* provided).
|
|
12
|
+
*
|
|
13
|
+
* Three branches:
|
|
14
|
+
*
|
|
15
|
+
* - `slack > 0`: distribute by `flexGrow`. Items without grow weight
|
|
16
|
+
* stay at basis; leftover slack is *not* smeared onto siblings.
|
|
17
|
+
* - `slack < 0` (deficit): distribute the shortfall by
|
|
18
|
+
* `basis * flexShrink` (CSS shrink default = 1). Each item is
|
|
19
|
+
* clamped at its `minWidth` floor so content doesn't break.
|
|
20
|
+
* - `slack == 0`: items take their basis as-is.
|
|
21
|
+
*
|
|
22
|
+
* `min`/`max` (combined with `intrinsicMin`) clamp per-item; rounding
|
|
23
|
+
* remainder lands on the last grower / shrinker.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
function allocateRow(items, opts) {
|
|
28
|
+
const { contentWidth } = opts;
|
|
29
|
+
const available = contentWidth - opts.gap * Math.max(0, items.length - 1);
|
|
30
|
+
const bases = Array.from({ length: items.length });
|
|
31
|
+
const grows = Array.from({ length: items.length });
|
|
32
|
+
const shrinks = Array.from({ length: items.length });
|
|
33
|
+
const mins = Array.from({ length: items.length });
|
|
34
|
+
let baseSum = 0;
|
|
35
|
+
let growSum = 0;
|
|
36
|
+
for (let i = 0; i < items.length; i++) {
|
|
37
|
+
const item = items[i];
|
|
38
|
+
if (isFixedWidth(item.width)) {
|
|
39
|
+
bases[i] = resolveSize(item.width, contentWidth) ?? 0;
|
|
40
|
+
grows[i] = 0;
|
|
41
|
+
shrinks[i] = 0;
|
|
42
|
+
} else if (item.width === "fill") {
|
|
43
|
+
bases[i] = 0;
|
|
44
|
+
grows[i] = item.flexGrow ?? 1;
|
|
45
|
+
shrinks[i] = item.flexShrink ?? 1;
|
|
46
|
+
} else {
|
|
47
|
+
bases[i] = item.natural ?? 0;
|
|
48
|
+
grows[i] = item.flexGrow ?? 0;
|
|
49
|
+
shrinks[i] = item.flexShrink ?? 1;
|
|
50
|
+
}
|
|
51
|
+
const intrinsicMin = item.intrinsicMin ?? 0;
|
|
52
|
+
const userMin = resolveSize(item.minWidth, contentWidth) ?? 0;
|
|
53
|
+
mins[i] = Math.max(intrinsicMin, userMin);
|
|
54
|
+
baseSum += bases[i];
|
|
55
|
+
growSum += grows[i];
|
|
56
|
+
}
|
|
57
|
+
const slack = available - baseSum;
|
|
58
|
+
const widths = Array.from({ length: items.length });
|
|
59
|
+
let allocated = 0;
|
|
60
|
+
let lastGrowIndex = -1;
|
|
61
|
+
let lastShrinkIndex = -1;
|
|
62
|
+
if (slack >= 0) {
|
|
63
|
+
for (let i = 0; i < items.length; i++) {
|
|
64
|
+
const item = items[i];
|
|
65
|
+
let w = bases[i];
|
|
66
|
+
if (growSum > 0 && grows[i] > 0) {
|
|
67
|
+
w += Math.floor(slack * grows[i] / growSum);
|
|
68
|
+
lastGrowIndex = i;
|
|
69
|
+
}
|
|
70
|
+
w = clamp(w, {
|
|
71
|
+
available: contentWidth,
|
|
72
|
+
max: item.maxWidth,
|
|
73
|
+
min: mins[i]
|
|
74
|
+
});
|
|
75
|
+
widths[i] = w;
|
|
76
|
+
allocated += w;
|
|
77
|
+
}
|
|
78
|
+
if (lastGrowIndex !== -1) {
|
|
79
|
+
const target = baseSum + slack;
|
|
80
|
+
if (allocated !== target) widths[lastGrowIndex] = Math.max(0, widths[lastGrowIndex] + (target - allocated));
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
for (let i = 0; i < items.length; i++) widths[i] = bases[i];
|
|
84
|
+
let deficit = -slack;
|
|
85
|
+
const frozen = Array.from({ length: items.length }, () => false);
|
|
86
|
+
for (let pass = 0; pass <= items.length && deficit > 0; pass++) {
|
|
87
|
+
let weightSum = 0;
|
|
88
|
+
for (let i = 0; i < items.length; i++) if (!frozen[i]) weightSum += widths[i] * shrinks[i];
|
|
89
|
+
if (weightSum === 0) break;
|
|
90
|
+
let froze = false;
|
|
91
|
+
for (let i = 0; i < items.length; i++) {
|
|
92
|
+
if (frozen[i]) continue;
|
|
93
|
+
const share = Math.floor(deficit * widths[i] * shrinks[i] / weightSum);
|
|
94
|
+
const next = widths[i] - share;
|
|
95
|
+
if (next <= mins[i]) {
|
|
96
|
+
deficit -= widths[i] - mins[i];
|
|
97
|
+
widths[i] = mins[i];
|
|
98
|
+
frozen[i] = true;
|
|
99
|
+
froze = true;
|
|
100
|
+
} else {
|
|
101
|
+
widths[i] = next;
|
|
102
|
+
lastShrinkIndex = i;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!froze) deficit = 0;
|
|
106
|
+
}
|
|
107
|
+
for (let i = 0; i < items.length; i++) {
|
|
108
|
+
widths[i] = clamp(widths[i], {
|
|
109
|
+
available: contentWidth,
|
|
110
|
+
max: items[i].maxWidth,
|
|
111
|
+
min: mins[i]
|
|
112
|
+
});
|
|
113
|
+
allocated += widths[i];
|
|
114
|
+
}
|
|
115
|
+
if (lastShrinkIndex !== -1) {
|
|
116
|
+
const target = Math.max(available, mins.reduce((a, b) => a + b, 0));
|
|
117
|
+
if (allocated !== target) {
|
|
118
|
+
const adj = target - allocated;
|
|
119
|
+
widths[lastShrinkIndex] = Math.max(mins[lastShrinkIndex], widths[lastShrinkIndex] + adj);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return widths;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Whether a `Flexible.width` spec is a fixed numeric size (number or
|
|
127
|
+
* `Pct`). `"fill"`/`"fit"`/undefined are non-fixed and resolve from
|
|
128
|
+
* surrounding context (slack / measurement).
|
|
129
|
+
*
|
|
130
|
+
* @internal
|
|
131
|
+
*/
|
|
132
|
+
function isFixedWidth(w) {
|
|
133
|
+
return typeof w === "number" || typeof w === "string" && w.endsWith("%");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Zip pre-rendered child rows horizontally. Shorter children are padded with
|
|
137
|
+
* blank rows of their allocated width. Rows of each child must already be
|
|
138
|
+
* padded to the corresponding width in `widths`.
|
|
139
|
+
*
|
|
140
|
+
* @internal
|
|
141
|
+
*/
|
|
142
|
+
function zipRow(children, opts) {
|
|
143
|
+
if (children.length === 0) return [];
|
|
144
|
+
const gapStr = " ".repeat(opts.gap);
|
|
145
|
+
const height = children.reduce((h, rows) => Math.max(h, rows.length), 0);
|
|
146
|
+
const out = [];
|
|
147
|
+
for (let r = 0; r < height; r++) {
|
|
148
|
+
let line = "";
|
|
149
|
+
for (let c = 0; c < children.length; c++) {
|
|
150
|
+
if (c > 0) line += gapStr;
|
|
151
|
+
const rows = children[c];
|
|
152
|
+
line += r < rows.length ? rows[r] : " ".repeat(opts.widths[c]);
|
|
153
|
+
}
|
|
154
|
+
out.push(line);
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Stack pre-rendered child rows vertically. `gap` blank rows of `width`
|
|
160
|
+
* spaces separate each sibling pair that *contributes rows*. Children
|
|
161
|
+
* that emit no rows (e.g. `visible: false`, falsy `show()` branch) are
|
|
162
|
+
* skipped along with the gap that would have surrounded them — no
|
|
163
|
+
* stray blank bands. Caller must ensure each child's rows are already
|
|
164
|
+
* padded to `width`.
|
|
165
|
+
*
|
|
166
|
+
* @internal
|
|
167
|
+
*/
|
|
168
|
+
function stackColumn(children, opts) {
|
|
169
|
+
const out = [];
|
|
170
|
+
const blank = " ".repeat(opts.width);
|
|
171
|
+
let any = false;
|
|
172
|
+
for (const child of children) {
|
|
173
|
+
if (child.length === 0) continue;
|
|
174
|
+
if (any) for (let g = 0; g < opts.gap; g++) out.push(blank);
|
|
175
|
+
out.push(...child);
|
|
176
|
+
any = true;
|
|
177
|
+
}
|
|
178
|
+
return out;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Pad a row to `width` cells without injecting a `RESET`. Used by the
|
|
182
|
+
* row/column zip-and-pad steps — the parent `Box`'s outer
|
|
183
|
+
* `reapplyStyle` chain handles re-emitting any ambient SGR after inner
|
|
184
|
+
* `[0m`s, so the pad cells stay inside the active style. Over-wide
|
|
185
|
+
* rows are clipped via `sliceAnsi`, preserving SGR state.
|
|
186
|
+
*
|
|
187
|
+
* Distinct from `padOrClip` in `style/ansi.ts`, which is for
|
|
188
|
+
* standalone (non-Box-wrapped) paths and *does* inject a `RESET`
|
|
189
|
+
* before the pad.
|
|
190
|
+
*
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
function padRow(row, width) {
|
|
194
|
+
const w = stringWidth(row);
|
|
195
|
+
if (w === width) return row;
|
|
196
|
+
if (w < width) return row + " ".repeat(width - w);
|
|
197
|
+
return sliceAnsi(row, 0, width);
|
|
198
|
+
}
|
|
199
|
+
//#endregion
|
|
200
|
+
export { zipRow as a, stackColumn as i, isFixedWidth as n, padRow as r, allocateRow as t };
|
|
201
|
+
|
|
202
|
+
//# sourceMappingURL=flex-Bguo-fUe.mjs.map
|