@visual-z/xeno 0.2.1 → 0.4.0
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/CHANGELOG.md +18 -0
- package/README.md +126 -34
- package/docs/app-server.md +84 -0
- package/docs/install.md +51 -13
- package/docs/providers.md +24 -3
- package/package.json +7 -5
- package/src/apps/app-server.ts +28 -0
- package/src/apps/approve.ts +3 -3
- package/src/apps/cli.ts +25 -10
- package/src/apps/commands.ts +45 -8
- package/src/apps/completions.ts +78 -22
- package/src/apps/control-transport.ts +89 -0
- package/src/apps/image-draft.ts +46 -0
- package/src/apps/model-catalog.ts +2 -1
- package/src/apps/project-trust.ts +2 -42
- package/src/apps/provider-connect.ts +346 -0
- package/src/apps/render.ts +22 -47
- package/src/apps/resources.ts +5 -2
- package/src/apps/shell.ts +333 -1542
- package/src/apps/startup-input.ts +45 -9
- package/src/apps/startup.ts +149 -94
- package/src/apps/ui/app.ts +1553 -0
- package/src/apps/ui/control-model.ts +26 -0
- package/src/apps/ui/extension-host.ts +194 -0
- package/src/apps/ui/image-cache.ts +78 -0
- package/src/apps/ui/palette.ts +88 -0
- package/src/apps/ui/project.ts +127 -0
- package/src/apps/ui/standalone.ts +146 -0
- package/src/apps/ui/status-model.ts +68 -0
- package/src/apps/ui/surface.ts +86 -0
- package/src/apps/ui/theme.ts +86 -0
- package/src/core/acceptance.ts +107 -0
- package/src/core/action-defaults.ts +47 -295
- package/src/core/benchmark.ts +2 -2
- package/src/core/branch-receipt.ts +24 -0
- package/src/core/checkpoints.ts +64 -0
- package/src/core/churn.ts +2 -0
- package/src/core/command-artifacts.ts +94 -0
- package/src/core/command-contract.ts +35 -0
- package/src/core/completion-action.ts +20 -0
- package/src/core/completion-review.ts +37 -21
- package/src/core/context-detail.ts +37 -0
- package/src/core/context-policy.ts +74 -1
- package/src/core/continuation.ts +17 -2
- package/src/core/control-protocol.ts +62 -0
- package/src/core/control-view.ts +42 -0
- package/src/core/derive.ts +29 -3
- package/src/core/draft-images.ts +56 -0
- package/src/core/environment.ts +145 -0
- package/src/core/evaluation-coverage.ts +74 -0
- package/src/core/event-validation.ts +81 -6
- package/src/core/events.ts +99 -3
- package/src/core/evidence-links.ts +7 -0
- package/src/core/evidence.ts +13 -3
- package/src/core/execution.ts +8 -2
- package/src/core/experiment.ts +133 -0
- package/src/core/export.ts +9 -0
- package/src/core/failure-recovery.ts +173 -0
- package/src/core/file-precondition.ts +72 -0
- package/src/core/handoff.ts +65 -4
- package/src/core/harness-diagnostics.ts +206 -0
- package/src/core/history-query.ts +38 -2
- package/src/core/history.ts +38 -1
- package/src/core/integration-health.ts +34 -0
- package/src/core/keymap.ts +8 -20
- package/src/core/model-picker.ts +64 -0
- package/src/core/native-eval.ts +25 -0
- package/src/core/note-guidance.ts +34 -0
- package/src/core/oauth-fields.ts +44 -0
- package/src/core/observation-view.ts +49 -8
- package/src/core/permissions.ts +24 -2
- package/src/core/process-receipt.ts +105 -0
- package/src/core/process-state.ts +37 -0
- package/src/core/program-status.ts +31 -0
- package/src/core/project.ts +70 -19
- package/src/core/prompt-policy.ts +37 -0
- package/src/core/query-eval-contract.ts +14 -0
- package/src/core/read-observation.ts +82 -0
- package/src/core/recovery-corpus.ts +17 -0
- package/src/core/recovery-evaluation.ts +60 -0
- package/src/core/recovery-sources.ts +74 -0
- package/src/core/request-progress.ts +87 -0
- package/src/core/request-state.ts +114 -0
- package/src/core/runtime-target.ts +94 -0
- package/src/core/search-observation.ts +97 -0
- package/src/core/session-tree.ts +147 -0
- package/src/core/settings.ts +33 -10
- package/src/core/skills.ts +44 -0
- package/src/core/sql-query.ts +211 -0
- package/src/core/theme.ts +36 -58
- package/src/core/tool-contract.ts +51 -2
- package/src/core/tool-discovery.ts +87 -0
- package/src/core/tool-failure.ts +60 -0
- package/src/core/tool-input.ts +6 -0
- package/src/core/tool-mode-protocol.ts +25 -0
- package/src/core/transcript.ts +19 -4
- package/src/core/trial-lifecycle.ts +14 -0
- package/src/core/tui-mode.ts +29 -0
- package/src/core/update-check.ts +97 -0
- package/src/core/version.ts +55 -0
- package/src/core/work-state.ts +49 -3
- package/src/core/working-set.ts +91 -0
- package/src/runtime/auth.ts +11 -4
- package/src/runtime/branch-jobs.ts +36 -0
- package/src/runtime/catalog.ts +13 -3
- package/src/runtime/clipboard.ts +15 -4
- package/src/runtime/command-artifacts.ts +64 -0
- package/src/runtime/control-connection.ts +104 -0
- package/src/runtime/environment-tools.ts +60 -0
- package/src/runtime/evidence-notes.ts +4 -2
- package/src/runtime/exec-sessions.ts +206 -61
- package/src/runtime/execution-control.ts +1 -0
- package/src/runtime/extension-ui.ts +22 -30
- package/src/runtime/extensions.ts +95 -16
- package/src/runtime/harness-comparison.ts +27 -7
- package/src/runtime/harness-eval.ts +462 -0
- package/src/runtime/history-cache.ts +57 -0
- package/src/runtime/history-tools.ts +62 -12
- package/src/runtime/integrations.ts +210 -0
- package/src/runtime/loop.ts +1088 -282
- package/src/runtime/model-discovery.ts +208 -0
- package/src/runtime/model.ts +9 -0
- package/src/runtime/native-eval-fixtures.ts +33 -0
- package/src/runtime/native-eval.ts +146 -0
- package/src/runtime/oauth.ts +80 -3
- package/src/runtime/pi-provider.ts +169 -12
- package/src/runtime/program-status.ts +38 -0
- package/src/runtime/provider-connect.ts +109 -0
- package/src/runtime/provider-presets.ts +87 -0
- package/src/runtime/provider-request.ts +13 -2
- package/src/runtime/provider.ts +23 -9
- package/src/runtime/query-eval-verifier.ts +28 -0
- package/src/runtime/recovery-coverage.ts +71 -0
- package/src/runtime/recovery-evidence.ts +76 -0
- package/src/runtime/recovery-fixtures.ts +116 -0
- package/src/runtime/runtime-target.ts +149 -0
- package/src/runtime/session-control.ts +198 -0
- package/src/runtime/tool-broker.ts +137 -0
- package/src/runtime/tool-discovery.ts +57 -0
- package/src/runtime/tool-mode-eval.ts +146 -0
- package/src/runtime/tool-queue.ts +77 -0
- package/src/runtime/tools.ts +628 -183
- package/src/runtime/trial-control.ts +45 -0
- package/src/runtime/update-check.ts +47 -0
- package/src/runtime/window-trial.ts +2 -1
- package/src/runtime/working-notes.ts +33 -3
- package/src/runtime/workspace-lifecycle.ts +97 -0
- package/src/store/acceptance.ts +15 -0
- package/src/store/checkpoint-history.ts +43 -0
- package/src/store/command-artifacts.ts +96 -0
- package/src/store/command.ts +65 -37
- package/src/store/file-checkpoints.ts +104 -0
- package/src/store/file-index.ts +13 -1
- package/src/store/git-checkpoints.ts +67 -0
- package/src/store/observations.ts +61 -20
- package/src/store/process-receipts.ts +34 -0
- package/src/store/query-source.ts +13 -8
- package/src/store/recovery.ts +2 -3
- package/src/store/replay.ts +16 -4
- package/src/store/runtime-target.ts +64 -0
- package/src/store/search.ts +171 -132
- package/src/store/session-archive.ts +4 -0
- package/src/store/source-cancellation.ts +6 -0
- package/src/store/update-cache.ts +36 -0
- package/src/store/workspace-observation.ts +113 -0
- package/src/tui/ansi-spans.ts +126 -0
- package/src/tui/style.ts +19 -16
- package/src/tui/ui/atomic-draft.ts +121 -0
- package/src/tui/ui/canvas.ts +96 -0
- package/src/tui/ui/colors.ts +70 -0
- package/src/tui/ui/composer.ts +200 -0
- package/src/tui/ui/control-panel.ts +23 -0
- package/src/tui/ui/dialog.ts +379 -0
- package/src/tui/ui/draft.ts +23 -0
- package/src/tui/ui/edge.ts +186 -0
- package/src/tui/ui/image-size.ts +40 -0
- package/src/tui/ui/ink.ts +210 -0
- package/src/tui/ui/items.ts +40 -0
- package/src/tui/ui/keys.ts +40 -0
- package/src/tui/ui/layout.ts +74 -0
- package/src/tui/ui/list-model.ts +86 -0
- package/src/tui/ui/list-view.ts +172 -0
- package/src/tui/ui/mascot.ts +67 -0
- package/src/tui/ui/messages.ts +518 -0
- package/src/tui/ui/motion.ts +28 -0
- package/src/tui/ui/paths.ts +10 -0
- package/src/tui/ui/pictures.ts +78 -0
- package/src/tui/ui/screen.ts +421 -0
- package/src/tui/ui/status.ts +279 -0
- package/src/tui/ui/styled.ts +85 -0
- package/src/tui/ui/testing.ts +5 -0
- package/src/tui/ui/tool-card.ts +150 -0
- package/src/apps/extension-ui.ts +0 -330
- package/src/apps/resource-browser.ts +0 -75
- package/src/apps/session-browser.ts +0 -113
- package/src/apps/session-picker.ts +0 -55
- package/src/tui/ansi.ts +0 -67
- package/src/tui/autocomplete.ts +0 -29
- package/src/tui/chat-viewport.ts +0 -47
- package/src/tui/complete.ts +0 -87
- package/src/tui/editor.ts +0 -940
- package/src/tui/host.ts +0 -264
- package/src/tui/input.ts +0 -75
- package/src/tui/keys.ts +0 -378
- package/src/tui/markdown.ts +0 -17
- package/src/tui/render.ts +0 -153
- package/src/tui/select.ts +0 -258
- package/src/tui/settings.ts +0 -44
- package/src/tui/statusline.ts +0 -162
- package/src/tui/system-theme.ts +0 -700
- package/src/tui/terminal.ts +0 -30
- package/src/tui/text.ts +0 -52
- package/src/tui/toolview.ts +0 -386
- package/src/tui/transcript.ts +0 -123
- package/src/tui/tree.ts +0 -471
- package/src/vendor/pi-tui/LICENSE.upstream +0 -21
- package/src/vendor/pi-tui/UPSTREAM.json +0 -50
- package/src/vendor/pi-tui/VENDOR.md +0 -25
- package/src/vendor/pi-tui/alt-screen-search.ts +0 -327
- package/src/vendor/pi-tui/autocomplete.ts +0 -860
- package/src/vendor/pi-tui/colors.ts +0 -367
- package/src/vendor/pi-tui/components/alt-screen-flash.ts +0 -51
- package/src/vendor/pi-tui/components/box.ts +0 -166
- package/src/vendor/pi-tui/components/cancellable-loader.ts +0 -40
- package/src/vendor/pi-tui/components/editor.ts +0 -2472
- package/src/vendor/pi-tui/components/h-stack.ts +0 -44
- package/src/vendor/pi-tui/components/image.ts +0 -127
- package/src/vendor/pi-tui/components/input.ts +0 -494
- package/src/vendor/pi-tui/components/loader.ts +0 -101
- package/src/vendor/pi-tui/components/markdown.ts +0 -1021
- package/src/vendor/pi-tui/components/mouse-region.ts +0 -33
- package/src/vendor/pi-tui/components/scroll-view.ts +0 -224
- package/src/vendor/pi-tui/components/select-list.ts +0 -273
- package/src/vendor/pi-tui/components/settings-list.ts +0 -328
- package/src/vendor/pi-tui/components/spacer.ts +0 -28
- package/src/vendor/pi-tui/components/stack.ts +0 -154
- package/src/vendor/pi-tui/components/text.ts +0 -107
- package/src/vendor/pi-tui/components/truncated-text.ts +0 -65
- package/src/vendor/pi-tui/components/v-stack.ts +0 -33
- package/src/vendor/pi-tui/editor-component.ts +0 -74
- package/src/vendor/pi-tui/fuzzy.ts +0 -138
- package/src/vendor/pi-tui/index.ts +0 -186
- package/src/vendor/pi-tui/keybindings.ts +0 -320
- package/src/vendor/pi-tui/keys.ts +0 -1401
- package/src/vendor/pi-tui/kill-ring.ts +0 -46
- package/src/vendor/pi-tui/latex.ts +0 -1506
- package/src/vendor/pi-tui/layout-node.ts +0 -51
- package/src/vendor/pi-tui/layout.ts +0 -449
- package/src/vendor/pi-tui/native/UPSTREAM.json +0 -23
- package/src/vendor/pi-tui/native/clipboard.h +0 -208
- package/src/vendor/pi-tui/native/darwin/README.md +0 -22
- package/src/vendor/pi-tui/native/darwin/build.sh +0 -69
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-platform.node +0 -0
- package/src/vendor/pi-tui/native/darwin/src/darwin-platform.m +0 -132
- package/src/vendor/pi-tui/native/linux/README.md +0 -23
- package/src/vendor/pi-tui/native/linux/build.sh +0 -65
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/src/vendor/pi-tui/native/linux/src/clipboard-worker.h +0 -70
- package/src/vendor/pi-tui/native/linux/src/linux-platform-x11.c +0 -274
- package/src/vendor/pi-tui/native/napi.h +0 -91
- package/src/vendor/pi-tui/native/win32/README.md +0 -31
- package/src/vendor/pi-tui/native/win32/build.mjs +0 -232
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/src/vendor/pi-tui/native/win32/src/win32-platform.c +0 -241
- package/src/vendor/pi-tui/native-modifiers.ts +0 -13
- package/src/vendor/pi-tui/native-module-path.ts +0 -31
- package/src/vendor/pi-tui/native-platform.ts +0 -65
- package/src/vendor/pi-tui/oklab.ts +0 -233
- package/src/vendor/pi-tui/stdin-buffer.ts +0 -444
- package/src/vendor/pi-tui/terminal-colors.ts +0 -91
- package/src/vendor/pi-tui/terminal-image.ts +0 -731
- package/src/vendor/pi-tui/terminal.ts +0 -554
- package/src/vendor/pi-tui/tui-alt-screen.ts +0 -1750
- package/src/vendor/pi-tui/tui-main-screen.ts +0 -655
- package/src/vendor/pi-tui/tui.ts +0 -1493
- package/src/vendor/pi-tui/undo-stack.ts +0 -28
- package/src/vendor/pi-tui/utils.ts +0 -1385
- package/src/vendor/pi-tui/wheel-scroll.ts +0 -82
- package/src/vendor/pi-tui/word-navigation.ts +0 -117
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
import { linearSrgbToRgb, okhslToRgb, oklabToLinearSrgb, rgbToOkhsl, rgbToOklab } from "./oklab.ts";
|
|
2
|
-
import type { RgbColor } from "./terminal-colors.ts";
|
|
3
|
-
|
|
4
|
-
export interface IndexedColor {
|
|
5
|
-
readonly kind: "indexed";
|
|
6
|
-
readonly index: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface RgbColorValue {
|
|
10
|
-
readonly kind: "rgb";
|
|
11
|
-
readonly r: number;
|
|
12
|
-
readonly g: number;
|
|
13
|
-
readonly b: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface OklchColorValue {
|
|
17
|
-
readonly kind: "oklch";
|
|
18
|
-
readonly l: number;
|
|
19
|
-
readonly c: number;
|
|
20
|
-
readonly h: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** A concrete color. Every color can be converted to sRGB, so color math never fails. */
|
|
24
|
-
export type Color = IndexedColor | RgbColorValue | OklchColorValue;
|
|
25
|
-
export type TerminalColorMode = "256color" | "truecolor";
|
|
26
|
-
export type ColorMixSpace = "oklch" | "srgb";
|
|
27
|
-
|
|
28
|
-
export interface OklchChannels {
|
|
29
|
-
l: number;
|
|
30
|
-
c: number;
|
|
31
|
-
h: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* OKHSL channels: hue in degrees, saturation and lightness 0-1. Saturation is relative to the most the
|
|
36
|
-
* sRGB gamut allows at that hue and lightness, so every value is in gamut.
|
|
37
|
-
*/
|
|
38
|
-
export interface OkhslChannels {
|
|
39
|
-
h: number;
|
|
40
|
-
s: number;
|
|
41
|
-
l: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface TextAttributes {
|
|
45
|
-
bold?: boolean;
|
|
46
|
-
dim?: boolean;
|
|
47
|
-
italic?: boolean;
|
|
48
|
-
underline?: boolean;
|
|
49
|
-
inverse?: boolean;
|
|
50
|
-
strikethrough?: boolean;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface TextStyle extends TextAttributes {
|
|
54
|
-
fg?: Color;
|
|
55
|
-
bg?: Color;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function requireFinite(value: number, name: string): void {
|
|
59
|
-
if (!Number.isFinite(value)) throw new Error(`${name} must be finite`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function indexedColor(index: number): IndexedColor {
|
|
63
|
-
if (!Number.isInteger(index) || index < 0 || index > 255) {
|
|
64
|
-
throw new Error(`ANSI color index must be an integer from 0 to 255: ${index}`);
|
|
65
|
-
}
|
|
66
|
-
return Object.freeze({ kind: "indexed", index });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function rgbColor(r: number, g: number, b: number): RgbColorValue {
|
|
70
|
-
for (const [name, value] of [
|
|
71
|
-
["r", r],
|
|
72
|
-
["g", g],
|
|
73
|
-
["b", b],
|
|
74
|
-
] as const) {
|
|
75
|
-
requireFinite(value, name);
|
|
76
|
-
if (value < 0 || value > 255) throw new Error(`${name} must be between 0 and 255: ${value}`);
|
|
77
|
-
}
|
|
78
|
-
return Object.freeze({ kind: "rgb", r, g, b });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function oklchColor(l: number, c: number, h: number): OklchColorValue {
|
|
82
|
-
requireFinite(l, "l");
|
|
83
|
-
requireFinite(c, "c");
|
|
84
|
-
requireFinite(h, "h");
|
|
85
|
-
if (l < 0 || l > 1) throw new Error(`l must be between 0 and 1: ${l}`);
|
|
86
|
-
if (c < 0) throw new Error(`c must not be negative: ${c}`);
|
|
87
|
-
return Object.freeze({ kind: "oklch", l, c, h: ((h % 360) + 360) % 360 });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const NUMBER_PATTERN = String.raw`[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?`;
|
|
91
|
-
const OKLCH_PATTERN = new RegExp(
|
|
92
|
-
`^oklch\\(\\s*(${NUMBER_PATTERN})(%)?\\s+(${NUMBER_PATTERN})\\s+(${NUMBER_PATTERN})(?:deg)?\\s*\\)$`,
|
|
93
|
-
"i",
|
|
94
|
-
);
|
|
95
|
-
const OKHSL_PATTERN = new RegExp(
|
|
96
|
-
`^okhsl\\(\\s*(${NUMBER_PATTERN})(?:deg)?\\s+(${NUMBER_PATTERN})(%)?\\s+(${NUMBER_PATTERN})(%)?\\s*\\)$`,
|
|
97
|
-
"i",
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* An OKHSL color, converted to sRGB. Saturation is relative to the sRGB gamut at the hue and lightness,
|
|
102
|
-
* so equal saturation looks equally colorful across hues and lightness.
|
|
103
|
-
* @param h Hue in degrees.
|
|
104
|
-
* @param s Saturation, 0-1.
|
|
105
|
-
* @param l Lightness, 0-1.
|
|
106
|
-
*/
|
|
107
|
-
export function okhslColor(h: number, s: number, l: number): RgbColorValue {
|
|
108
|
-
requireFinite(h, "h");
|
|
109
|
-
requireFinite(s, "s");
|
|
110
|
-
requireFinite(l, "l");
|
|
111
|
-
if (s < 0 || s > 1) throw new Error(`s must be between 0 and 1: ${s}`);
|
|
112
|
-
if (l < 0 || l > 1) throw new Error(`l must be between 0 and 1: ${l}`);
|
|
113
|
-
const { r, g, b } = okhslToRgb(h, s, l);
|
|
114
|
-
return rgbColor(r, g, b);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function colorToOkhsl(color: Color): OkhslChannels {
|
|
118
|
-
return rgbToOkhsl(colorToRgb(color));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export function parseColor(value: string | number): Color {
|
|
122
|
-
if (typeof value === "number") return indexedColor(value);
|
|
123
|
-
|
|
124
|
-
const hex = /^#([\da-f]{3}|[\da-f]{6})$/i.exec(value);
|
|
125
|
-
if (hex) {
|
|
126
|
-
const digits = hex[1].length === 3 ? [...hex[1]].map((digit) => digit + digit).join("") : hex[1];
|
|
127
|
-
return rgbColor(
|
|
128
|
-
Number.parseInt(digits.slice(0, 2), 16),
|
|
129
|
-
Number.parseInt(digits.slice(2, 4), 16),
|
|
130
|
-
Number.parseInt(digits.slice(4, 6), 16),
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const oklch = OKLCH_PATTERN.exec(value);
|
|
135
|
-
if (oklch) {
|
|
136
|
-
const lightness = Number.parseFloat(oklch[1]) / (oklch[2] ? 100 : 1);
|
|
137
|
-
return oklchColor(lightness, Number.parseFloat(oklch[3]), Number.parseFloat(oklch[4]));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const okhsl = OKHSL_PATTERN.exec(value);
|
|
141
|
-
if (okhsl) {
|
|
142
|
-
const saturation = Number.parseFloat(okhsl[2]) / (okhsl[3] ? 100 : 1);
|
|
143
|
-
const lightness = Number.parseFloat(okhsl[4]) / (okhsl[5] ? 100 : 1);
|
|
144
|
-
return okhslColor(Number.parseFloat(okhsl[1]), saturation, lightness);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
throw new Error(`Invalid color value: ${value}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const BASIC_COLORS: readonly RgbColor[] = [
|
|
151
|
-
{ r: 0, g: 0, b: 0 },
|
|
152
|
-
{ r: 128, g: 0, b: 0 },
|
|
153
|
-
{ r: 0, g: 128, b: 0 },
|
|
154
|
-
{ r: 128, g: 128, b: 0 },
|
|
155
|
-
{ r: 0, g: 0, b: 128 },
|
|
156
|
-
{ r: 128, g: 0, b: 128 },
|
|
157
|
-
{ r: 0, g: 128, b: 128 },
|
|
158
|
-
{ r: 192, g: 192, b: 192 },
|
|
159
|
-
{ r: 128, g: 128, b: 128 },
|
|
160
|
-
{ r: 255, g: 0, b: 0 },
|
|
161
|
-
{ r: 0, g: 255, b: 0 },
|
|
162
|
-
{ r: 255, g: 255, b: 0 },
|
|
163
|
-
{ r: 0, g: 0, b: 255 },
|
|
164
|
-
{ r: 255, g: 0, b: 255 },
|
|
165
|
-
{ r: 0, g: 255, b: 255 },
|
|
166
|
-
{ r: 255, g: 255, b: 255 },
|
|
167
|
-
];
|
|
168
|
-
const CUBE_VALUES = [0, 95, 135, 175, 215, 255] as const;
|
|
169
|
-
const GRAY_VALUES = Array.from({ length: 24 }, (_, index) => 8 + index * 10);
|
|
170
|
-
|
|
171
|
-
function indexedToRgb(index: number): RgbColor {
|
|
172
|
-
if (index < 16) return { ...BASIC_COLORS[index] };
|
|
173
|
-
if (index < 232) {
|
|
174
|
-
const cubeIndex = index - 16;
|
|
175
|
-
return {
|
|
176
|
-
r: CUBE_VALUES[Math.floor(cubeIndex / 36)],
|
|
177
|
-
g: CUBE_VALUES[Math.floor((cubeIndex % 36) / 6)],
|
|
178
|
-
b: CUBE_VALUES[cubeIndex % 6],
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
const gray = 8 + (index - 232) * 10;
|
|
182
|
-
return { r: gray, g: gray, b: gray };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function isInSrgbGamut(linear: number[]): boolean {
|
|
186
|
-
const epsilon = 1e-7;
|
|
187
|
-
return linear.every((channel) => channel >= -epsilon && channel <= 1 + epsilon);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function oklchToRgb({ l, c, h }: OklchChannels): RgbColor {
|
|
191
|
-
// Gamut mapping keeps the hue fixed, so its direction is computed once and scaled by chroma.
|
|
192
|
-
const radians = (h * Math.PI) / 180;
|
|
193
|
-
const cos = Math.cos(radians);
|
|
194
|
-
const sin = Math.sin(radians);
|
|
195
|
-
const atChroma = (chroma: number) => oklabToLinearSrgb([l, chroma * cos, chroma * sin]);
|
|
196
|
-
|
|
197
|
-
const direct = atChroma(c);
|
|
198
|
-
if (isInSrgbGamut(direct)) return linearSrgbToRgb(direct);
|
|
199
|
-
|
|
200
|
-
// Reduce chroma until the color fits. The achromatic color is always in gamut, so it is the
|
|
201
|
-
// fallback when no bisection step fits, e.g. `oklch(100% 0.3 150)` must map to white.
|
|
202
|
-
let linear = atChroma(0);
|
|
203
|
-
let low = 0;
|
|
204
|
-
let high = c;
|
|
205
|
-
for (let index = 0; index < 20; index++) {
|
|
206
|
-
const chroma = (low + high) / 2;
|
|
207
|
-
const candidate = atChroma(chroma);
|
|
208
|
-
if (isInSrgbGamut(candidate)) {
|
|
209
|
-
low = chroma;
|
|
210
|
-
linear = candidate;
|
|
211
|
-
} else {
|
|
212
|
-
high = chroma;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return linearSrgbToRgb(linear);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export function colorToRgb(color: Color): RgbColor {
|
|
219
|
-
switch (color.kind) {
|
|
220
|
-
case "indexed":
|
|
221
|
-
return indexedToRgb(color.index);
|
|
222
|
-
case "rgb":
|
|
223
|
-
return { r: color.r, g: color.g, b: color.b };
|
|
224
|
-
case "oklch":
|
|
225
|
-
return oklchToRgb(color);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export function colorToOklch(color: Color): OklchChannels {
|
|
230
|
-
if (color.kind === "oklch") return { l: color.l, c: color.c, h: color.h };
|
|
231
|
-
const [l, a, b] = rgbToOklab(colorToRgb(color));
|
|
232
|
-
return { l, c: Math.hypot(a, b), h: ((Math.atan2(b, a) * 180) / Math.PI + 360) % 360 };
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export function colorToHex(color: Color): string {
|
|
236
|
-
const { r, g, b } = colorToRgb(color);
|
|
237
|
-
const channel = (value: number) => Math.round(value).toString(16).padStart(2, "0");
|
|
238
|
-
return `#${channel(r)}${channel(g)}${channel(b)}`;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export function mixColors(first: Color, second: Color, amount: number, space: ColorMixSpace = "oklch"): Color {
|
|
242
|
-
requireFinite(amount, "amount");
|
|
243
|
-
if (amount < 0 || amount > 1) throw new Error(`amount must be between 0 and 1: ${amount}`);
|
|
244
|
-
|
|
245
|
-
if (space === "srgb") {
|
|
246
|
-
const a = colorToRgb(first);
|
|
247
|
-
const b = colorToRgb(second);
|
|
248
|
-
return rgbColor(a.r + (b.r - a.r) * amount, a.g + (b.g - a.g) * amount, a.b + (b.b - a.b) * amount);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const a = colorToOklch(first);
|
|
252
|
-
const b = colorToOklch(second);
|
|
253
|
-
const firstHue = a.c < 1e-7 ? b.h : a.h;
|
|
254
|
-
const secondHue = b.c < 1e-7 ? firstHue : b.h;
|
|
255
|
-
const hueDelta = ((secondHue - firstHue + 540) % 360) - 180;
|
|
256
|
-
return oklchColor(a.l + (b.l - a.l) * amount, a.c + (b.c - a.c) * amount, firstHue + hueDelta * amount);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function findClosest(values: readonly number[], target: number): number {
|
|
260
|
-
let closestIndex = 0;
|
|
261
|
-
let closestDistance = Infinity;
|
|
262
|
-
for (let index = 0; index < values.length; index++) {
|
|
263
|
-
const distance = Math.abs(target - values[index]);
|
|
264
|
-
if (distance < closestDistance) {
|
|
265
|
-
closestIndex = index;
|
|
266
|
-
closestDistance = distance;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return closestIndex;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function colorDistance(first: RgbColor, second: RgbColor): number {
|
|
273
|
-
const dr = first.r - second.r;
|
|
274
|
-
const dg = first.g - second.g;
|
|
275
|
-
const db = first.b - second.b;
|
|
276
|
-
return dr * dr * 0.299 + dg * dg * 0.587 + db * db * 0.114;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function rgbToAnsi256(color: RgbColor): number {
|
|
280
|
-
const rIndex = findClosest(CUBE_VALUES, color.r);
|
|
281
|
-
const gIndex = findClosest(CUBE_VALUES, color.g);
|
|
282
|
-
const bIndex = findClosest(CUBE_VALUES, color.b);
|
|
283
|
-
const cubeColor = { r: CUBE_VALUES[rIndex], g: CUBE_VALUES[gIndex], b: CUBE_VALUES[bIndex] };
|
|
284
|
-
const cubeIndex = 16 + 36 * rIndex + 6 * gIndex + bIndex;
|
|
285
|
-
|
|
286
|
-
const gray = Math.round(0.299 * color.r + 0.587 * color.g + 0.114 * color.b);
|
|
287
|
-
const grayOffset = findClosest(GRAY_VALUES, gray);
|
|
288
|
-
const grayValue = GRAY_VALUES[grayOffset];
|
|
289
|
-
const spread = Math.max(color.r, color.g, color.b) - Math.min(color.r, color.g, color.b);
|
|
290
|
-
if (
|
|
291
|
-
spread < 10 &&
|
|
292
|
-
colorDistance(color, { r: grayValue, g: grayValue, b: grayValue }) < colorDistance(color, cubeColor)
|
|
293
|
-
) {
|
|
294
|
-
return 232 + grayOffset;
|
|
295
|
-
}
|
|
296
|
-
return cubeIndex;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function colorAnsi(color: Color, mode: TerminalColorMode, background: boolean): string {
|
|
300
|
-
if (color.kind === "indexed") return `\x1b[${background ? 48 : 38};5;${color.index}m`;
|
|
301
|
-
|
|
302
|
-
const rgb = colorToRgb(color);
|
|
303
|
-
if (mode === "truecolor") {
|
|
304
|
-
return `\x1b[${background ? 48 : 38};2;${Math.round(rgb.r)};${Math.round(rgb.g)};${Math.round(rgb.b)}m`;
|
|
305
|
-
}
|
|
306
|
-
return `\x1b[${background ? 48 : 38};5;${rgbToAnsi256(rgb)}m`;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export function foregroundAnsi(color: Color, mode: TerminalColorMode): string {
|
|
310
|
-
return colorAnsi(color, mode, false);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export function backgroundAnsi(color: Color, mode: TerminalColorMode): string {
|
|
314
|
-
return colorAnsi(color, mode, true);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export function styleText(text: string, options: TextStyle, mode: TerminalColorMode): string {
|
|
318
|
-
return styleTextWithAnsi(
|
|
319
|
-
text,
|
|
320
|
-
options.fg && foregroundAnsi(options.fg, mode),
|
|
321
|
-
options.bg && backgroundAnsi(options.bg, mode),
|
|
322
|
-
options,
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Like `styleText()`, but with precomputed color escape sequences, e.g. cached theme colors.
|
|
328
|
-
* Colors in `options` are ignored.
|
|
329
|
-
*/
|
|
330
|
-
export function styleTextWithAnsi(
|
|
331
|
-
text: string,
|
|
332
|
-
fgAnsi: string | undefined,
|
|
333
|
-
bgAnsi: string | undefined,
|
|
334
|
-
options: TextAttributes,
|
|
335
|
-
): string {
|
|
336
|
-
// Resets are prepended so they close in reverse order of the opening sequences.
|
|
337
|
-
let prefix = "";
|
|
338
|
-
let suffix = "";
|
|
339
|
-
if (fgAnsi) {
|
|
340
|
-
prefix += fgAnsi;
|
|
341
|
-
suffix = "\x1b[39m";
|
|
342
|
-
}
|
|
343
|
-
if (bgAnsi) {
|
|
344
|
-
prefix += bgAnsi;
|
|
345
|
-
suffix = `\x1b[49m${suffix}`;
|
|
346
|
-
}
|
|
347
|
-
if (options.bold) prefix += "\x1b[1m";
|
|
348
|
-
if (options.dim) prefix += "\x1b[2m";
|
|
349
|
-
if (options.bold || options.dim) suffix = `\x1b[22m${suffix}`;
|
|
350
|
-
if (options.italic) {
|
|
351
|
-
prefix += "\x1b[3m";
|
|
352
|
-
suffix = `\x1b[23m${suffix}`;
|
|
353
|
-
}
|
|
354
|
-
if (options.underline) {
|
|
355
|
-
prefix += "\x1b[4m";
|
|
356
|
-
suffix = `\x1b[24m${suffix}`;
|
|
357
|
-
}
|
|
358
|
-
if (options.inverse) {
|
|
359
|
-
prefix += "\x1b[7m";
|
|
360
|
-
suffix = `\x1b[27m${suffix}`;
|
|
361
|
-
}
|
|
362
|
-
if (options.strikethrough) {
|
|
363
|
-
prefix += "\x1b[9m";
|
|
364
|
-
suffix = `\x1b[29m${suffix}`;
|
|
365
|
-
}
|
|
366
|
-
return `${prefix}${text}${suffix}`;
|
|
367
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { Component } from "../tui.ts";
|
|
2
|
-
import { truncateToWidth } from "../utils.ts";
|
|
3
|
-
|
|
4
|
-
const DEFAULT_DURATION_MS = 1000;
|
|
5
|
-
|
|
6
|
-
interface FlashEntry {
|
|
7
|
-
id: number;
|
|
8
|
-
message: string;
|
|
9
|
-
timer: NodeJS.Timeout;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** Transient messages composited by the alternate-screen renderer. */
|
|
13
|
-
export class AltScreenFlashContainer implements Component {
|
|
14
|
-
private readonly entries: FlashEntry[] = [];
|
|
15
|
-
private nextId = 0;
|
|
16
|
-
private readonly requestRender: () => void;
|
|
17
|
-
|
|
18
|
-
constructor(requestRender: () => void) {
|
|
19
|
-
this.requestRender = requestRender;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
flash(message: string, durationMs = DEFAULT_DURATION_MS): void {
|
|
23
|
-
const id = this.nextId++;
|
|
24
|
-
const timer = setTimeout(
|
|
25
|
-
() => {
|
|
26
|
-
const index = this.entries.findIndex((entry) => entry.id === id);
|
|
27
|
-
if (index === -1) return;
|
|
28
|
-
this.entries.splice(index, 1);
|
|
29
|
-
this.requestRender();
|
|
30
|
-
},
|
|
31
|
-
Math.max(0, durationMs),
|
|
32
|
-
);
|
|
33
|
-
timer.unref();
|
|
34
|
-
this.entries.push({ id, message, timer });
|
|
35
|
-
this.requestRender();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
dispose(): void {
|
|
39
|
-
for (const entry of this.entries) clearTimeout(entry.timer);
|
|
40
|
-
this.entries.length = 0;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
invalidate(): void {}
|
|
44
|
-
|
|
45
|
-
render(width: number): string[] {
|
|
46
|
-
return this.entries.map((entry) => {
|
|
47
|
-
const message = truncateToWidth(` ${entry.message} `, width, "");
|
|
48
|
-
return `\x1b[7m${message}\x1b[27m`;
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { type Component, dispatchMouseEvent, type TuiMouseDispatchResult, type TuiMouseEvent } from "../tui.ts";
|
|
2
|
-
import { visibleWidth } from "../utils.ts";
|
|
3
|
-
|
|
4
|
-
type RenderCache = {
|
|
5
|
-
childLines: string[];
|
|
6
|
-
width: number;
|
|
7
|
-
bgSample: string | undefined;
|
|
8
|
-
lines: string[];
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Box component - a container that applies padding and background to all children
|
|
13
|
-
*/
|
|
14
|
-
export class Box implements Component {
|
|
15
|
-
children: Component[] = [];
|
|
16
|
-
private paddingX: number;
|
|
17
|
-
private paddingY: number;
|
|
18
|
-
private bgFn?: (text: string) => string;
|
|
19
|
-
|
|
20
|
-
// Cache for rendered output
|
|
21
|
-
private cache?: RenderCache;
|
|
22
|
-
private mouseLayout?: { width: number; children: Array<{ component: Component; height: number }> };
|
|
23
|
-
|
|
24
|
-
constructor(paddingX = 1, paddingY = 1, bgFn?: (text: string) => string) {
|
|
25
|
-
this.paddingX = paddingX;
|
|
26
|
-
this.paddingY = paddingY;
|
|
27
|
-
this.bgFn = bgFn;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
addChild(component: Component): void {
|
|
31
|
-
this.children.push(component);
|
|
32
|
-
this.invalidateCache();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
removeChild(component: Component): void {
|
|
36
|
-
const index = this.children.indexOf(component);
|
|
37
|
-
if (index !== -1) {
|
|
38
|
-
this.children.splice(index, 1);
|
|
39
|
-
this.invalidateCache();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
clear(): void {
|
|
44
|
-
this.children = [];
|
|
45
|
-
this.invalidateCache();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
setBgFn(bgFn?: (text: string) => string): void {
|
|
49
|
-
this.bgFn = bgFn;
|
|
50
|
-
// Don't invalidate here - we'll detect bgFn changes by sampling output
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
private invalidateCache(): void {
|
|
54
|
-
this.cache = undefined;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
private matchCache(width: number, childLines: string[], bgSample: string | undefined): boolean {
|
|
58
|
-
const cache = this.cache;
|
|
59
|
-
return (
|
|
60
|
-
!!cache &&
|
|
61
|
-
cache.width === width &&
|
|
62
|
-
cache.bgSample === bgSample &&
|
|
63
|
-
cache.childLines.length === childLines.length &&
|
|
64
|
-
cache.childLines.every((line, i) => line === childLines[i])
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
invalidate(): void {
|
|
69
|
-
this.invalidateCache();
|
|
70
|
-
for (const child of this.children) {
|
|
71
|
-
child.invalidate?.();
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
handleMouse(event: TuiMouseEvent): TuiMouseDispatchResult | undefined {
|
|
76
|
-
const contentWidth = Math.max(1, event.width - this.paddingX * 2);
|
|
77
|
-
const contentY = event.y - this.paddingY;
|
|
78
|
-
const contentX = event.x - this.paddingX;
|
|
79
|
-
if (contentY < 0 || contentX < 0 || contentX >= contentWidth) return undefined;
|
|
80
|
-
|
|
81
|
-
const mouseChildren =
|
|
82
|
-
this.mouseLayout?.width === contentWidth
|
|
83
|
-
? this.mouseLayout.children
|
|
84
|
-
: this.children.map((component) => ({ component, height: component.render(contentWidth).length }));
|
|
85
|
-
let childY = 0;
|
|
86
|
-
for (const { component: child, height: childHeight } of mouseChildren) {
|
|
87
|
-
if (contentY >= childY && contentY < childY + childHeight) {
|
|
88
|
-
return dispatchMouseEvent(child, {
|
|
89
|
-
...event,
|
|
90
|
-
x: contentX,
|
|
91
|
-
y: contentY - childY,
|
|
92
|
-
width: contentWidth,
|
|
93
|
-
height: childHeight,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
childY += childHeight;
|
|
97
|
-
}
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
render(width: number): string[] {
|
|
102
|
-
if (this.children.length === 0) {
|
|
103
|
-
return [];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const contentWidth = Math.max(1, width - this.paddingX * 2);
|
|
107
|
-
const leftPad = " ".repeat(this.paddingX);
|
|
108
|
-
|
|
109
|
-
// Render all children. Keep the child lines unpadded: children usually return the same string
|
|
110
|
-
// objects every frame, so the cache check below is a cheap identity comparison per line.
|
|
111
|
-
// Padding here would create new strings that must be compared character by character.
|
|
112
|
-
const childLines: string[] = [];
|
|
113
|
-
const mouseChildren: Array<{ component: Component; height: number }> = [];
|
|
114
|
-
for (const child of this.children) {
|
|
115
|
-
const lines = child.render(contentWidth);
|
|
116
|
-
mouseChildren.push({ component: child, height: lines.length });
|
|
117
|
-
for (const line of lines) {
|
|
118
|
-
childLines.push(line);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
this.mouseLayout = { width: contentWidth, children: mouseChildren };
|
|
122
|
-
|
|
123
|
-
if (childLines.length === 0) {
|
|
124
|
-
return [];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Check if bgFn output changed by sampling
|
|
128
|
-
const bgSample = this.bgFn ? this.bgFn("test") : undefined;
|
|
129
|
-
|
|
130
|
-
// Check cache validity
|
|
131
|
-
if (this.matchCache(width, childLines, bgSample)) {
|
|
132
|
-
return this.cache!.lines;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Apply background and padding
|
|
136
|
-
const result: string[] = [];
|
|
137
|
-
|
|
138
|
-
// Top padding
|
|
139
|
-
for (let i = 0; i < this.paddingY; i++) {
|
|
140
|
-
result.push(this.applyBg("", width));
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Content
|
|
144
|
-
for (const line of childLines) {
|
|
145
|
-
result.push(this.applyBg(leftPad + line, width));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Bottom padding
|
|
149
|
-
for (let i = 0; i < this.paddingY; i++) {
|
|
150
|
-
result.push(this.applyBg("", width));
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Update cache
|
|
154
|
-
this.cache = { childLines, width, bgSample, lines: result };
|
|
155
|
-
|
|
156
|
-
return result;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private applyBg(line: string, width: number): string {
|
|
160
|
-
const visLen = visibleWidth(line);
|
|
161
|
-
const padNeeded = Math.max(0, width - visLen);
|
|
162
|
-
const padded = line + " ".repeat(padNeeded);
|
|
163
|
-
// Already padded to width, so apply the background directly instead of measuring the line again.
|
|
164
|
-
return this.bgFn ? this.bgFn(padded) : padded;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { getKeybindings } from "../keybindings.ts";
|
|
2
|
-
import { Loader } from "./loader.ts";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Loader that can be cancelled with Escape.
|
|
6
|
-
* Extends Loader with an AbortSignal for cancelling async operations.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* const loader = new CancellableLoader(tui, cyan, dim, "Working...");
|
|
10
|
-
* loader.onAbort = () => done(null);
|
|
11
|
-
* doWork(loader.signal).then(done);
|
|
12
|
-
*/
|
|
13
|
-
export class CancellableLoader extends Loader {
|
|
14
|
-
private abortController = new AbortController();
|
|
15
|
-
|
|
16
|
-
/** Called when user presses Escape */
|
|
17
|
-
onAbort?: () => void;
|
|
18
|
-
|
|
19
|
-
/** AbortSignal that is aborted when user presses Escape */
|
|
20
|
-
get signal(): AbortSignal {
|
|
21
|
-
return this.abortController.signal;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Whether the loader was aborted */
|
|
25
|
-
get aborted(): boolean {
|
|
26
|
-
return this.abortController.signal.aborted;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
handleInput(data: string): void {
|
|
30
|
-
const kb = getKeybindings();
|
|
31
|
-
if (kb.matches(data, "tui.select.cancel")) {
|
|
32
|
-
this.abortController.abort();
|
|
33
|
-
this.onAbort?.();
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
dispose(): void {
|
|
38
|
-
this.stop();
|
|
39
|
-
}
|
|
40
|
-
}
|