@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 @@
|
|
|
1
|
+
{"version":3,"file":"types-D3jk1rtk.d.mts","names":[],"sources":["../src/markdown/types.ts"],"mappings":";UAEiB;;EAEf;;EAEA;;UAGe;;EAEf;;EAEA;;EAEA;;UAGe;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;UAGe;;EAEf;;UAGe;;EAEf;;EAEA;;UAGe;;EAEf;;EAEA;;KAGU,YAAY;KACZ,OAAO,MAAM,kBAAkB,MAAM;UAEhC;;EAEf;;EAEA;;;;;;;;UASe;EACf,UAAU,OAAO;EACjB,YAAY;EACZ,aAAa;EACb,QAAQ,kBAAkB,OAAO;EACjC,OAAO,OAAO;EACd,WAAW,OAAO;EAClB,KAAK;EACL,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,KAAK;EACL,MAAM,kBAAkB,OAAO;EAC/B,MAAM,kBAAkB,OAAO;EAC/B,OAAO;EACP,SAAS;EACT,WAAW;EACX,OAAO,OAAO;EACd,QAAQ,OAAO;EACf,WAAW;EACX,gBAAgB;EAChB,QAAQ;;;;;;;KAQE,kBAAkB,eAAe,WAAW,aAAa,OAAO;;;;;UAM3D;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;IAAwB;IAAe;IAAe;;;EAEtD;IAAuB;IAAe;;;;;;;;;EAQtC,SAAS"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replace spaces inside every fenced-block info-string with `FENCE_MARKER`
|
|
3
|
+
* so renderers that truncate after the first token (Bun) still surface the
|
|
4
|
+
* full info-string as the first token. Inverse of the decode step done by
|
|
5
|
+
* `parseCodeInfoString`.
|
|
6
|
+
*
|
|
7
|
+
* Closing fences (just ``` with optional trailing whitespace) are left
|
|
8
|
+
* untouched — rewriting their spaces into markers would prevent the
|
|
9
|
+
* parser from recognizing the closer, swallowing the rest of the document
|
|
10
|
+
* as code.
|
|
11
|
+
*/
|
|
12
|
+
function encodeFenceInfoStrings(md) {
|
|
13
|
+
return md.replaceAll(/^( {0,3}`{3,})([^\n]*)$/gm, (match, fence, info) => info.trim() === "" ? match : fence + info.replaceAll(" ", "\0"));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse a fenced code-block info-string like `jsx title="src/Hello.js"`.
|
|
17
|
+
* First token → language; `title="..."` / `title='...'` → title. Unknown
|
|
18
|
+
* attrs after the language are ignored.
|
|
19
|
+
*
|
|
20
|
+
* Input may carry `FENCE_MARKER` in place of spaces (from
|
|
21
|
+
* `encodeFenceInfoStrings`); those are decoded before parsing.
|
|
22
|
+
*/
|
|
23
|
+
function parseCodeInfoString(info) {
|
|
24
|
+
if (!info) return void 0;
|
|
25
|
+
const decoded = info.includes("\0") ? info.replaceAll("\0", " ") : info;
|
|
26
|
+
const firstSpace = decoded.search(/\s/);
|
|
27
|
+
if (firstSpace === -1) return { language: decoded };
|
|
28
|
+
const language = decoded.slice(0, firstSpace);
|
|
29
|
+
const rest = decoded.slice(firstSpace + 1);
|
|
30
|
+
const titleMatch = /title=(?:"([^"]*)"|'([^']*)')/.exec(rest);
|
|
31
|
+
const title = titleMatch === null ? void 0 : titleMatch[1] ?? titleMatch[2];
|
|
32
|
+
const meta = {};
|
|
33
|
+
if (language !== "") meta.language = language;
|
|
34
|
+
if (title !== void 0) meta.title = title;
|
|
35
|
+
return meta;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { parseCodeInfoString as n, encodeFenceInfoStrings as t };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=utils-3M4D_AXZ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils-3M4D_AXZ.mjs","names":[],"sources":["../src/markdown/utils.ts"],"sourcesContent":["// Lightweight string-level utilities for the markdown pipeline. Lives in\n// its own file so Bun callers can use them without pulling in `marked`\n// (which is the heavy dep in `src/md.ts`).\n\nimport type { MdCodeBlockMeta } from \"./types.ts\"\n\n/** Sentinel stitched into fence info-strings in place of spaces. */\nexport const FENCE_MARKER = \"\\u0000\"\n\n/**\n * Replace spaces inside every fenced-block info-string with `FENCE_MARKER`\n * so renderers that truncate after the first token (Bun) still surface the\n * full info-string as the first token. Inverse of the decode step done by\n * `parseCodeInfoString`.\n *\n * Closing fences (just ``` with optional trailing whitespace) are left\n * untouched — rewriting their spaces into markers would prevent the\n * parser from recognizing the closer, swallowing the rest of the document\n * as code.\n */\nexport function encodeFenceInfoStrings(md: string): string {\n return md.replaceAll(/^( {0,3}`{3,})([^\\n]*)$/gm, (match, fence: string, info: string) =>\n info.trim() === \"\" ? match : fence + info.replaceAll(\" \", FENCE_MARKER)\n )\n}\n\n/**\n * Parse a fenced code-block info-string like `jsx title=\"src/Hello.js\"`.\n * First token → language; `title=\"...\"` / `title='...'` → title. Unknown\n * attrs after the language are ignored.\n *\n * Input may carry `FENCE_MARKER` in place of spaces (from\n * `encodeFenceInfoStrings`); those are decoded before parsing.\n */\nexport function parseCodeInfoString(info: string | undefined): MdCodeBlockMeta | undefined {\n if (!info) return undefined\n const decoded = info.includes(FENCE_MARKER) ? info.replaceAll(FENCE_MARKER, \" \") : info\n const firstSpace = decoded.search(/\\s/)\n if (firstSpace === -1) return { language: decoded }\n const language = decoded.slice(0, firstSpace)\n const rest = decoded.slice(firstSpace + 1)\n const titleMatch = /title=(?:\"([^\"]*)\"|'([^']*)')/.exec(rest) as\n | [string, string | undefined, string | undefined]\n | null\n const title = titleMatch === null ? undefined : (titleMatch[1] ?? titleMatch[2])\n const meta: MdCodeBlockMeta = {}\n if (language !== \"\") meta.language = language\n if (title !== undefined) meta.title = title\n return meta\n}\n"],"mappings":";;;;;;;;;;;AAoBA,SAAgB,uBAAuB,IAAoB;CACzD,OAAO,GAAG,WAAW,8BAA8B,OAAO,OAAe,SACvE,KAAK,KAAK,MAAM,KAAK,QAAQ,QAAQ,KAAK,WAAW,KAAA,IAAiB,CACxE;AACF;;;;;;;;;AAUA,SAAgB,oBAAoB,MAAuD;CACzF,IAAI,CAAC,MAAM,OAAO,KAAA;CAClB,MAAM,UAAU,KAAK,SAAA,IAAqB,IAAI,KAAK,WAAA,MAAyB,GAAG,IAAI;CACnF,MAAM,aAAa,QAAQ,OAAO,IAAI;CACtC,IAAI,eAAe,IAAI,OAAO,EAAE,UAAU,QAAQ;CAClD,MAAM,WAAW,QAAQ,MAAM,GAAG,UAAU;CAC5C,MAAM,OAAO,QAAQ,MAAM,aAAa,CAAC;CACzC,MAAM,aAAa,gCAAgC,KAAK,IAAI;CAG5D,MAAM,QAAQ,eAAe,OAAO,KAAA,IAAa,WAAW,MAAM,WAAW;CAC7E,MAAM,OAAwB,CAAC;CAC/B,IAAI,aAAa,IAAI,KAAK,WAAW;CACrC,IAAI,UAAU,KAAA,GAAW,KAAK,QAAQ;CACtC,OAAO;AACT"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as AutocompleteState, i as AutocompleteOptions, n as Autocomplete, o as CompletionSource, r as AutocompleteEvents, s as autocomplete, t as AcceptFn } from "../autocomplete-Dfw-yQDa.mjs";
|
|
2
|
+
export { AcceptFn, Autocomplete, AutocompleteEvents, AutocompleteOptions, AutocompleteState, CompletionSource, autocomplete };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { S as unwrap, f as effect, h as memo, y as signal } from "../reactive-f7NpAi69.mjs";
|
|
2
|
+
import { t as Node } from "../node-BZ7f6HzU.mjs";
|
|
3
|
+
import { t as picker } from "../picker-ndVVt6qF.mjs";
|
|
4
|
+
import { Input } from "./input.mjs";
|
|
5
|
+
//#region src/widgets/autocomplete.ts
|
|
6
|
+
/**
|
|
7
|
+
* Autocomplete popup bound to an `Input`. Watches the input's value and
|
|
8
|
+
* cursor; when one of the configured sources' trigger regexes matches
|
|
9
|
+
* the text before the cursor, the source's `complete(query)` is called
|
|
10
|
+
* and the resulting items are shown in a `Menu` child.
|
|
11
|
+
*
|
|
12
|
+
* Selecting an item replaces the matched trigger-prefix + query with
|
|
13
|
+
* the item's `value` and fires `"complete"` for apps that want to react
|
|
14
|
+
* beyond plain text insertion.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* autocomplete({
|
|
18
|
+
* input: "chat-input",
|
|
19
|
+
* sources: {
|
|
20
|
+
* slash: {
|
|
21
|
+
* triggers: [/^\s*\//],
|
|
22
|
+
* complete: (q) => slashCommands.filter((c) => c.startsWith(q)),
|
|
23
|
+
* },
|
|
24
|
+
* },
|
|
25
|
+
* }).on("complete", (src, item) => { ... })
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Positioning is layout-based — place the `Autocomplete` in your tree
|
|
29
|
+
* (typically directly above the input inside the UI footer) and it
|
|
30
|
+
* takes as many rows as it has items (up to `maxHeight`). When no
|
|
31
|
+
* source matches, `visible` flips to `false` and the widget takes
|
|
32
|
+
* zero rows, so the footer naturally collapses.
|
|
33
|
+
*/
|
|
34
|
+
var Autocomplete = class Autocomplete extends Node {
|
|
35
|
+
static type = "autocomplete";
|
|
36
|
+
type = Autocomplete.type;
|
|
37
|
+
select;
|
|
38
|
+
#opts;
|
|
39
|
+
#sources;
|
|
40
|
+
#match;
|
|
41
|
+
constructor(opts) {
|
|
42
|
+
super({ visible: false });
|
|
43
|
+
this.#opts = opts;
|
|
44
|
+
this.#sources = opts.sources;
|
|
45
|
+
const [cancelled, cancel] = signal(false);
|
|
46
|
+
this.#match = memo(() => {
|
|
47
|
+
const ret = this.#detect();
|
|
48
|
+
if (ret) cancel(false);
|
|
49
|
+
return ret;
|
|
50
|
+
});
|
|
51
|
+
const source = memo(() => {
|
|
52
|
+
const m = this.#match();
|
|
53
|
+
return m ? this.#sources[m.source] : void 0;
|
|
54
|
+
});
|
|
55
|
+
const visible = memo(() => this.enabled && this.#match() !== void 0 && !cancelled() && this.select.count > 0);
|
|
56
|
+
effect(() => {
|
|
57
|
+
if (visible()) this.show();
|
|
58
|
+
else this.hide();
|
|
59
|
+
});
|
|
60
|
+
this.select = picker({
|
|
61
|
+
...opts,
|
|
62
|
+
items: memo(() => unwrap(source()?.complete) ?? []),
|
|
63
|
+
maxHeight: opts.maxHeight ?? 8,
|
|
64
|
+
pattern: memo(() => this.#match()?.query ?? ""),
|
|
65
|
+
render: memo(() => source()?.render),
|
|
66
|
+
visible
|
|
67
|
+
});
|
|
68
|
+
this.add(this.select);
|
|
69
|
+
this.select.bind(this.#opts.input);
|
|
70
|
+
this.select.on("complete", ({ item }) => this.#complete(item));
|
|
71
|
+
this.select.on("accept", ({ item }) => this.#accept(item));
|
|
72
|
+
this.select.on("close", () => cancel(true));
|
|
73
|
+
}
|
|
74
|
+
get #input() {
|
|
75
|
+
if (this.#opts.input instanceof Input) return this.#opts.input;
|
|
76
|
+
return this.#opts.input();
|
|
77
|
+
}
|
|
78
|
+
get enabled() {
|
|
79
|
+
return unwrap(this.#opts.enabled) ?? true;
|
|
80
|
+
}
|
|
81
|
+
#detect() {
|
|
82
|
+
if (!this.#input) return void 0;
|
|
83
|
+
const value = this.#input.state.value ?? "";
|
|
84
|
+
const cursor = this.#input.state.cursor ?? 0;
|
|
85
|
+
const before = value.slice(0, cursor);
|
|
86
|
+
for (const [name, src] of Object.entries(this.#sources)) for (const rx of src.triggers) {
|
|
87
|
+
const re = new RegExp(rx.source, rx.flags.replace("g", ""));
|
|
88
|
+
let searchFrom = 0;
|
|
89
|
+
let best;
|
|
90
|
+
while (searchFrom <= before.length) {
|
|
91
|
+
const m = re.exec(before.slice(searchFrom));
|
|
92
|
+
if (m === null) break;
|
|
93
|
+
const start = searchFrom + m.index;
|
|
94
|
+
const end = start + m[0].length;
|
|
95
|
+
best = {
|
|
96
|
+
end,
|
|
97
|
+
start
|
|
98
|
+
};
|
|
99
|
+
searchFrom = end;
|
|
100
|
+
}
|
|
101
|
+
if (best === void 0) continue;
|
|
102
|
+
const query = before.slice(best.end);
|
|
103
|
+
if (/\s/.test(query)) continue;
|
|
104
|
+
return {
|
|
105
|
+
end: best.end,
|
|
106
|
+
query,
|
|
107
|
+
source: name,
|
|
108
|
+
start: best.start
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
#complete(item) {
|
|
113
|
+
const match = this.#match();
|
|
114
|
+
if (match === void 0) return;
|
|
115
|
+
this.#replace(item, defaultAccept(item), match.end);
|
|
116
|
+
}
|
|
117
|
+
#accept(item) {
|
|
118
|
+
const match = this.#match();
|
|
119
|
+
if (match === void 0) return;
|
|
120
|
+
const source = this.#sources[match.source];
|
|
121
|
+
this.#replace(item, source.accept ? source.accept(item, match.query) : defaultAccept(item), match.start);
|
|
122
|
+
}
|
|
123
|
+
#replace(item, accepted, start) {
|
|
124
|
+
const match = this.#match();
|
|
125
|
+
if (match === void 0 || !this.#input) return;
|
|
126
|
+
const value = this.#input.state.value ?? "";
|
|
127
|
+
const cursor = this.#input.state.cursor ?? 0;
|
|
128
|
+
const tail = value.slice(cursor);
|
|
129
|
+
accepted ??= "";
|
|
130
|
+
const newValue = value.slice(0, start) + accepted + tail;
|
|
131
|
+
if (newValue.trim() === "") {
|
|
132
|
+
const completed = value.slice(0, match.end) + defaultAccept(item) + tail;
|
|
133
|
+
if (completed.trim() !== "") this.#input.historyAdd(completed);
|
|
134
|
+
}
|
|
135
|
+
this.#input.state.set({
|
|
136
|
+
cursor: start + accepted.length,
|
|
137
|
+
value: newValue
|
|
138
|
+
});
|
|
139
|
+
this.emit("complete", {
|
|
140
|
+
item,
|
|
141
|
+
source: match.source
|
|
142
|
+
});
|
|
143
|
+
this.hide();
|
|
144
|
+
}
|
|
145
|
+
_render(ctx) {
|
|
146
|
+
return this.select.render(ctx);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
/** Default `accept` fallback used when a source doesn't provide one.
|
|
150
|
+
* Assumes the item is `MenuItem`-shaped and inserts `value + " "` —
|
|
151
|
+
* matches the old `trailingSpace: true` behaviour. Returns `undefined`
|
|
152
|
+
* (clear-only) when the item has no `text`. */
|
|
153
|
+
function defaultAccept(item) {
|
|
154
|
+
return `${item.text} `;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Factory for `Autocomplete`. See the class doc for the full API.
|
|
158
|
+
*/
|
|
159
|
+
function autocomplete(opts) {
|
|
160
|
+
return new Autocomplete(opts);
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
export { Autocomplete, autocomplete };
|
|
164
|
+
|
|
165
|
+
//# sourceMappingURL=autocomplete.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autocomplete.mjs","names":["#sources","#opts","#match","#detect","#complete","#accept","#input","#replace"],"sources":["../../src/widgets/autocomplete.ts"],"sourcesContent":["import type { RenderCtx } from \"../core/ctx.ts\"\nimport type { BaseEvents } from \"../core/node.ts\"\nimport type { Accessor, Reactive, Ref } from \"../core/reactive.ts\"\nimport type { StyleState } from \"../core/state.ts\"\nimport type { SearchItems, SearchOptions } from \"../search/search.ts\"\nimport type { Option, OptionRender, Select } from \"./select.ts\"\n\nimport { Node } from \"../core/node.ts\"\nimport { effect, memo, signal, unwrap } from \"../core/reactive.ts\"\nimport { Input } from \"./input.ts\"\nimport { picker } from \"./picker.ts\"\n\n/** Called when the user picks `item` from this source. Return a string\n * to insert in place of the trigger + query range, or `undefined`\n * when the source handled the selection itself (dispatched an action,\n * opened a modal, …) and the trigger range should just be removed. */\nexport type AcceptFn<T> = (item: T, query: string) => string | undefined\n\n/** Single completion source. `triggers` are regexes run against the text\n * *before the cursor*; the first regex whose match ends in the trailing\n * word wins. The slice between match-end and the cursor becomes the\n * `query` passed to `complete`.\n *\n * `complete` receives a `match` helper bound to `query` — call it on\n * whatever string the source considers the matching field (action id,\n * file basename, contact email, etc.) and either filter or rank.\n *\n * `accept` controls what happens on selection. Default: insert\n * `item.value + \" \"`. Return `undefined` to have the source do its own\n * side effect (dispatch an action, open a picker, …) and just clear\n * the trigger range in the input.\n *\n * `render` is forwarded to the internal `Menu` so the source can carry\n * its own row presentation — handy when `T` isn't a plain `MenuItem`. */\nexport interface CompletionSource<T extends Option = Option> {\n triggers: readonly RegExp[]\n complete: Reactive<SearchItems<T>>\n accept?: AcceptFn<T>\n render?: OptionRender<T>\n}\n\n// oxlint-disable-next-line no-empty-interface\nexport interface AutocompleteState extends StyleState {}\n\nexport interface AutocompleteOptions extends SearchOptions {\n /** The `Input` to watch. Pass the node directly, or a `Ref<Input>`\n * populated by `node.ref(ref)` elsewhere in the tree — the latter\n * enables fully inline composition where the input doesn't need a\n * local binding. The ref is dereferenced on mount, so wiring is\n * type-safe and ordering-flexible (autocomplete can be constructed\n * before the Input is). */\n input: Input | Ref<Input>\n // Per-source item types are erased at this level — each source\n // declares its own `T` which is preserved within its own `complete`\n // / `accept` / `render` callbacks. The `complete` event payload is\n // consequently typed as `unknown`; callers discriminate by source\n // name and cast.\n sources: Record<string, CompletionSource<any>>\n /** Cap on rows the popup shows at once. Default: 8. */\n maxHeight?: Reactive<number>\n enabled?: Reactive<boolean>\n reverse?: boolean\n}\n\nexport interface AutocompleteEvents extends BaseEvents {\n /** Fired after an item is inserted into the input. Payload item type\n * is `unknown` because sources may have different `T`; discriminate\n * by source name and cast. */\n complete: { source: string; item: Option }\n}\n\ninterface Match {\n source: string\n /** Absolute start index of the trigger in the input value. */\n start: number\n /** Absolute end index of the trigger in the input value. */\n end: number\n /** The query (text between trigger-end and cursor). */\n query: string\n}\n\n// const AutocompleteBase = Node as unknown as abstract new <T extends MenuItem>(\n// ...args: ConstructorParameters<typeof Menu<T>>\n// ) => Node<MenuState<T>> & Emitter<MenuEvents<T> & EventMap>\n\n/**\n * Autocomplete popup bound to an `Input`. Watches the input's value and\n * cursor; when one of the configured sources' trigger regexes matches\n * the text before the cursor, the source's `complete(query)` is called\n * and the resulting items are shown in a `Menu` child.\n *\n * Selecting an item replaces the matched trigger-prefix + query with\n * the item's `value` and fires `\"complete\"` for apps that want to react\n * beyond plain text insertion.\n *\n * ```ts\n * autocomplete({\n * input: \"chat-input\",\n * sources: {\n * slash: {\n * triggers: [/^\\s*\\//],\n * complete: (q) => slashCommands.filter((c) => c.startsWith(q)),\n * },\n * },\n * }).on(\"complete\", (src, item) => { ... })\n * ```\n *\n * Positioning is layout-based — place the `Autocomplete` in your tree\n * (typically directly above the input inside the UI footer) and it\n * takes as many rows as it has items (up to `maxHeight`). When no\n * source matches, `visible` flips to `false` and the widget takes\n * zero rows, so the footer naturally collapses.\n */\nexport class Autocomplete extends Node<AutocompleteState, AutocompleteEvents> {\n static readonly type = \"autocomplete\"\n override readonly type = Autocomplete.type\n\n readonly select: Select\n #opts: AutocompleteOptions\n readonly #sources: Record<string, CompletionSource<any>>\n #match: Accessor<Match | undefined>\n\n constructor(opts: AutocompleteOptions) {\n super({ visible: false })\n this.#opts = opts\n this.#sources = opts.sources\n\n const [cancelled, cancel] = signal(false)\n\n this.#match = memo(() => {\n const ret = this.#detect()\n if (ret) cancel(false)\n return ret\n })\n\n const source = memo(() => {\n const m = this.#match()\n return m ? this.#sources[m.source] : undefined\n })\n\n const visible = memo(\n () => this.enabled && this.#match() !== undefined && !cancelled() && this.select.count > 0\n )\n\n // Use an effect instead of setting a signal, since Node.show/hide overwrites the signal\n effect(() => {\n if (visible()) this.show()\n else this.hide()\n })\n\n this.select = picker({\n ...opts,\n items: memo(() => unwrap(source()?.complete) ?? []),\n maxHeight: opts.maxHeight ?? 8,\n pattern: memo(() => this.#match()?.query ?? \"\"),\n render: memo(() => source()?.render),\n visible,\n })\n\n this.add(this.select)\n this.select.bind(this.#opts.input)\n\n // Bridge menu events to input rewrite. Tab completes by inserting the\n // item's value; Enter selects, giving the source a chance to execute.\n this.select.on(\"complete\", ({ item }) => this.#complete(item))\n this.select.on(\"accept\", ({ item }) => this.#accept(item))\n this.select.on(\"close\", () => cancel(true))\n }\n\n get #input(): Input | undefined {\n if (this.#opts.input instanceof Input) return this.#opts.input\n return this.#opts.input()\n }\n\n get enabled(): boolean {\n return unwrap(this.#opts.enabled) ?? true\n }\n\n #detect(): Match | undefined {\n if (!this.#input) return undefined\n const value = this.#input.state.value ?? \"\"\n const cursor = this.#input.state.cursor ?? 0\n const before = value.slice(0, cursor)\n for (const [name, src] of Object.entries(this.#sources)) {\n for (const rx of src.triggers) {\n // `exec` with /g wouldn't help — we want the *last* match whose\n // tail reaches the cursor, since the relevant trigger is the one\n // just typed. Scan with a sticky-ish loop and keep the latest hit.\n const re = new RegExp(rx.source, rx.flags.replace(\"g\", \"\"))\n let searchFrom = 0\n let best: { start: number; end: number } | undefined\n while (searchFrom <= before.length) {\n const m = re.exec(before.slice(searchFrom))\n if (m === null) break\n const start = searchFrom + m.index\n const end = start + m[0].length\n best = { end, start }\n searchFrom = end\n }\n if (best === undefined) continue\n // The query is text from trigger-end to cursor. Reject if there's\n // whitespace between them — that means the trigger word is over.\n const query = before.slice(best.end)\n if (/\\s/.test(query)) continue\n return { end: best.end, query, source: name, start: best.start }\n }\n }\n return undefined\n }\n\n #complete(item: Option): void {\n const match = this.#match()\n if (match === undefined) return\n // Completion keeps the trigger (`/`, `@`, ...) and only replaces the query.\n this.#replace(item, defaultAccept(item), match.end)\n }\n\n #accept(item: Option): void {\n const match = this.#match()\n if (match === undefined) return\n const source = this.#sources[match.source]\n // Source-provided accept wins on Enter; default is `item.value + \" \"` when\n // the item looks MenuItem-shaped, otherwise a no-op clear.\n this.#replace(\n item,\n source.accept ? source.accept(item, match.query) : defaultAccept(item),\n match.start\n )\n }\n\n #replace(item: Option, accepted: string | undefined, start: number): void {\n const match = this.#match()\n if (match === undefined || !this.#input) return\n const value = this.#input.state.value ?? \"\"\n const cursor = this.#input.state.cursor ?? 0\n const tail = value.slice(cursor)\n accepted ??= \"\"\n const newValue = value.slice(0, start) + accepted + tail\n\n if (newValue.trim() === \"\") {\n // In case the source fully consumed the input, add the fully completed value\n // to the input history. When not fully consumed, submit will add the completed\n // value to the history, so we don't need to do it here.\n const completed = value.slice(0, match.end) + defaultAccept(item) + tail\n if (completed.trim() !== \"\") this.#input.historyAdd(completed)\n }\n\n this.#input.state.set({\n cursor: start + accepted.length,\n value: newValue,\n })\n void this.emit(\"complete\", { item, source: match.source })\n this.hide()\n }\n\n protected _render(ctx: RenderCtx): string[] | Promise<string[]> {\n // Menu does all the layout. We're just a wrapper node so callers\n // can place the popup in their tree and so the `visible` toggle\n // flows through one handle.\n return this.select.render(ctx)\n }\n}\n\n/** Default `accept` fallback used when a source doesn't provide one.\n * Assumes the item is `MenuItem`-shaped and inserts `value + \" \"` —\n * matches the old `trailingSpace: true` behaviour. Returns `undefined`\n * (clear-only) when the item has no `text`. */\nfunction defaultAccept(item: Option): string {\n return `${item.text} `\n}\n\n/**\n * Factory for `Autocomplete`. See the class doc for the full API.\n */\nexport function autocomplete(opts: AutocompleteOptions): Autocomplete {\n return new Autocomplete(opts)\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { R as Box, X as State, nt as Reactive } from "../queries-DH3bj74K.mjs";
|
|
2
|
+
import { o as AnyStyle } from "../types-B9Eh7k2v.mjs";
|
|
3
|
+
//#region src/widgets/code.d.ts
|
|
4
|
+
interface CodeState {
|
|
5
|
+
/** Source to render. Plain string or reactive accessor — pass a
|
|
6
|
+
* signal for streaming bash output / tool results. */
|
|
7
|
+
code: Reactive<string>;
|
|
8
|
+
/** Optional file path. Drives the default `lang` (extension) and
|
|
9
|
+
* `title` (path) when those aren't set explicitly. */
|
|
10
|
+
path?: Reactive<string>;
|
|
11
|
+
/** Language for syntax highlighting (any shiki-bundled name or
|
|
12
|
+
* alias). Defaults to `extname(path).slice(1)` when `path` is set.
|
|
13
|
+
* Unknown langs fall through to plain rendering. */
|
|
14
|
+
lang?: string;
|
|
15
|
+
/** Title line shown above the block. May contain ANSI. Defaults to
|
|
16
|
+
* `path` when set. */
|
|
17
|
+
title?: Reactive<string>;
|
|
18
|
+
/** Disable syntax highlighting even if `lang` is set. Default: `true`. */
|
|
19
|
+
syntax?: boolean;
|
|
20
|
+
style?: AnyStyle | false;
|
|
21
|
+
numbered?: boolean;
|
|
22
|
+
numberOffset?: Reactive<number | undefined>;
|
|
23
|
+
offset?: Reactive<number | undefined>;
|
|
24
|
+
limit?: Reactive<number | undefined>;
|
|
25
|
+
more?: (more: number, msg: string) => string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Standalone code block: shiki-highlighted body inside a `code`-styled
|
|
29
|
+
* shrink-to-content backdrop, optionally titled.
|
|
30
|
+
*
|
|
31
|
+
* Composition over custom layout — the backdrop is `box({ bg: "code",
|
|
32
|
+
* width: "fit" })`, the body is plain `text(...)`. Ambient theme/style
|
|
33
|
+
* arrive via `useContext(RenderContext)`; the async closure reads
|
|
34
|
+
* `style()?.theme` so it stays correct across theme swaps.
|
|
35
|
+
*
|
|
36
|
+
* Async highlighting flows through `createAsync`: signal reads inside
|
|
37
|
+
* the async closure auto-track, so `props.code` (or any other reactive
|
|
38
|
+
* source) drives a re-fire. The accessor returns `initialValue` (the
|
|
39
|
+
* plain source) until shiki resolves, then swaps to the highlighted
|
|
40
|
+
* version. When called inside a Stream surface's `append`, the surface
|
|
41
|
+
* installs a `SuspenseContext` boundary; Stream awaits it before
|
|
42
|
+
* painting, so rows committed to scrollback always reflect the
|
|
43
|
+
* resolved highlight rather than the initial plain source.
|
|
44
|
+
*/
|
|
45
|
+
declare const code: (props: State<CodeState>) => Box<{}>;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { CodeState, code };
|
|
48
|
+
//# sourceMappingURL=code.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code.d.mts","names":[],"sources":["../../src/widgets/code.ts"],"mappings":";;;UAciB;;;EAGf,MAAM;;;EAGN,OAAO;;;;EAIP;;;EAGA,QAAQ;;EAER;EACA,QAAQ;EACR;EACA,eAAe;EACf,SAAS;EACT,QAAQ;EACR,QAAQ,cAAc;;;;;;;;;;;;;;;;;;;;cAqBX,OAAI,OAAkB,MAAM,eAAN"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { S as unwrap, h as memo, r as createAsync, w as useContext } from "../reactive-f7NpAi69.mjs";
|
|
2
|
+
import { n as box } from "../box-DyaByEiG.mjs";
|
|
3
|
+
import { formatLines } from "../text.mjs";
|
|
4
|
+
import { text } from "./text.mjs";
|
|
5
|
+
import { t as RenderContext } from "../render-DxVLqqB9.mjs";
|
|
6
|
+
import { t as codeToAnsi } from "../shiki-BrjVUkeh.mjs";
|
|
7
|
+
import { widget } from "./widget.mjs";
|
|
8
|
+
import { show } from "./show.mjs";
|
|
9
|
+
import { extname } from "pathe";
|
|
10
|
+
//#region src/widgets/code.ts
|
|
11
|
+
/**
|
|
12
|
+
* Standalone code block: shiki-highlighted body inside a `code`-styled
|
|
13
|
+
* shrink-to-content backdrop, optionally titled.
|
|
14
|
+
*
|
|
15
|
+
* Composition over custom layout — the backdrop is `box({ bg: "code",
|
|
16
|
+
* width: "fit" })`, the body is plain `text(...)`. Ambient theme/style
|
|
17
|
+
* arrive via `useContext(RenderContext)`; the async closure reads
|
|
18
|
+
* `style()?.theme` so it stays correct across theme swaps.
|
|
19
|
+
*
|
|
20
|
+
* Async highlighting flows through `createAsync`: signal reads inside
|
|
21
|
+
* the async closure auto-track, so `props.code` (or any other reactive
|
|
22
|
+
* source) drives a re-fire. The accessor returns `initialValue` (the
|
|
23
|
+
* plain source) until shiki resolves, then swaps to the highlighted
|
|
24
|
+
* version. When called inside a Stream surface's `append`, the surface
|
|
25
|
+
* installs a `SuspenseContext` boundary; Stream awaits it before
|
|
26
|
+
* painting, so rows committed to scrollback always reflect the
|
|
27
|
+
* resolved highlight rather than the initial plain source.
|
|
28
|
+
*/
|
|
29
|
+
const code = widget((props) => {
|
|
30
|
+
const path = unwrap(props.path);
|
|
31
|
+
const lang = props.lang ?? (path !== void 0 ? extname(path).slice(1).toLowerCase() : void 0);
|
|
32
|
+
const syntax = props.syntax ?? true;
|
|
33
|
+
const context = useContext(RenderContext);
|
|
34
|
+
const title = memo(() => unwrap(props.title) ?? path);
|
|
35
|
+
const plain = () => unwrap(props.code).replace(/\n+$/, "");
|
|
36
|
+
const body = createAsync(async () => {
|
|
37
|
+
const source = plain();
|
|
38
|
+
if (!lang || !syntax) return source;
|
|
39
|
+
return await codeToAnsi(source, lang, { theme: context?.style().theme.shiki });
|
|
40
|
+
}, { initialValue: plain() });
|
|
41
|
+
const formatted = memo(() => formatLines(body().split("\n"), {
|
|
42
|
+
limit: unwrap(props.limit),
|
|
43
|
+
more: props.more,
|
|
44
|
+
numberOffset: unwrap(props.numberOffset),
|
|
45
|
+
numbered: props.numbered,
|
|
46
|
+
offset: unwrap(props.offset),
|
|
47
|
+
style: context?.style().gutter
|
|
48
|
+
}).join("\n"));
|
|
49
|
+
return box({
|
|
50
|
+
flexDirection: "column",
|
|
51
|
+
padding: props.style === false ? void 0 : [0, 1],
|
|
52
|
+
style: props.style === false ? void 0 : props.style ?? "code",
|
|
53
|
+
width: "fit"
|
|
54
|
+
}, show({ when: title }, () => text((ctx) => ctx.style.codeTitle(title() ?? ""), { wrap: "none" })), text(formatted, { wrap: "none" }));
|
|
55
|
+
});
|
|
56
|
+
//#endregion
|
|
57
|
+
export { code };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=code.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code.mjs","names":[],"sources":["../../src/widgets/code.ts"],"sourcesContent":["import type { Reactive } from \"../core/reactive.ts\"\nimport type { State } from \"../core/state.ts\"\nimport type { AnyStyle } from \"../style/types.ts\"\n\nimport { extname } from \"pathe\"\nimport { createAsync, memo, unwrap, useContext } from \"../core/reactive.ts\"\nimport { RenderContext } from \"../core/render.ts\"\nimport { formatLines } from \"../layout/text.ts\"\nimport { codeToAnsi } from \"../shiki/shiki.ts\"\nimport { box } from \"./box.ts\"\nimport { show } from \"./show.ts\"\nimport { text } from \"./text.ts\"\nimport { widget } from \"./widget.ts\"\n\nexport interface CodeState {\n /** Source to render. Plain string or reactive accessor — pass a\n * signal for streaming bash output / tool results. */\n code: Reactive<string>\n /** Optional file path. Drives the default `lang` (extension) and\n * `title` (path) when those aren't set explicitly. */\n path?: Reactive<string>\n /** Language for syntax highlighting (any shiki-bundled name or\n * alias). Defaults to `extname(path).slice(1)` when `path` is set.\n * Unknown langs fall through to plain rendering. */\n lang?: string\n /** Title line shown above the block. May contain ANSI. Defaults to\n * `path` when set. */\n title?: Reactive<string>\n /** Disable syntax highlighting even if `lang` is set. Default: `true`. */\n syntax?: boolean\n style?: AnyStyle | false\n numbered?: boolean\n numberOffset?: Reactive<number | undefined>\n offset?: Reactive<number | undefined>\n limit?: Reactive<number | undefined>\n more?: (more: number, msg: string) => string\n}\n\n/**\n * Standalone code block: shiki-highlighted body inside a `code`-styled\n * shrink-to-content backdrop, optionally titled.\n *\n * Composition over custom layout — the backdrop is `box({ bg: \"code\",\n * width: \"fit\" })`, the body is plain `text(...)`. Ambient theme/style\n * arrive via `useContext(RenderContext)`; the async closure reads\n * `style()?.theme` so it stays correct across theme swaps.\n *\n * Async highlighting flows through `createAsync`: signal reads inside\n * the async closure auto-track, so `props.code` (or any other reactive\n * source) drives a re-fire. The accessor returns `initialValue` (the\n * plain source) until shiki resolves, then swaps to the highlighted\n * version. When called inside a Stream surface's `append`, the surface\n * installs a `SuspenseContext` boundary; Stream awaits it before\n * painting, so rows committed to scrollback always reflect the\n * resolved highlight rather than the initial plain source.\n */\nexport const code = widget((props: State<CodeState>) => {\n const path = unwrap(props.path)\n const lang = props.lang ?? (path !== undefined ? extname(path).slice(1).toLowerCase() : undefined)\n const syntax = props.syntax ?? true\n\n // Theme is sourced from a render-time hook since the async closure\n // runs outside the render phase.\n\n const context = useContext(RenderContext)\n\n const title = memo(() => unwrap(props.title) ?? path)\n\n const plain = () => unwrap(props.code).replace(/\\n+$/, \"\")\n\n const body = createAsync(\n async () => {\n const source = plain()\n if (!lang || !syntax) return source\n return await codeToAnsi(source, lang, { theme: context?.style().theme.shiki })\n },\n { initialValue: plain() }\n )\n\n const formatted = memo(() =>\n formatLines(body().split(\"\\n\"), {\n limit: unwrap(props.limit),\n more: props.more,\n numberOffset: unwrap(props.numberOffset),\n numbered: props.numbered,\n offset: unwrap(props.offset),\n style: context?.style().gutter,\n }).join(\"\\n\")\n )\n\n return box(\n {\n flexDirection: \"column\",\n padding: props.style === false ? undefined : [0, 1],\n style: props.style === false ? undefined : (props.style ?? \"code\"),\n width: \"fit\",\n },\n show({ when: title }, () =>\n text((ctx) => ctx.style.codeTitle(title() ?? \"\"), { wrap: \"none\" })\n ),\n text(formatted, { wrap: \"none\" })\n )\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,MAAa,OAAO,QAAQ,UAA4B;CACtD,MAAM,OAAO,OAAO,MAAM,IAAI;CAC9B,MAAM,OAAO,MAAM,SAAS,SAAS,KAAA,IAAY,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,IAAI,KAAA;CACxF,MAAM,SAAS,MAAM,UAAU;CAK/B,MAAM,UAAU,WAAW,aAAa;CAExC,MAAM,QAAQ,WAAW,OAAO,MAAM,KAAK,KAAK,IAAI;CAEpD,MAAM,cAAc,OAAO,MAAM,IAAI,CAAC,CAAC,QAAQ,QAAQ,EAAE;CAEzD,MAAM,OAAO,YACX,YAAY;EACV,MAAM,SAAS,MAAM;EACrB,IAAI,CAAC,QAAQ,CAAC,QAAQ,OAAO;EAC7B,OAAO,MAAM,WAAW,QAAQ,MAAM,EAAE,OAAO,SAAS,MAAM,CAAC,CAAC,MAAM,MAAM,CAAC;CAC/E,GACA,EAAE,cAAc,MAAM,EAAE,CAC1B;CAEA,MAAM,YAAY,WAChB,YAAY,KAAK,CAAC,CAAC,MAAM,IAAI,GAAG;EAC9B,OAAO,OAAO,MAAM,KAAK;EACzB,MAAM,MAAM;EACZ,cAAc,OAAO,MAAM,YAAY;EACvC,UAAU,MAAM;EAChB,QAAQ,OAAO,MAAM,MAAM;EAC3B,OAAO,SAAS,MAAM,CAAC,CAAC;CAC1B,CAAC,CAAC,CAAC,KAAK,IAAI,CACd;CAEA,OAAO,IACL;EACE,eAAe;EACf,SAAS,MAAM,UAAU,QAAQ,KAAA,IAAY,CAAC,GAAG,CAAC;EAClD,OAAO,MAAM,UAAU,QAAQ,KAAA,IAAa,MAAM,SAAS;EAC3D,OAAO;CACT,GACA,KAAK,EAAE,MAAM,MAAM,SACjB,MAAM,QAAQ,IAAI,MAAM,UAAU,MAAM,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CACpE,GACA,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC,CAClC;AACF,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Bt as Actions, It as ActionDef } from "../../queries-DH3bj74K.mjs";
|
|
2
|
+
import { n as PickerItem } from "../../picker-D8OLb6kZ.mjs";
|
|
3
|
+
import { o as CompletionSource } from "../../autocomplete-Dfw-yQDa.mjs";
|
|
4
|
+
//#region src/widgets/completions/actions.d.ts
|
|
5
|
+
/** Completion item produced by `actionsSource`. Carries the full
|
|
6
|
+
* `ActionInfo` plus the action `id` so the source's `accept` can
|
|
7
|
+
* dispatch without a secondary lookup. Items match the `MenuItem`
|
|
8
|
+
* contract loosely via the overlapping `name`/... shape. */
|
|
9
|
+
type ActionCompletionItem = ActionDef & PickerItem & {
|
|
10
|
+
id: string;
|
|
11
|
+
score: number;
|
|
12
|
+
};
|
|
13
|
+
interface ActionsSourceOptions {
|
|
14
|
+
/** Registry to read from. Usually `renderer.actions`. */
|
|
15
|
+
actions: Actions;
|
|
16
|
+
/** Trigger regex. Default: `/^\s*\//` (slash at start of line). */
|
|
17
|
+
trigger?: RegExp;
|
|
18
|
+
/** Keep predicate. Default skips `info.hidden`. Return `false` to
|
|
19
|
+
* drop the entry from the completion list. */
|
|
20
|
+
filter?: (id: string, info: ActionDef) => boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Completion source backed by the `Actions` registry — typical slash
|
|
24
|
+
* command ergonomics. Items are raw `ActionInfo` objects augmented
|
|
25
|
+
* with their `id`; `accept` dispatches the action through the registry
|
|
26
|
+
* and clears the trigger+query range, so the user's typed `/foo` is
|
|
27
|
+
* removed once the action fires (no stale text left in the input).
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* autocomplete({
|
|
31
|
+
* input: "chat-input",
|
|
32
|
+
* sources: {
|
|
33
|
+
* slash: actionsSource({ actions: renderer.actions }),
|
|
34
|
+
* },
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function actionsSource(opts: ActionsSourceOptions): CompletionSource<ActionCompletionItem>;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { ActionCompletionItem, ActionsSourceOptions, actionsSource };
|
|
41
|
+
//# sourceMappingURL=actions.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.d.mts","names":[],"sources":["../../../src/widgets/completions/actions.ts"],"mappings":";;;;;;;;KAQY,uBAAuB,YAAY;EAAe;EAAY;;UAEzD;;EAEf,SAAS;;EAET,UAAU;;;EAGV,UAAU,YAAY,MAAM;;;;;;;;;;;;;;;;;;iBAmBd,cAAc,MAAM,uBAAuB,iBAAiB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region src/widgets/completions/actions.ts
|
|
2
|
+
/**
|
|
3
|
+
* Completion source backed by the `Actions` registry — typical slash
|
|
4
|
+
* command ergonomics. Items are raw `ActionInfo` objects augmented
|
|
5
|
+
* with their `id`; `accept` dispatches the action through the registry
|
|
6
|
+
* and clears the trigger+query range, so the user's typed `/foo` is
|
|
7
|
+
* removed once the action fires (no stale text left in the input).
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* autocomplete({
|
|
11
|
+
* input: "chat-input",
|
|
12
|
+
* sources: {
|
|
13
|
+
* slash: actionsSource({ actions: renderer.actions }),
|
|
14
|
+
* },
|
|
15
|
+
* })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
function actionsSource(opts) {
|
|
19
|
+
const trigger = opts.trigger ?? /^\//;
|
|
20
|
+
const filter = opts.filter ?? ((_id, info) => !info.hidden && !!info.cmd);
|
|
21
|
+
return {
|
|
22
|
+
accept(item) {
|
|
23
|
+
opts.actions.dispatch(item.id, { source: "autocomplete" });
|
|
24
|
+
},
|
|
25
|
+
complete(_query, match) {
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const info of opts.actions.list()) {
|
|
28
|
+
if (!filter(info.id, info)) continue;
|
|
29
|
+
const name = info.cmd ?? info.id;
|
|
30
|
+
const score = match(name);
|
|
31
|
+
if (!score) continue;
|
|
32
|
+
out.push({
|
|
33
|
+
...info,
|
|
34
|
+
id: info.id,
|
|
35
|
+
name,
|
|
36
|
+
score,
|
|
37
|
+
text: name
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
},
|
|
42
|
+
triggers: [trigger]
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { actionsSource };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=actions.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions.mjs","names":[],"sources":["../../../src/widgets/completions/actions.ts"],"sourcesContent":["import type { ActionDef, Actions } from \"../../input/actions.ts\"\nimport type { CompletionSource } from \"../autocomplete.ts\"\nimport type { PickerItem } from \"../picker.ts\"\n\n/** Completion item produced by `actionsSource`. Carries the full\n * `ActionInfo` plus the action `id` so the source's `accept` can\n * dispatch without a secondary lookup. Items match the `MenuItem`\n * contract loosely via the overlapping `name`/... shape. */\nexport type ActionCompletionItem = ActionDef & PickerItem & { id: string; score: number }\n\nexport interface ActionsSourceOptions {\n /** Registry to read from. Usually `renderer.actions`. */\n actions: Actions\n /** Trigger regex. Default: `/^\\s*\\//` (slash at start of line). */\n trigger?: RegExp\n /** Keep predicate. Default skips `info.hidden`. Return `false` to\n * drop the entry from the completion list. */\n filter?: (id: string, info: ActionDef) => boolean\n}\n\n/**\n * Completion source backed by the `Actions` registry — typical slash\n * command ergonomics. Items are raw `ActionInfo` objects augmented\n * with their `id`; `accept` dispatches the action through the registry\n * and clears the trigger+query range, so the user's typed `/foo` is\n * removed once the action fires (no stale text left in the input).\n *\n * ```ts\n * autocomplete({\n * input: \"chat-input\",\n * sources: {\n * slash: actionsSource({ actions: renderer.actions }),\n * },\n * })\n * ```\n */\nexport function actionsSource(opts: ActionsSourceOptions): CompletionSource<ActionCompletionItem> {\n const trigger = opts.trigger ?? /^\\//\n const filter = opts.filter ?? ((_id, info): boolean => !info.hidden && !!info.cmd)\n\n return {\n accept(item): undefined {\n opts.actions.dispatch(item.id, { source: \"autocomplete\" })\n return undefined\n },\n complete(_query, match): ActionCompletionItem[] {\n const out: ActionCompletionItem[] = []\n for (const info of opts.actions.list()) {\n if (!filter(info.id, info)) continue\n const name = info.cmd ?? info.id\n const score = match(name)\n if (!score) continue\n out.push({ ...info, id: info.id, name, score, text: name })\n }\n return out\n },\n triggers: [trigger],\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAoCA,SAAgB,cAAc,MAAoE;CAChG,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,SAAS,KAAK,YAAY,KAAK,SAAkB,CAAC,KAAK,UAAU,CAAC,CAAC,KAAK;CAE9E,OAAO;EACL,OAAO,MAAiB;GACtB,KAAK,QAAQ,SAAS,KAAK,IAAI,EAAE,QAAQ,eAAe,CAAC;EAE3D;EACA,SAAS,QAAQ,OAA+B;GAC9C,MAAM,MAA8B,CAAC;GACrC,KAAK,MAAM,QAAQ,KAAK,QAAQ,KAAK,GAAG;IACtC,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,GAAG;IAC5B,MAAM,OAAO,KAAK,OAAO,KAAK;IAC9B,MAAM,QAAQ,MAAM,IAAI;IACxB,IAAI,CAAC,OAAO;IACZ,IAAI,KAAK;KAAE,GAAG;KAAM,IAAI,KAAK;KAAI;KAAM;KAAO,MAAM;IAAK,CAAC;GAC5D;GACA,OAAO;EACT;EACA,UAAU,CAAC,OAAO;CACpB;AACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { n as PickerItem } from "../../picker-D8OLb6kZ.mjs";
|
|
2
|
+
import { o as CompletionSource } from "../../autocomplete-Dfw-yQDa.mjs";
|
|
3
|
+
import { FindOptions } from "@zaly/shared/find";
|
|
4
|
+
//#region src/widgets/completions/files.d.ts
|
|
5
|
+
interface FilesSourceOptions extends FindOptions {
|
|
6
|
+
/** Base directory relative queries resolve against. Default:
|
|
7
|
+
* `process.cwd()`. */
|
|
8
|
+
cwd?: string;
|
|
9
|
+
/** Trigger regex. Default: `/(?<=^|\s)@/` — `@` preceded by start-of-
|
|
10
|
+
* string or whitespace. Lookbehind keeps `match.start` on the `@`
|
|
11
|
+
* itself so the leading space isn't eaten on insertion. */
|
|
12
|
+
trigger?: RegExp;
|
|
13
|
+
/** Prefix prepended to accepted values so the trigger character stays
|
|
14
|
+
* in the input after a pick (letting users keep typing to drill into
|
|
15
|
+
* subdirectories). Default: `"@"` — matches the default trigger.
|
|
16
|
+
* When customizing `trigger`, set this to the character your trigger
|
|
17
|
+
* represents. */
|
|
18
|
+
prefix?: string;
|
|
19
|
+
refreshInterval?: number;
|
|
20
|
+
}
|
|
21
|
+
type File = PickerItem & {
|
|
22
|
+
file: string;
|
|
23
|
+
};
|
|
24
|
+
/** Completion source for files. Uses `@zaly/shared/find` under the hood, so
|
|
25
|
+
* supports all the same options and respects .gitignore by default. */
|
|
26
|
+
declare function filesSource(opts?: FilesSourceOptions): CompletionSource<File>;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { FilesSourceOptions, filesSource };
|
|
29
|
+
//# sourceMappingURL=files.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.mts","names":[],"sources":["../../../src/widgets/completions/files.ts"],"mappings":";;;;UAQiB,2BAA2B;;;EAG1C;;;;EAIA,UAAU;;;;;;EAMV;EACA;;KAGG,OAAO;EAAe;;;;iBAIX,YAAY,OAAM,qBAA0B,iBAAiB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { a as createIterable, y as signal } from "../../reactive-f7NpAi69.mjs";
|
|
2
|
+
import { normPath } from "@zaly/shared";
|
|
3
|
+
//#region src/widgets/completions/files.ts
|
|
4
|
+
/** Completion source for files. Uses `@zaly/shared/find` under the hood, so
|
|
5
|
+
* supports all the same options and respects .gitignore by default. */
|
|
6
|
+
function filesSource(opts = {}) {
|
|
7
|
+
const cwd = normPath(opts.cwd);
|
|
8
|
+
const trigger = opts.trigger ?? /(?<=^|\s)@/;
|
|
9
|
+
const prefix = opts.prefix ?? "@";
|
|
10
|
+
let results;
|
|
11
|
+
const [gen, setGen] = signal(0);
|
|
12
|
+
let lastRefresh = 0;
|
|
13
|
+
return {
|
|
14
|
+
accept: (item) => {
|
|
15
|
+
const v = item.file;
|
|
16
|
+
return `${prefix}${v}${v.endsWith("/") ? "" : " "}`;
|
|
17
|
+
},
|
|
18
|
+
get complete() {
|
|
19
|
+
const now = performance.now();
|
|
20
|
+
if (!results?.loading() && now - lastRefresh > (opts.refreshInterval ?? 1e4)) {
|
|
21
|
+
setGen((g) => g + 1);
|
|
22
|
+
lastRefresh = now;
|
|
23
|
+
}
|
|
24
|
+
return results ??= createIterable(async function* iterFiles() {
|
|
25
|
+
gen();
|
|
26
|
+
const { find } = await import("@zaly/shared/find");
|
|
27
|
+
for await (const f of find({
|
|
28
|
+
...opts,
|
|
29
|
+
cwd
|
|
30
|
+
})) yield (Array.isArray(f) ? f : [f]).map((file) => ({
|
|
31
|
+
file,
|
|
32
|
+
text: file
|
|
33
|
+
}));
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
triggers: [trigger]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { filesSource };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=files.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.mjs","names":[],"sources":["../../../src/widgets/completions/files.ts"],"sourcesContent":["import type { FindOptions } from \"@zaly/shared/find\"\nimport type { Progressive } from \"../../core/reactive.ts\"\nimport type { CompletionSource } from \"../autocomplete.ts\"\nimport type { PickerItem } from \"../picker.ts\"\n\nimport { normPath } from \"@zaly/shared\"\nimport { createIterable, signal } from \"../../core/reactive.ts\"\n\nexport interface FilesSourceOptions extends FindOptions {\n /** Base directory relative queries resolve against. Default:\n * `process.cwd()`. */\n cwd?: string\n /** Trigger regex. Default: `/(?<=^|\\s)@/` — `@` preceded by start-of-\n * string or whitespace. Lookbehind keeps `match.start` on the `@`\n * itself so the leading space isn't eaten on insertion. */\n trigger?: RegExp\n /** Prefix prepended to accepted values so the trigger character stays\n * in the input after a pick (letting users keep typing to drill into\n * subdirectories). Default: `\"@\"` — matches the default trigger.\n * When customizing `trigger`, set this to the character your trigger\n * represents. */\n prefix?: string\n refreshInterval?: number\n}\n\ntype File = PickerItem & { file: string }\n\n/** Completion source for files. Uses `@zaly/shared/find` under the hood, so\n * supports all the same options and respects .gitignore by default. */\nexport function filesSource(opts: FilesSourceOptions = {}): CompletionSource<File> {\n const cwd = normPath(opts.cwd)\n const trigger = opts.trigger ?? /(?<=^|\\s)@/\n const prefix = opts.prefix ?? \"@\"\n let results: Progressive<readonly File[]> | undefined\n const [gen, setGen] = signal(0)\n let lastRefresh = 0\n return {\n accept: (item) => {\n // Dirs keep the popup open (trigger still matches, user can\n // drill in). Files close it — a trailing space makes `#detect`\n // see whitespace in the query and bail.\n const v = item.file\n return `${prefix}${v}${v.endsWith(\"/\") ? \"\" : \" \"}`\n },\n get complete() {\n const now = performance.now()\n if (!results?.loading() && now - lastRefresh > (opts.refreshInterval ?? 10_000)) {\n setGen((g) => g + 1) // Refresh results\n lastRefresh = now\n }\n return (results ??= createIterable<File>(async function* iterFiles() {\n gen() // Re-run when `gen` changes\n const { find } = await import(\"@zaly/shared/find\")\n for await (const f of find({ ...opts, cwd })) {\n const ff = Array.isArray(f) ? f : [f]\n yield ff.map((file) => ({ file, text: file }))\n }\n }))\n },\n triggers: [trigger],\n }\n}\n"],"mappings":";;;;;AA6BA,SAAgB,YAAY,OAA2B,CAAC,GAA2B;CACjF,MAAM,MAAM,SAAS,KAAK,GAAG;CAC7B,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,SAAS,KAAK,UAAU;CAC9B,IAAI;CACJ,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC;CAC9B,IAAI,cAAc;CAClB,OAAO;EACL,SAAS,SAAS;GAIhB,MAAM,IAAI,KAAK;GACf,OAAO,GAAG,SAAS,IAAI,EAAE,SAAS,GAAG,IAAI,KAAK;EAChD;EACA,IAAI,WAAW;GACb,MAAM,MAAM,YAAY,IAAI;GAC5B,IAAI,CAAC,SAAS,QAAQ,KAAK,MAAM,eAAe,KAAK,mBAAmB,MAAS;IAC/E,QAAQ,MAAM,IAAI,CAAC;IACnB,cAAc;GAChB;GACA,OAAQ,YAAY,eAAqB,gBAAgB,YAAY;IACnE,IAAI;IACJ,MAAM,EAAE,SAAS,MAAM,OAAO;IAC9B,WAAW,MAAM,KAAK,KAAK;KAAE,GAAG;KAAM;IAAI,CAAC,GAEzC,OADW,MAAM,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC,EAAA,CAC3B,KAAK,UAAU;KAAE;KAAM,MAAM;IAAK,EAAE;GAEjD,CAAC;EACH;EACA,UAAU,CAAC,OAAO;CACpB;AACF"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { n as PickerItem } from "../../picker-D8OLb6kZ.mjs";
|
|
2
|
+
import { o as CompletionSource } from "../../autocomplete-Dfw-yQDa.mjs";
|
|
3
|
+
//#region src/widgets/completions/github.d.ts
|
|
4
|
+
/** A GitHub issue or pull request returned by `githubSource`. Shape
|
|
5
|
+
* mirrors the `gh` CLI's `--json number,title,state,author,url`
|
|
6
|
+
* output, with `type` added to distinguish issues from PRs. */
|
|
7
|
+
type GithubItem = PickerItem & {
|
|
8
|
+
number: number;
|
|
9
|
+
title: string;
|
|
10
|
+
/** `"open"` / `"closed"` for issues; `"open"` / `"closed"` / `"merged"`
|
|
11
|
+
* for PRs. Kept as a string so we don't trip if gh evolves. */
|
|
12
|
+
state: string;
|
|
13
|
+
type: "issue" | "pr";
|
|
14
|
+
url: string;
|
|
15
|
+
author?: {
|
|
16
|
+
login?: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
type GithubState = "open" | "closed" | "all";
|
|
20
|
+
type GithubFetcher = (cwd: string, state: GithubState) => Promise<GithubItem[]>;
|
|
21
|
+
interface GithubSourceOptions {
|
|
22
|
+
/** Working directory used to locate the repo. `gh` resolves the repo
|
|
23
|
+
* from the git remote of this dir. Default: `process.cwd()`. */
|
|
24
|
+
cwd?: string;
|
|
25
|
+
/** Trigger regex. Default: `/(?<=^|\s)#/` — `#` preceded by start-of-
|
|
26
|
+
* string or whitespace (same lookbehind trick as `filesSource`). */
|
|
27
|
+
trigger?: RegExp;
|
|
28
|
+
/** Prepended to the accepted value so the trigger character stays in
|
|
29
|
+
* the input. Default: `"#"`. */
|
|
30
|
+
prefix?: string;
|
|
31
|
+
/** Which issues / PRs to include. Default: `"open"`. */
|
|
32
|
+
state?: GithubState;
|
|
33
|
+
/** Per-kind cap passed to `gh`. Default: `50`. */
|
|
34
|
+
limit?: number;
|
|
35
|
+
/** Override the fetch implementation. Mainly useful for tests — the
|
|
36
|
+
* default shells out to `gh`. The fetcher is called at most once per
|
|
37
|
+
* `githubSource` instance; results are cached thereafter. */
|
|
38
|
+
fetcher?: GithubFetcher;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Completion source for GitHub issues and pull requests from the repo
|
|
42
|
+
* at `cwd`. Shells out to the `gh` CLI on first use, caches the result,
|
|
43
|
+
* and fuzzy-filters locally on subsequent calls so typing stays snappy.
|
|
44
|
+
*
|
|
45
|
+
* Accepts insert `#123 ` into the input — ready for a markdown-style
|
|
46
|
+
* reference in a chat message or commit subject.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* autocomplete({
|
|
50
|
+
* input: "chat-input",
|
|
51
|
+
* sources: {
|
|
52
|
+
* gh: githubSource(),
|
|
53
|
+
* },
|
|
54
|
+
* })
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* Silently returns no items when `gh` isn't installed, the user isn't
|
|
58
|
+
* authenticated, or the directory isn't a GitHub repo — so it's safe
|
|
59
|
+
* to wire up unconditionally.
|
|
60
|
+
*/
|
|
61
|
+
declare function githubSource(opts?: GithubSourceOptions): CompletionSource<GithubItem>;
|
|
62
|
+
//#endregion
|
|
63
|
+
export { GithubFetcher, GithubItem, GithubSourceOptions, GithubState, githubSource };
|
|
64
|
+
//# sourceMappingURL=github.d.mts.map
|