@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,279 @@
|
|
|
1
|
+
import { i as hyperlink } from "./ansi-Clq0ozyA.mjs";
|
|
2
|
+
import { t as allocateRow } from "./flex-Bguo-fUe.mjs";
|
|
3
|
+
import { calcLayout, capitalize, formatText } from "./text.mjs";
|
|
4
|
+
import { splitAnsi, stringWidth } from "@zaly/shared/ansi";
|
|
5
|
+
//#region src/markdown/code.ts
|
|
6
|
+
/**
|
|
7
|
+
* Build the `code` callback. The returned closure captures ctx/style/highlighter
|
|
8
|
+
* and renders each fenced block: syntax-highlight (if available), pad lines to
|
|
9
|
+
* widest-content + 1, apply `mdCodeBlock` as a bg-only backdrop, and prefix an
|
|
10
|
+
* optional title line from `title="..."`.
|
|
11
|
+
*/
|
|
12
|
+
function createCodeCallback(ctx) {
|
|
13
|
+
const s = ctx.style;
|
|
14
|
+
return (text, meta) => {
|
|
15
|
+
text = text.replace(/\n+$/, "");
|
|
16
|
+
const highlighted = ctx.highlight?.(text, meta?.language) ?? text;
|
|
17
|
+
const wrap = highlighted === text ? s.mdCodeBlock : s.mdCodeBlock.fg("inherit");
|
|
18
|
+
const hpad = 2;
|
|
19
|
+
const maxWidth = ctx.width;
|
|
20
|
+
const lines = formatText(`\n${highlighted}\n`, {
|
|
21
|
+
width: maxWidth - hpad * 2,
|
|
22
|
+
wrap: "word"
|
|
23
|
+
});
|
|
24
|
+
let width = Math.max(...lines.map(stringWidth)) + hpad * 2;
|
|
25
|
+
width = Math.min(width, maxWidth);
|
|
26
|
+
const padded = lines.map((line) => {
|
|
27
|
+
const w = stringWidth(line);
|
|
28
|
+
if (w >= width - hpad) return wrap(line);
|
|
29
|
+
return wrap(`${" ".repeat(hpad)}${line}${" ".repeat(width - w - hpad)}`);
|
|
30
|
+
});
|
|
31
|
+
return `\n${meta?.title === void 0 ? "" : `${s.mdCodeBlockTitle(meta.title)}\n`}${padded.join("\n")}\n\n`;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/markdown/list.ts
|
|
36
|
+
const BULLETS = [
|
|
37
|
+
"●",
|
|
38
|
+
"○",
|
|
39
|
+
"◆",
|
|
40
|
+
"◇"
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Stack-based list renderer. Each depth has its own `ListState`. Inner
|
|
44
|
+
* lists return `""` and stash their formatted output on `state.rendered`;
|
|
45
|
+
* the parent `listItem` pulls + splices, so inner output never reaches
|
|
46
|
+
* the parent's `children` string. That keeps each level's loose-vs-tight
|
|
47
|
+
* verdict independent — `children.endsWith("\n\n")` is then the per-item
|
|
48
|
+
* loose signal (parser only emits `<p>` for items in loose lists).
|
|
49
|
+
*
|
|
50
|
+
* Nested list lands at the end of its parent's body — `text, list, more`
|
|
51
|
+
* is rare in real markdown and not worth the extra plumbing.
|
|
52
|
+
*/
|
|
53
|
+
function createListCallbacks(ctx) {
|
|
54
|
+
const s = ctx.style;
|
|
55
|
+
const stack = [];
|
|
56
|
+
return {
|
|
57
|
+
list: (_children, meta) => {
|
|
58
|
+
const state = stack[meta.depth];
|
|
59
|
+
if (state === void 0 || state.items.length === 0) return "";
|
|
60
|
+
state.rendered = state.items.join(state.block ? "\n\n" : "\n");
|
|
61
|
+
if (meta.depth > 0) return "";
|
|
62
|
+
stack.length = 0;
|
|
63
|
+
return `${state.rendered}\n\n`;
|
|
64
|
+
},
|
|
65
|
+
listItem: (children, meta) => {
|
|
66
|
+
let marker = s.mdListBullet(meta.ordered ? `${(meta.start ?? 1) + meta.index}.` : BULLETS[meta.depth % BULLETS.length]);
|
|
67
|
+
if (meta.checked !== void 0) {
|
|
68
|
+
const ref = meta.checked ? "mdListChecked" : "mdListUnchecked";
|
|
69
|
+
marker += ` ${s.add(ref)(meta.checked ? "[x]" : "[ ]")}`;
|
|
70
|
+
}
|
|
71
|
+
const indent = " ".repeat(stringWidth(marker));
|
|
72
|
+
const nested = stack[meta.depth + 1];
|
|
73
|
+
stack[meta.depth + 1] = void 0;
|
|
74
|
+
const block = children.endsWith("\n\n");
|
|
75
|
+
const trimmed = children.replace(/\s*\n+$/, "").replace(/\n{3,}/g, "\n\n");
|
|
76
|
+
let body = trimmed;
|
|
77
|
+
if (nested?.rendered !== void 0) body = trimmed === "" ? nested.rendered : `${trimmed}${block ? "\n\n" : "\n"}${nested.rendered}`;
|
|
78
|
+
const content = splitAnsi(body).map((line, l) => {
|
|
79
|
+
if (l === 0) return `${marker} ${line}`;
|
|
80
|
+
return line === "" ? "" : `${indent}${line}`;
|
|
81
|
+
}).join("\n");
|
|
82
|
+
const state = stack[meta.depth] ??= {
|
|
83
|
+
block: false,
|
|
84
|
+
items: []
|
|
85
|
+
};
|
|
86
|
+
state.block ||= block;
|
|
87
|
+
state.items.push(content);
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
//#region src/markdown/table.ts
|
|
94
|
+
/**
|
|
95
|
+
* Build the table-related callbacks. They share a closure-local
|
|
96
|
+
* `TableState` accumulator: `th`/`td` push cells, `tr` commits rows into
|
|
97
|
+
* header or body, and `table` runs the final layout + reset. Tables can't
|
|
98
|
+
* nest in markdown, so a single top-level slot is enough.
|
|
99
|
+
*/
|
|
100
|
+
function createTableCallbacks(ctx) {
|
|
101
|
+
const { style } = ctx;
|
|
102
|
+
let state;
|
|
103
|
+
const begin = () => state ??= {
|
|
104
|
+
body: [],
|
|
105
|
+
header: [],
|
|
106
|
+
pending: [],
|
|
107
|
+
pendingIsHeader: false
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
table: () => {
|
|
111
|
+
if (state === void 0) return "";
|
|
112
|
+
const out = formatTable(state, ctx);
|
|
113
|
+
state = void 0;
|
|
114
|
+
return `${out}\n\n`;
|
|
115
|
+
},
|
|
116
|
+
tbody: () => "",
|
|
117
|
+
td: (children, meta) => {
|
|
118
|
+
const t = begin();
|
|
119
|
+
t.pending.push({
|
|
120
|
+
align: meta?.align,
|
|
121
|
+
text: children
|
|
122
|
+
});
|
|
123
|
+
t.pendingIsHeader = false;
|
|
124
|
+
return "";
|
|
125
|
+
},
|
|
126
|
+
th: (children, meta) => {
|
|
127
|
+
const t = begin();
|
|
128
|
+
t.pending.push({
|
|
129
|
+
align: meta?.align,
|
|
130
|
+
text: style.mdTableHeader(children)
|
|
131
|
+
});
|
|
132
|
+
t.pendingIsHeader = true;
|
|
133
|
+
return "";
|
|
134
|
+
},
|
|
135
|
+
thead: () => "",
|
|
136
|
+
tr: () => {
|
|
137
|
+
if (state === void 0 || state.pending.length === 0) return "";
|
|
138
|
+
if (state.pendingIsHeader) state.header.push(state.pending);
|
|
139
|
+
else state.body.push(state.pending);
|
|
140
|
+
state.pending = [];
|
|
141
|
+
return "";
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function formatTable(state, ctx) {
|
|
146
|
+
const cols = [];
|
|
147
|
+
const cells = [...state.header, ...state.body];
|
|
148
|
+
const colCount = Math.max(...cells.map((r) => r.length));
|
|
149
|
+
for (let c = 0; c < colCount; c++) for (const row of cells) {
|
|
150
|
+
const cell = row[c];
|
|
151
|
+
if (!cell) continue;
|
|
152
|
+
const l = calcLayout(cell.text);
|
|
153
|
+
cols[c] ??= { ...l };
|
|
154
|
+
cols[c].width = Math.max(cols[c].width, l.width);
|
|
155
|
+
cols[c].minWidth = Math.max(cols[c].minWidth, l.minWidth);
|
|
156
|
+
}
|
|
157
|
+
const chromeWidth = 3 * colCount + 1;
|
|
158
|
+
const widths = allocateRow(cols.map((c) => ({
|
|
159
|
+
flex: 1,
|
|
160
|
+
intrinsicMin: c.minWidth,
|
|
161
|
+
natural: c.width
|
|
162
|
+
})), {
|
|
163
|
+
contentWidth: ctx.width - chromeWidth,
|
|
164
|
+
gap: 0
|
|
165
|
+
});
|
|
166
|
+
const style = ctx.style;
|
|
167
|
+
const v = style.mdTable("│");
|
|
168
|
+
const ruleRow = (left, mid, right) => style.mdTable(left + widths.map((w) => "─".repeat(w + 2)).join(mid) + right);
|
|
169
|
+
const top = ruleRow("┌", "┬", "┐");
|
|
170
|
+
const sep = ruleRow("├", "┼", "┤");
|
|
171
|
+
const bot = ruleRow("└", "┴", "┘");
|
|
172
|
+
const renderRow = (row) => {
|
|
173
|
+
const formatted = row.map((cell, c) => formatText(cell.text, {
|
|
174
|
+
indent: false,
|
|
175
|
+
width: widths[c],
|
|
176
|
+
wrapBg: false
|
|
177
|
+
}));
|
|
178
|
+
const rows = [];
|
|
179
|
+
const rowCount = Math.max(...formatted.map((r) => r.length));
|
|
180
|
+
for (let r = 0; r < rowCount; r++) {
|
|
181
|
+
const line = [];
|
|
182
|
+
for (let c = 0; c < colCount; c++) {
|
|
183
|
+
const t = formatted[c]?.[r] ?? "";
|
|
184
|
+
line.push(padCell(t, widths[c], row[c]?.align));
|
|
185
|
+
}
|
|
186
|
+
rows.push(`${v} ${line.join(` ${v} `)} ${v}`);
|
|
187
|
+
}
|
|
188
|
+
return rows.join("\n");
|
|
189
|
+
};
|
|
190
|
+
const lines = [top];
|
|
191
|
+
for (const row of state.header) lines.push(renderRow(row));
|
|
192
|
+
if (state.header.length > 0 && state.body.length > 0) lines.push(sep);
|
|
193
|
+
for (const row of state.body) lines.push(renderRow(row));
|
|
194
|
+
lines.push(bot);
|
|
195
|
+
return lines.join("\n");
|
|
196
|
+
}
|
|
197
|
+
function padCell(text, width, align) {
|
|
198
|
+
const slack = Math.max(0, width - stringWidth(text));
|
|
199
|
+
if (align === "right") return " ".repeat(slack) + text;
|
|
200
|
+
if (align === "center") {
|
|
201
|
+
const left = Math.floor(slack / 2);
|
|
202
|
+
return " ".repeat(left) + text + " ".repeat(slack - left);
|
|
203
|
+
}
|
|
204
|
+
return text + " ".repeat(slack);
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/markdown/callbacks.ts
|
|
208
|
+
const icons = {
|
|
209
|
+
hr: "─",
|
|
210
|
+
quote: "┃"
|
|
211
|
+
};
|
|
212
|
+
const alerts = {
|
|
213
|
+
CAUTION: {
|
|
214
|
+
icon: "✖ ",
|
|
215
|
+
style: "error"
|
|
216
|
+
},
|
|
217
|
+
IMPORTANT: {
|
|
218
|
+
icon: "‼",
|
|
219
|
+
style: "syntaxConstant"
|
|
220
|
+
},
|
|
221
|
+
NOTE: {
|
|
222
|
+
icon: "ℹ ",
|
|
223
|
+
style: "info"
|
|
224
|
+
},
|
|
225
|
+
TIP: {
|
|
226
|
+
icon: "🛈 ",
|
|
227
|
+
style: "success"
|
|
228
|
+
},
|
|
229
|
+
WARNING: {
|
|
230
|
+
icon: "⚠",
|
|
231
|
+
style: "warn"
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const alertRe = /^\s*\[!(NOTE|TIP|WARNING|IMPORTANT|CAUTION)\]\s*$/;
|
|
235
|
+
/**
|
|
236
|
+
* Build the `MdCallbacks` that drive the theme-aware rendering. Exposed so
|
|
237
|
+
* callers can invoke `renderMarkdown` directly when they need the string
|
|
238
|
+
* output without the wrapping `Markdown` node (e.g. to embed markdown inside
|
|
239
|
+
* a custom `Text` content function).
|
|
240
|
+
* @internal
|
|
241
|
+
*/
|
|
242
|
+
function createCallbacks(ctx) {
|
|
243
|
+
const s = ctx.style;
|
|
244
|
+
return {
|
|
245
|
+
blockquote: (children) => {
|
|
246
|
+
const lines = children.replace(/\n+$/, "").split("\n");
|
|
247
|
+
let lineStyle = "mdQuote";
|
|
248
|
+
const m = lines.length >= 1 ? lines[0].match(alertRe) : void 0;
|
|
249
|
+
if (m) {
|
|
250
|
+
const alertType = m[1];
|
|
251
|
+
const alert = alerts[alertType];
|
|
252
|
+
lineStyle = alert.style;
|
|
253
|
+
const name = capitalize(alertType.toLowerCase());
|
|
254
|
+
lines[0] = s.add(lineStyle)(`${alert.icon} ${name}`);
|
|
255
|
+
}
|
|
256
|
+
return `${lines.map((line) => `${s.add(lineStyle)(icons.quote)} ${line}`).join("\n")}\n\n`;
|
|
257
|
+
},
|
|
258
|
+
code: createCodeCallback(ctx),
|
|
259
|
+
codespan: (text) => s.mdCode(text),
|
|
260
|
+
emphasis: (children) => s.mdItalic(children),
|
|
261
|
+
heading: (children, { level }) => {
|
|
262
|
+
const width = ctx.width;
|
|
263
|
+
const padded = children.split("\n").map((line) => line + " ".repeat(Math.max(0, width - stringWidth(line)))).join("\n");
|
|
264
|
+
return `${s.add(`mdHeading${level}`)(padded)}\n\n`;
|
|
265
|
+
},
|
|
266
|
+
hr: () => `${s.mdHr(icons.hr.repeat(ctx.width))}\n\n`,
|
|
267
|
+
html: (children) => children,
|
|
268
|
+
link: (children, { href }) => hyperlink(href, s.mdLink(children)),
|
|
269
|
+
paragraph: (children) => `${children}\n\n`,
|
|
270
|
+
strikethrough: (children) => s.mdStrikethrough(children),
|
|
271
|
+
strong: (children) => s.mdBold(children),
|
|
272
|
+
...createListCallbacks(ctx),
|
|
273
|
+
...createTableCallbacks(ctx)
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
export { createCallbacks };
|
|
278
|
+
|
|
279
|
+
//# sourceMappingURL=callbacks-CNjGNxVw.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callbacks-CNjGNxVw.mjs","names":[],"sources":["../src/markdown/code.ts","../src/markdown/list.ts","../src/markdown/table.ts","../src/markdown/callbacks.ts"],"sourcesContent":["import type { MarkdownCtx } from \"./renderer.ts\"\nimport type { MdCallbacks } from \"./types.ts\"\n\nimport { stringWidth } from \"@zaly/shared/ansi\"\nimport { formatText } from \"../layout/text.ts\"\n\n/**\n * Build the `code` callback. The returned closure captures ctx/style/highlighter\n * and renders each fenced block: syntax-highlight (if available), pad lines to\n * widest-content + 1, apply `mdCodeBlock` as a bg-only backdrop, and prefix an\n * optional title line from `title=\"...\"`.\n */\nexport function createCodeCallback(ctx: MarkdownCtx): NonNullable<MdCallbacks[\"code\"]> {\n const s = ctx.style\n return (text, meta) => {\n text = text.replace(/\\n+$/, \"\") // trim to avoid extra blank lines after the block\n const highlighted = ctx.highlight?.(text, meta?.language) ?? text\n\n // For highlighted output the per-token fgs would clash with the slot's\n // fg; override fg with `\"inherit\"` so only bg + attrs wrap each row\n // and shiki's per-token colors show through on top of the backdrop.\n const wrap = highlighted === text ? s.mdCodeBlock : s.mdCodeBlock.fg(\"inherit\")\n const hpad = 2\n const maxWidth = ctx.width\n const lines = formatText(`\\n${highlighted}\\n`, {\n width: maxWidth - hpad * 2,\n wrap: \"word\",\n })\n let width = Math.max(...lines.map(stringWidth)) + hpad * 2\n width = Math.min(width, maxWidth) // leave room for the border\n // Left-align with a fixed `hpad` of leading whitespace; pad the\n // right to fill the bg out to `width`. Centering would push short\n // lines toward the middle, which reads as visually misaligned code.\n // Lines wider than the available room (width − hpad) drop the\n // leading pad so the bg ends at the cap rather than overshooting.\n const padded = lines.map((line) => {\n const w = stringWidth(line)\n if (w >= width - hpad) return wrap(line)\n return wrap(`${\" \".repeat(hpad)}${line}${\" \".repeat(width - w - hpad)}`)\n })\n const titleLine = meta?.title === undefined ? \"\" : `${s.mdCodeBlockTitle(meta.title)}\\n`\n // Leading `\\n` guards against raw-text siblings that don't emit a\n // paragraph-wrap (e.g. some inline-only list items where the parser\n // drops the paragraph). Containers (`listItem`, document) collapse\n // `\\n{3,}` → `\\n\\n` so paragraph + code doesn't double up.\n return `\\n${titleLine}${padded.join(\"\\n\")}\\n\\n`\n }\n}\n","import type { RenderCtx } from \"../core/ctx.ts\"\nimport type { MdCallbacks } from \"./types.ts\"\n\nimport { splitAnsi, stringWidth } from \"@zaly/shared/ansi\"\n\ninterface ListState {\n block: boolean\n items: string[]\n /** Formatted output, set when the list closes. The parent `listItem`\n * pulls it via `stack[depth+1]` and splices it onto its own body. */\n rendered?: string\n}\n\nconst BULLETS = [\"●\", \"○\", \"◆\", \"◇\"] as const\n\ntype ListCallbacks = Pick<MdCallbacks, \"list\" | \"listItem\">\n\n/**\n * Stack-based list renderer. Each depth has its own `ListState`. Inner\n * lists return `\"\"` and stash their formatted output on `state.rendered`;\n * the parent `listItem` pulls + splices, so inner output never reaches\n * the parent's `children` string. That keeps each level's loose-vs-tight\n * verdict independent — `children.endsWith(\"\\n\\n\")` is then the per-item\n * loose signal (parser only emits `<p>` for items in loose lists).\n *\n * Nested list lands at the end of its parent's body — `text, list, more`\n * is rare in real markdown and not worth the extra plumbing.\n */\nexport function createListCallbacks(ctx: RenderCtx): ListCallbacks {\n const s = ctx.style\n // Lazy-pushed on the first `listItem` at a given depth; consumed\n // either by the parent `listItem` (nested) or by `list(0)` (top).\n const stack: (ListState | undefined)[] = []\n\n return {\n list: (_children, meta) => {\n const state = stack[meta.depth]\n if (state === undefined || state.items.length === 0) return \"\"\n state.rendered = state.items.join(state.block ? \"\\n\\n\" : \"\\n\")\n if (meta.depth > 0) return \"\"\n // Top-level: defensive stack reset + block-contract `\\n\\n` trailer.\n stack.length = 0\n return `${state.rendered}\\n\\n`\n },\n\n listItem: (children, meta) => {\n let marker = s.mdListBullet(\n meta.ordered ? `${(meta.start ?? 1) + meta.index}.` : BULLETS[meta.depth % BULLETS.length]\n )\n if (meta.checked !== undefined) {\n const ref = meta.checked ? \"mdListChecked\" : \"mdListUnchecked\"\n marker += ` ${s.add(ref)(meta.checked ? \"[x]\" : \"[ ]\")}`\n }\n const indent = \" \".repeat(stringWidth(marker))\n // Pull nested list output (if any) off the stack.\n const nested = stack[meta.depth + 1]\n stack[meta.depth + 1] = undefined\n // Trailing `\\n\\n` = loose signal. Nested output is in `rendered`,\n // not `children`, so it can't pollute this check.\n const block = children.endsWith(\"\\n\\n\")\n const trimmed = children.replace(/\\s*\\n+$/, \"\").replace(/\\n{3,}/g, \"\\n\\n\")\n // Splice nested after the prose; loose item adds a blank-row gap.\n let body = trimmed\n if (nested?.rendered !== undefined) {\n const sep = block ? \"\\n\\n\" : \"\\n\"\n body = trimmed === \"\" ? nested.rendered : `${trimmed}${sep}${nested.rendered}`\n }\n const lines = splitAnsi(body)\n const content = lines\n .map((line, l) => {\n if (l === 0) return `${marker} ${line}`\n // Bare empty lines so loose `\\n\\n` separators stay clean.\n return line === \"\" ? \"\" : `${indent}${line}`\n })\n .join(\"\\n\")\n\n const state = (stack[meta.depth] ??= { block: false, items: [] })\n state.block ||= block\n state.items.push(content)\n return \"\"\n },\n }\n}\n","import type { RenderCtx } from \"../core/ctx.ts\"\nimport type { Layout } from \"../core/state.ts\"\nimport type { RowItem } from \"../layout/flex.ts\"\nimport type { MdCallbacks } from \"./types.ts\"\n\nimport { stringWidth } from \"@zaly/shared/ansi\"\nimport { allocateRow } from \"../layout/flex.ts\"\nimport { calcLayout, formatText } from \"../layout/text.ts\"\n\ntype CellAlign = \"left\" | \"center\" | \"right\"\n\ninterface TableCell {\n text: string\n align: CellAlign | undefined\n}\n\ninterface TableState {\n header: TableCell[][]\n body: TableCell[][]\n pending: TableCell[]\n pendingIsHeader: boolean\n}\n\ntype TableCallbacks = Pick<MdCallbacks, \"table\" | \"tbody\" | \"td\" | \"th\" | \"thead\" | \"tr\">\n\n/**\n * Build the table-related callbacks. They share a closure-local\n * `TableState` accumulator: `th`/`td` push cells, `tr` commits rows into\n * header or body, and `table` runs the final layout + reset. Tables can't\n * nest in markdown, so a single top-level slot is enough.\n */\nexport function createTableCallbacks(ctx: RenderCtx): TableCallbacks {\n const { style } = ctx\n let state: TableState | undefined\n const begin = (): TableState =>\n (state ??= { body: [], header: [], pending: [], pendingIsHeader: false })\n\n return {\n table: () => {\n // Children of th/td/tr/thead/tbody are empty strings; all content is\n // in `state`. Format, reset, emit.\n if (state === undefined) return \"\"\n const out = formatTable(state, ctx)\n state = undefined\n return `${out}\\n\\n`\n },\n\n tbody: () => \"\",\n\n td: (children, meta) => {\n const t = begin()\n t.pending.push({ align: meta?.align, text: children })\n t.pendingIsHeader = false\n return \"\"\n },\n\n th: (children, meta) => {\n const t = begin()\n t.pending.push({ align: meta?.align, text: style.mdTableHeader(children) })\n t.pendingIsHeader = true\n return \"\"\n },\n\n thead: () => \"\",\n\n tr: () => {\n if (state === undefined || state.pending.length === 0) return \"\"\n if (state.pendingIsHeader) state.header.push(state.pending)\n else state.body.push(state.pending)\n state.pending = []\n return \"\"\n },\n }\n}\n\nfunction formatTable(state: TableState, ctx: RenderCtx): string {\n const cols: Layout[] = []\n const cells = [...state.header, ...state.body]\n const colCount = Math.max(...cells.map((r) => r.length))\n\n // 1. Calculate min-content and max-content for each column\n for (let c = 0; c < colCount; c++) {\n for (const row of cells) {\n const cell = row[c] as TableCell | undefined\n if (!cell) continue // Safety guard for ragged rows\n const l = calcLayout(cell.text)\n cols[c] ??= { ...l }\n cols[c].width = Math.max(cols[c].width, l.width)\n cols[c].minWidth = Math.max(cols[c].minWidth, l.minWidth)\n }\n }\n const chromeWidth = 3 * colCount + 1\n\n const items: RowItem[] = cols.map((c) => ({\n flex: 1,\n intrinsicMin: c.minWidth,\n natural: c.width,\n }))\n\n const widths = allocateRow(items, { contentWidth: ctx.width - chromeWidth, gap: 0 })\n\n // 4. Render output string using the exact final Column Widths\n // Pre-style each piece of chrome once. Each column span on a rule row is\n // `widths[i] + 2` dashes (one cell of padding on each side of the cell).\n const style = ctx.style\n const v = style.mdTable(\"│\")\n const ruleRow = (left: string, mid: string, right: string): string =>\n style.mdTable(left + widths.map((w) => \"─\".repeat(w + 2)).join(mid) + right)\n const top = ruleRow(\"┌\", \"┬\", \"┐\")\n const sep = ruleRow(\"├\", \"┼\", \"┤\")\n const bot = ruleRow(\"└\", \"┴\", \"┘\")\n\n const renderRow = (row: TableCell[]): string => {\n const formatted = row.map((cell, c) =>\n formatText(cell.text, { indent: false, width: widths[c], wrapBg: false })\n )\n const rows: string[] = []\n const rowCount = Math.max(...formatted.map((r) => r.length))\n for (let r = 0; r < rowCount; r++) {\n const line: string[] = []\n for (let c = 0; c < colCount; c++) {\n const t = formatted[c]?.[r] ?? \"\"\n line.push(padCell(t, widths[c], row[c]?.align))\n }\n rows.push(`${v} ${line.join(` ${v} `)} ${v}`)\n }\n\n return rows.join(\"\\n\")\n }\n\n const lines: string[] = [top]\n for (const row of state.header) lines.push(renderRow(row))\n if (state.header.length > 0 && state.body.length > 0) lines.push(sep)\n for (const row of state.body) lines.push(renderRow(row))\n lines.push(bot)\n return lines.join(\"\\n\")\n}\n\nfunction padCell(text: string, width: number, align: CellAlign | undefined): string {\n const slack = Math.max(0, width - stringWidth(text))\n if (align === \"right\") return \" \".repeat(slack) + text\n if (align === \"center\") {\n const left = Math.floor(slack / 2)\n return \" \".repeat(left) + text + \" \".repeat(slack - left)\n }\n return text + \" \".repeat(slack)\n}\n","import type { AnyStyle } from \"../style/types.ts\"\nimport type { MarkdownCtx } from \"./renderer.ts\"\nimport type { MdCallbacks } from \"./types.ts\"\n\nimport { stringWidth } from \"@zaly/shared/ansi\"\nimport { capitalize } from \"../layout/text.ts\"\nimport { hyperlink } from \"../style/ansi.ts\"\nimport { createCodeCallback } from \"./code.ts\"\nimport { createListCallbacks } from \"./list.ts\"\nimport { createTableCallbacks } from \"./table.ts\"\n\nconst icons = {\n hr: \"─\",\n quote: \"┃\",\n} as const\n\ntype AlertType = \"NOTE\" | \"TIP\" | \"WARNING\" | \"IMPORTANT\" | \"CAUTION\"\nconst alerts: Record<AlertType, { icon: string; style: AnyStyle }> = {\n CAUTION: { icon: \"✖ \", style: \"error\" },\n IMPORTANT: { icon: \"‼\", style: \"syntaxConstant\" },\n NOTE: { icon: \"ℹ \", style: \"info\" },\n TIP: { icon: \"🛈 \", style: \"success\" },\n WARNING: { icon: \"⚠\", style: \"warn\" },\n}\n\nconst alertRe = /^\\s*\\[!(NOTE|TIP|WARNING|IMPORTANT|CAUTION)\\]\\s*$/\n\n/**\n * Build the `MdCallbacks` that drive the theme-aware rendering. Exposed so\n * callers can invoke `renderMarkdown` directly when they need the string\n * output without the wrapping `Markdown` node (e.g. to embed markdown inside\n * a custom `Text` content function).\n * @internal\n */\nexport function createCallbacks(ctx: MarkdownCtx): MdCallbacks {\n const s = ctx.style\n\n return {\n blockquote: (children) => {\n const lines = children.replace(/\\n+$/, \"\").split(\"\\n\")\n let lineStyle: AnyStyle = \"mdQuote\"\n\n const m = lines.length >= 1 ? lines[0].match(alertRe) : undefined\n if (m) {\n const alertType = m[1] as AlertType\n const alert = alerts[alertType]\n lineStyle = alert.style\n const name = capitalize(alertType.toLowerCase())\n lines[0] = s.add(lineStyle)(`${alert.icon} ${name}`)\n }\n\n // Inner blocks (typically a paragraph) end with trailing newlines; if\n // we prefix those empty lines with \"│ \" they render as styled empty\n // rows between the last line of the quote and the block separator.\n // Trim first so the quote stops cleanly.\n const prefixed = lines.map((line) => `${s.add(lineStyle)(icons.quote)} ${line}`).join(\"\\n\")\n return `${prefixed}\\n\\n`\n },\n\n code: createCodeCallback(ctx),\n\n codespan: (text) => s.mdCode(text),\n\n emphasis: (children) => s.mdItalic(children),\n\n heading: (children, { level }) => {\n const width = ctx.width\n const padded = children\n .split(\"\\n\")\n .map((line) => line + \" \".repeat(Math.max(0, width - stringWidth(line))))\n .join(\"\\n\")\n return `${s.add(`mdHeading${level}`)(padded)}\\n\\n`\n },\n\n hr: () => `${s.mdHr(icons.hr.repeat(ctx.width))}\\n\\n`,\n\n html: (children) => children,\n\n link: (children, { href }) => hyperlink(href, s.mdLink(children)),\n\n paragraph: (children) => `${children}\\n\\n`,\n\n strikethrough: (children) => s.mdStrikethrough(children),\n\n strong: (children) => s.mdBold(children),\n\n ...createListCallbacks(ctx),\n ...createTableCallbacks(ctx),\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { n as isShikiLang } from "./shiki-BrjVUkeh.mjs";
|
|
3
|
+
import { hash } from "@zaly/shared";
|
|
4
|
+
import { hasColors } from "@zaly/shared/env";
|
|
5
|
+
//#region src/shiki/client.ts
|
|
6
|
+
var client_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
ShikiWorkerClient: () => ShikiWorkerClient,
|
|
8
|
+
shikiWorker: () => shikiWorker
|
|
9
|
+
});
|
|
10
|
+
function isJob(s) {
|
|
11
|
+
return "key" in s && "lang" in s;
|
|
12
|
+
}
|
|
13
|
+
const MAX_CACHE_SIZE = 100;
|
|
14
|
+
const MAX_RUNNING = 2;
|
|
15
|
+
var ShikiWorkerClient = class {
|
|
16
|
+
#id = 0;
|
|
17
|
+
#cache = /* @__PURE__ */ new Map();
|
|
18
|
+
#queue = /* @__PURE__ */ new Map();
|
|
19
|
+
#running = /* @__PURE__ */ new Map();
|
|
20
|
+
#results = [];
|
|
21
|
+
#worker;
|
|
22
|
+
#workerPromise;
|
|
23
|
+
#updateScheduled = void 0;
|
|
24
|
+
key(req) {
|
|
25
|
+
return req.key ?? hash(`${req.lang}:${req.code}:${req.theme ?? "default"}`);
|
|
26
|
+
}
|
|
27
|
+
toJob(req) {
|
|
28
|
+
const id = ++this.#id;
|
|
29
|
+
const key = this.key(req);
|
|
30
|
+
if (!hasColors) return {
|
|
31
|
+
error: "terminal does not support colors",
|
|
32
|
+
id,
|
|
33
|
+
key,
|
|
34
|
+
value: req.code
|
|
35
|
+
};
|
|
36
|
+
if (!isShikiLang(req.lang)) return {
|
|
37
|
+
error: `unsupported language: ${req.lang}`,
|
|
38
|
+
id,
|
|
39
|
+
key,
|
|
40
|
+
value: req.code
|
|
41
|
+
};
|
|
42
|
+
const cached = this.#cache.get(key);
|
|
43
|
+
if (cached) {
|
|
44
|
+
this.#cache.delete(key);
|
|
45
|
+
this.#cache.set(key, cached);
|
|
46
|
+
return cached;
|
|
47
|
+
}
|
|
48
|
+
while (this.#cache.size > MAX_CACHE_SIZE) this.#cache.delete(this.#cache.keys().next().value);
|
|
49
|
+
const ret = {
|
|
50
|
+
...req,
|
|
51
|
+
id,
|
|
52
|
+
key,
|
|
53
|
+
lang: req.lang,
|
|
54
|
+
...Promise.withResolvers()
|
|
55
|
+
};
|
|
56
|
+
this.#cache.set(key, ret);
|
|
57
|
+
return ret;
|
|
58
|
+
}
|
|
59
|
+
highlight(code, lang, opts = {}) {
|
|
60
|
+
const req = {
|
|
61
|
+
code,
|
|
62
|
+
lang,
|
|
63
|
+
signal: opts.signal,
|
|
64
|
+
theme: opts.theme
|
|
65
|
+
};
|
|
66
|
+
const job = this.toJob(req);
|
|
67
|
+
if (!isJob(job)) return Promise.resolve(job);
|
|
68
|
+
if (!job.scheduled) {
|
|
69
|
+
this.#queue.set(job.id, job);
|
|
70
|
+
job.signal?.addEventListener("abort", () => this.update(), { once: true });
|
|
71
|
+
this.update();
|
|
72
|
+
}
|
|
73
|
+
return job.promise;
|
|
74
|
+
}
|
|
75
|
+
dispose() {
|
|
76
|
+
const error = /* @__PURE__ */ new Error("Shiki worker disposed");
|
|
77
|
+
this.#rejectAll(error);
|
|
78
|
+
}
|
|
79
|
+
async #request(job) {
|
|
80
|
+
job.scheduled = true;
|
|
81
|
+
this.#running.set(job.id, job);
|
|
82
|
+
const worker = await this.#createWorker();
|
|
83
|
+
const { signal: _s, promise: _prom, resolve: _res, reject: _rej, ...msg } = job;
|
|
84
|
+
worker.postMessage(msg);
|
|
85
|
+
}
|
|
86
|
+
async #createWorker() {
|
|
87
|
+
if (this.#worker) return this.#worker;
|
|
88
|
+
this.#workerPromise ??= (async () => {
|
|
89
|
+
const { createWorker } = await import("./worker-DjHuQSbY.mjs");
|
|
90
|
+
const worker = await createWorker(new URL("server.ts", import.meta.url));
|
|
91
|
+
worker.on("message", (event) => {
|
|
92
|
+
this.#running.delete(event.id);
|
|
93
|
+
this.#results.push(event);
|
|
94
|
+
this.update();
|
|
95
|
+
});
|
|
96
|
+
worker.on("error", (error) => {
|
|
97
|
+
this.#rejectAll(error);
|
|
98
|
+
});
|
|
99
|
+
return worker;
|
|
100
|
+
})();
|
|
101
|
+
return this.#worker = await this.#workerPromise;
|
|
102
|
+
}
|
|
103
|
+
update() {
|
|
104
|
+
this.#updateScheduled ??= this.#update().finally(() => {
|
|
105
|
+
this.#updateScheduled = void 0;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async #update() {
|
|
109
|
+
while (this.#results.length) {
|
|
110
|
+
const result = this.#results.shift();
|
|
111
|
+
const job = this.#queue.get(result.id);
|
|
112
|
+
if (job) {
|
|
113
|
+
if (result.error) job.reject(new Error(result.error));
|
|
114
|
+
else job.resolve(result);
|
|
115
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
116
|
+
this.#queue.delete(job.id);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const job of this.#queue.values()) if (job.signal?.aborted && !job.scheduled) {
|
|
120
|
+
job.reject(/* @__PURE__ */ new Error("Job aborted"));
|
|
121
|
+
this.#queue.delete(job.id);
|
|
122
|
+
this.#cache.delete(job.key);
|
|
123
|
+
}
|
|
124
|
+
if (this.#running.size >= MAX_RUNNING || this.#queue.size === 0) return;
|
|
125
|
+
const todo = [...this.#queue.values()].filter((j) => !j.scheduled).slice(-(MAX_RUNNING - this.#running.size));
|
|
126
|
+
await Promise.all(todo.map((j) => this.#request(j)));
|
|
127
|
+
}
|
|
128
|
+
#rejectAll(error) {
|
|
129
|
+
for (const { reject } of this.#queue.values()) reject(error);
|
|
130
|
+
this.#cache.clear();
|
|
131
|
+
this.#queue.clear();
|
|
132
|
+
this.#running.clear();
|
|
133
|
+
this.#results = [];
|
|
134
|
+
this.#worker?.terminate();
|
|
135
|
+
this.#worker = void 0;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const shikiWorker = new ShikiWorkerClient();
|
|
139
|
+
//#endregion
|
|
140
|
+
export { shikiWorker as n, client_exports as t };
|
|
141
|
+
|
|
142
|
+
//# sourceMappingURL=client-D6C_uczF.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
//#region src/input/clipboard.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Read text or image content from the system clipboard.
|
|
4
|
+
*
|
|
5
|
+
* Agent CLIs need this to make `ctrl-v` "just work" — most terminals
|
|
6
|
+
* don't forward clipboard paste events (they only forward typed keys),
|
|
7
|
+
* so the app has to query the OS clipboard itself via the platform's
|
|
8
|
+
* native tool. The detection order mirrors Neovim's clipboard provider:
|
|
9
|
+
*
|
|
10
|
+
* 1. macOS → `pbpaste` + `osascript` (for images)
|
|
11
|
+
* 2. Wayland → `wl-paste`
|
|
12
|
+
* 3. X11 (xsel) → `xsel`
|
|
13
|
+
* 4. X11 (xclip) → `xclip`
|
|
14
|
+
* 5. WSL → `win32yank.exe`
|
|
15
|
+
* 6. Windows → PowerShell `Get-Clipboard`
|
|
16
|
+
*
|
|
17
|
+
* The chosen provider is cached per process. Spawn primitives live in
|
|
18
|
+
* `@zaly/shared/process` — this file is just glue plus the per-platform
|
|
19
|
+
* AppleScript/PowerShell snippets.
|
|
20
|
+
*/
|
|
21
|
+
/** Image bytes on the clipboard, already written to a temp PNG. */
|
|
22
|
+
interface ClipboardImage {
|
|
23
|
+
kind: "image";
|
|
24
|
+
/** Absolute path to a temporary PNG file. Caller takes ownership. */
|
|
25
|
+
path: string;
|
|
26
|
+
/** MIME type. PNG only for now; kept for future formats. */
|
|
27
|
+
type: "image/png";
|
|
28
|
+
}
|
|
29
|
+
/** One or more file references on the clipboard — what you get when
|
|
30
|
+
* the user copies a file in their file manager and pastes. */
|
|
31
|
+
interface ClipboardFiles {
|
|
32
|
+
kind: "files";
|
|
33
|
+
/** Absolute filesystem paths. */
|
|
34
|
+
paths: string[];
|
|
35
|
+
}
|
|
36
|
+
/** Plain text on the clipboard — the common case. */
|
|
37
|
+
interface ClipboardText {
|
|
38
|
+
kind: "text";
|
|
39
|
+
text: string;
|
|
40
|
+
}
|
|
41
|
+
/** Anything the clipboard can hold that this library surfaces. */
|
|
42
|
+
type ClipboardContent = ClipboardImage | ClipboardFiles | ClipboardText;
|
|
43
|
+
/** One of the content kinds `ClipboardContent` can take. Derived from
|
|
44
|
+
* the union so adding a new variant doesn't require updating this
|
|
45
|
+
* alias separately. */
|
|
46
|
+
type ClipboardKind = ClipboardContent["kind"];
|
|
47
|
+
/**
|
|
48
|
+
* Read the system clipboard, returning the richest content available.
|
|
49
|
+
*
|
|
50
|
+
* Priority order (decided by each backend internally):
|
|
51
|
+
* 1. **image** — raw image bytes on the clipboard (e.g. a screenshot
|
|
52
|
+
* from `cmd-shift-4`, or an image copied from a web page).
|
|
53
|
+
* 2. **files** — file URIs (e.g. the user copied a PDF / video in
|
|
54
|
+
* their file manager).
|
|
55
|
+
* 3. **text** — plain text.
|
|
56
|
+
*
|
|
57
|
+
* Resolves to `undefined` when the clipboard is empty, no provider is
|
|
58
|
+
* available, or every read fails. Never throws — clipboard access is
|
|
59
|
+
* best-effort.
|
|
60
|
+
*/
|
|
61
|
+
declare function readClipboard(): Promise<ClipboardContent | undefined>;
|
|
62
|
+
declare function readClipboard(kind: "text"): Promise<ClipboardText | undefined>;
|
|
63
|
+
declare function readClipboard(kind: "image"): Promise<ClipboardImage | undefined>;
|
|
64
|
+
declare function readClipboard(kind: "files"): Promise<ClipboardFiles | undefined>;
|
|
65
|
+
/** Reset the cached provider — mostly for tests that want to force a
|
|
66
|
+
* re-probe. Also useful if the environment changes (e.g. Wayland
|
|
67
|
+
* display started after process boot). */
|
|
68
|
+
declare function resetClipboardCache(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Copy text to the system clipboard.
|
|
71
|
+
*
|
|
72
|
+
* Policy:
|
|
73
|
+
* - **SSH session** → always OSC 52. Native tools on the remote box
|
|
74
|
+
* would write to the remote clipboard, which isn't what the user
|
|
75
|
+
* wants. OSC 52 routes through the local terminal.
|
|
76
|
+
* - **Native tool available** → use it (fast, silent, no prompts).
|
|
77
|
+
* - **Neither** → OSC 52 anyway, fire-and-forget.
|
|
78
|
+
*
|
|
79
|
+
* Resolves to `true` on best-effort delivery; `false` only when we had
|
|
80
|
+
* nothing to try. OSC 52 writes are silent by design — the terminal
|
|
81
|
+
* never confirms receipt — so `true` means "we emitted" not "the
|
|
82
|
+
* user's clipboard definitely has it".
|
|
83
|
+
*/
|
|
84
|
+
declare function writeClipboardText(text: string): Promise<boolean>;
|
|
85
|
+
/**
|
|
86
|
+
* Namespace export for readable call sites:
|
|
87
|
+
*
|
|
88
|
+
* ```ts
|
|
89
|
+
* import { clipboard } from "@zaly/tui"
|
|
90
|
+
*
|
|
91
|
+
* await clipboard.read()
|
|
92
|
+
* await clipboard.read("text")
|
|
93
|
+
* await clipboard.write("hello")
|
|
94
|
+
* clipboard.reset()
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
declare const clipboard: {
|
|
98
|
+
read: typeof readClipboard;
|
|
99
|
+
reset: typeof resetClipboardCache;
|
|
100
|
+
write: typeof writeClipboardText;
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
export { ClipboardContent, ClipboardFiles, ClipboardImage, ClipboardKind, ClipboardText, clipboard };
|
|
104
|
+
//# sourceMappingURL=clipboard.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clipboard.d.mts","names":[],"sources":["../src/input/clipboard.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;UA+BiB;EACf;;EAEA;;EAEA;;;;UAKe;EACf;;EAEA;;;UAIe;EACf;EACA;;;KAIU,mBAAmB,iBAAiB,iBAAiB;;;;KAKrD,gBAAgB;;;;;;;;;;;;;;;iBA8Cb,iBAAiB,QAAQ;iBACzB,cAAc,eAAe,QAAQ;iBACrC,cAAc,gBAAgB,QAAQ;iBACtC,cAAc,gBAAgB,QAAQ;;;;iBAc5C;;;;;;;;;;;;;;;;iBAoBM,mBAAmB,eAAe;;;;;;;;;;;;;cA6BpC;EACX,aAAI;EACJ,cAAK;EACL,cAAK"}
|