@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,33 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Component,
|
|
3
|
-
dispatchMouseEvent,
|
|
4
|
-
type TuiMouseDispatchResult,
|
|
5
|
-
type TuiMouseEvent,
|
|
6
|
-
type TuiMouseEventResult,
|
|
7
|
-
} from "../tui.ts";
|
|
8
|
-
|
|
9
|
-
export type MouseRegionHandler = (event: TuiMouseEvent) => TuiMouseEventResult | undefined;
|
|
10
|
-
|
|
11
|
-
/** Adds mouse handling to an existing component without changing its rendering. */
|
|
12
|
-
export class MouseRegion implements Component {
|
|
13
|
-
private readonly child: Component;
|
|
14
|
-
private readonly onMouse: MouseRegionHandler;
|
|
15
|
-
|
|
16
|
-
constructor(child: Component, onMouse: MouseRegionHandler) {
|
|
17
|
-
this.child = child;
|
|
18
|
-
this.onMouse = onMouse;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
render(width: number): string[] {
|
|
22
|
-
return this.child.render(width);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
handleMouse(event: TuiMouseEvent): TuiMouseDispatchResult | TuiMouseEventResult | undefined {
|
|
26
|
-
const childResult = dispatchMouseEvent(this.child, event);
|
|
27
|
-
return childResult ?? this.onMouse(event);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
invalidate(): void {
|
|
31
|
-
this.child.invalidate();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import { LAYOUT_NODE, type ScrollLayoutNode } from "../layout-node.ts";
|
|
2
|
-
import { type Component, Container } from "../tui.ts";
|
|
3
|
-
|
|
4
|
-
export type ScrollViewScrollbar = "hidden" | "auto" | "always";
|
|
5
|
-
|
|
6
|
-
export interface ScrollViewOptions {
|
|
7
|
-
axis?: "vertical";
|
|
8
|
-
follow?: "none" | "end";
|
|
9
|
-
primary?: boolean;
|
|
10
|
-
overscroll?: "chain" | "contain";
|
|
11
|
-
scrollbar?: ScrollViewScrollbar;
|
|
12
|
-
scrollbarTrackStyle?: (text: string) => string;
|
|
13
|
-
scrollbarThumbStyle?: (text: string) => string;
|
|
14
|
-
scrollbarHideDelayMs?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ScrollViewScrollToOptions {
|
|
18
|
-
/** Keep follow-end disabled even when the target is the current content end. */
|
|
19
|
-
disableFollow?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export class ScrollView extends Container {
|
|
23
|
-
private readonly child: Component;
|
|
24
|
-
readonly followEnd: boolean;
|
|
25
|
-
readonly primary: boolean;
|
|
26
|
-
readonly overscroll: "chain" | "contain";
|
|
27
|
-
readonly scrollbarTrackStyle: (text: string) => string;
|
|
28
|
-
readonly scrollbarThumbStyle: (text: string) => string;
|
|
29
|
-
private currentScrollbar: ScrollViewScrollbar;
|
|
30
|
-
private readonly scrollbarHideDelayMs: number;
|
|
31
|
-
private currentScrollTop = 0;
|
|
32
|
-
private contentHeight = 0;
|
|
33
|
-
private currentViewportHeight = 0;
|
|
34
|
-
private followingEnd: boolean;
|
|
35
|
-
private followSuppressedAtEnd = false;
|
|
36
|
-
private requestRenderCallback: (() => void) | undefined;
|
|
37
|
-
private transientScrollbarVisible = false;
|
|
38
|
-
private scrollbarActive = false;
|
|
39
|
-
private scrollbarHideTimer: NodeJS.Timeout | undefined;
|
|
40
|
-
|
|
41
|
-
constructor(component: Component, options: ScrollViewOptions = {}) {
|
|
42
|
-
super();
|
|
43
|
-
if (options.axis !== undefined && options.axis !== "vertical") {
|
|
44
|
-
throw new Error(`Unsupported ScrollView axis: ${options.axis}`);
|
|
45
|
-
}
|
|
46
|
-
this.child = component;
|
|
47
|
-
this.children.push(component);
|
|
48
|
-
this.followEnd = (options.follow ?? "none") === "end";
|
|
49
|
-
this.followingEnd = this.followEnd;
|
|
50
|
-
this.primary = options.primary ?? false;
|
|
51
|
-
this.overscroll = options.overscroll ?? "chain";
|
|
52
|
-
this.currentScrollbar = options.scrollbar ?? "hidden";
|
|
53
|
-
this.scrollbarTrackStyle = options.scrollbarTrackStyle ?? ((text) => `\x1b[90m${text}\x1b[39m`);
|
|
54
|
-
this.scrollbarThumbStyle = options.scrollbarThumbStyle ?? ((text) => `\x1b[37m${text}\x1b[39m`);
|
|
55
|
-
this.scrollbarHideDelayMs = Math.max(0, Math.floor(options.scrollbarHideDelayMs ?? 1000));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
get scrollTop(): number {
|
|
59
|
-
return this.currentScrollTop;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
get isFollowingEnd(): boolean {
|
|
63
|
-
return this.followingEnd;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
get viewportHeight(): number {
|
|
67
|
-
return this.currentViewportHeight;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
get scrollbar(): ScrollViewScrollbar {
|
|
71
|
-
return this.currentScrollbar;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
get isScrollbarVisible(): boolean {
|
|
75
|
-
if (this.scrollbar === "always") return this.currentViewportHeight > 0;
|
|
76
|
-
return (
|
|
77
|
-
this.scrollbar === "auto" && this.contentHeight > this.currentViewportHeight && this.transientScrollbarVisible
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
get isScrollbarActive(): boolean {
|
|
82
|
-
return this.scrollbarActive;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
setScrollbar(scrollbar: ScrollViewScrollbar): void {
|
|
86
|
-
if (scrollbar === this.currentScrollbar) return;
|
|
87
|
-
this.currentScrollbar = scrollbar;
|
|
88
|
-
if (scrollbar !== "auto") this.hideTransientScrollbar();
|
|
89
|
-
else if (this.scrollbarActive) this.markScrollbarActivity();
|
|
90
|
-
this.requestRenderCallback?.();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
getContentWidth(width: number): number {
|
|
94
|
-
return this.scrollbar === "always" && width > 1 ? width - 1 : width;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private markScrollbarActivity(): void {
|
|
98
|
-
if (this.scrollbar !== "auto" || this.contentHeight <= this.currentViewportHeight) return;
|
|
99
|
-
this.transientScrollbarVisible = true;
|
|
100
|
-
if (this.scrollbarHideTimer) {
|
|
101
|
-
clearTimeout(this.scrollbarHideTimer);
|
|
102
|
-
this.scrollbarHideTimer = undefined;
|
|
103
|
-
}
|
|
104
|
-
if (this.scrollbarActive) return;
|
|
105
|
-
this.scrollbarHideTimer = setTimeout(() => {
|
|
106
|
-
this.scrollbarHideTimer = undefined;
|
|
107
|
-
this.transientScrollbarVisible = false;
|
|
108
|
-
this.requestRenderCallback?.();
|
|
109
|
-
}, this.scrollbarHideDelayMs);
|
|
110
|
-
this.scrollbarHideTimer.unref();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
private hideTransientScrollbar(): void {
|
|
114
|
-
this.transientScrollbarVisible = false;
|
|
115
|
-
if (!this.scrollbarHideTimer) return;
|
|
116
|
-
clearTimeout(this.scrollbarHideTimer);
|
|
117
|
-
this.scrollbarHideTimer = undefined;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
setScrollbarActive(active: boolean): void {
|
|
121
|
-
if (active === this.scrollbarActive) return;
|
|
122
|
-
this.scrollbarActive = active;
|
|
123
|
-
this.markScrollbarActivity();
|
|
124
|
-
this.requestRenderCallback?.();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
scrollTo(scrollTop: number, options: ScrollViewScrollToOptions = {}): void {
|
|
128
|
-
const requested = Number.isFinite(scrollTop) ? Math.trunc(scrollTop) : this.currentScrollTop;
|
|
129
|
-
const maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);
|
|
130
|
-
const next = Math.max(0, Math.min(maxScrollTop, requested));
|
|
131
|
-
const nextFollowSuppressedAtEnd = options.disableFollow === true && next === maxScrollTop;
|
|
132
|
-
const nextFollowingEnd = !nextFollowSuppressedAtEnd && this.followEnd && next === maxScrollTop;
|
|
133
|
-
if (
|
|
134
|
-
next === this.currentScrollTop &&
|
|
135
|
-
nextFollowingEnd === this.followingEnd &&
|
|
136
|
-
nextFollowSuppressedAtEnd === this.followSuppressedAtEnd
|
|
137
|
-
) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const moved = next !== this.currentScrollTop;
|
|
141
|
-
this.currentScrollTop = next;
|
|
142
|
-
this.followingEnd = nextFollowingEnd;
|
|
143
|
-
this.followSuppressedAtEnd = nextFollowSuppressedAtEnd;
|
|
144
|
-
if (moved) this.markScrollbarActivity();
|
|
145
|
-
this.requestRenderCallback?.();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
scrollBy(lines: number): number {
|
|
149
|
-
const requested = Number.isFinite(lines) ? Math.trunc(lines) : 0;
|
|
150
|
-
if (requested === 0) return 0;
|
|
151
|
-
const maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);
|
|
152
|
-
const start = this.followingEnd ? maxScrollTop : this.currentScrollTop;
|
|
153
|
-
const next = Math.max(0, Math.min(maxScrollTop, start + requested));
|
|
154
|
-
const moved = next - start;
|
|
155
|
-
const wasFollowingEnd = this.followingEnd;
|
|
156
|
-
this.currentScrollTop = next;
|
|
157
|
-
this.followingEnd = this.followEnd && next === maxScrollTop;
|
|
158
|
-
this.followSuppressedAtEnd = false;
|
|
159
|
-
if (moved !== 0) this.markScrollbarActivity();
|
|
160
|
-
if (moved !== 0 || this.followingEnd !== wasFollowingEnd) this.requestRenderCallback?.();
|
|
161
|
-
return requested - moved;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
scrollToStart(): void {
|
|
165
|
-
const changed =
|
|
166
|
-
this.currentScrollTop !== 0 ||
|
|
167
|
-
this.followingEnd !== (this.followEnd && this.contentHeight <= this.currentViewportHeight);
|
|
168
|
-
this.currentScrollTop = 0;
|
|
169
|
-
this.followingEnd = this.followEnd && this.contentHeight <= this.currentViewportHeight;
|
|
170
|
-
this.followSuppressedAtEnd = false;
|
|
171
|
-
if (changed) {
|
|
172
|
-
this.markScrollbarActivity();
|
|
173
|
-
this.requestRenderCallback?.();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
scrollToEnd(): void {
|
|
178
|
-
const next = Math.max(0, this.contentHeight - this.currentViewportHeight);
|
|
179
|
-
const changed = this.currentScrollTop !== next || this.followingEnd !== this.followEnd;
|
|
180
|
-
this.currentScrollTop = next;
|
|
181
|
-
this.followingEnd = this.followEnd;
|
|
182
|
-
this.followSuppressedAtEnd = false;
|
|
183
|
-
if (changed) {
|
|
184
|
-
this.markScrollbarActivity();
|
|
185
|
-
this.requestRenderCallback?.();
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
updateLayout(contentHeight: number, viewportHeight: number, requestRender: () => void): void {
|
|
190
|
-
this.contentHeight = Math.max(0, Math.floor(contentHeight));
|
|
191
|
-
this.currentViewportHeight = Math.max(0, Math.floor(viewportHeight));
|
|
192
|
-
this.requestRenderCallback = requestRender;
|
|
193
|
-
const maxScrollTop = Math.max(0, this.contentHeight - this.currentViewportHeight);
|
|
194
|
-
if (this.followingEnd) this.currentScrollTop = maxScrollTop;
|
|
195
|
-
else this.currentScrollTop = Math.max(0, Math.min(this.currentScrollTop, maxScrollTop));
|
|
196
|
-
if (this.currentScrollTop < maxScrollTop) this.followSuppressedAtEnd = false;
|
|
197
|
-
if (this.followEnd && this.currentScrollTop === maxScrollTop && !this.followSuppressedAtEnd) {
|
|
198
|
-
this.followingEnd = true;
|
|
199
|
-
}
|
|
200
|
-
if (this.contentHeight <= this.currentViewportHeight) this.hideTransientScrollbar();
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
override addChild(_component: Component): void {
|
|
204
|
-
throw new Error("ScrollView has exactly one child");
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
override removeChild(_component: Component): void {
|
|
208
|
-
throw new Error("ScrollView child cannot be removed");
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
override clear(): void {
|
|
212
|
-
throw new Error("ScrollView child cannot be cleared");
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
override render(width: number): string[] {
|
|
216
|
-
const contentWidth = this.getContentWidth(width);
|
|
217
|
-
const lines = this.child.render(contentWidth);
|
|
218
|
-
return contentWidth === width ? lines : lines.map((line) => `${line} `);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
[LAYOUT_NODE](): ScrollLayoutNode {
|
|
222
|
-
return { type: "scroll", component: this.child, state: this };
|
|
223
|
-
}
|
|
224
|
-
}
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { getKeybindings } from "../keybindings.ts";
|
|
2
|
-
import type { Component, TuiMouseEvent, TuiMouseEventResult } from "../tui.ts";
|
|
3
|
-
import { truncateToWidth, visibleWidth } from "../utils.ts";
|
|
4
|
-
|
|
5
|
-
const DEFAULT_PRIMARY_COLUMN_WIDTH = 32;
|
|
6
|
-
const PRIMARY_COLUMN_GAP = 2;
|
|
7
|
-
const MIN_DESCRIPTION_WIDTH = 10;
|
|
8
|
-
|
|
9
|
-
const normalizeToSingleLine = (text: string): string => text.replace(/[\r\n]+/g, " ").trim();
|
|
10
|
-
const clamp = (value: number, min: number, max: number): number => Math.max(min, Math.min(value, max));
|
|
11
|
-
|
|
12
|
-
export interface SelectItem {
|
|
13
|
-
value: string;
|
|
14
|
-
label: string;
|
|
15
|
-
description?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface SelectListTheme {
|
|
19
|
-
selectedPrefix: (text: string) => string;
|
|
20
|
-
selectedText: (text: string) => string;
|
|
21
|
-
description: (text: string) => string;
|
|
22
|
-
scrollInfo: (text: string) => string;
|
|
23
|
-
noMatch: (text: string) => string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface SelectListTruncatePrimaryContext {
|
|
27
|
-
text: string;
|
|
28
|
-
maxWidth: number;
|
|
29
|
-
columnWidth: number;
|
|
30
|
-
item: SelectItem;
|
|
31
|
-
isSelected: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface SelectListLayoutOptions {
|
|
35
|
-
minPrimaryColumnWidth?: number;
|
|
36
|
-
maxPrimaryColumnWidth?: number;
|
|
37
|
-
truncatePrimary?: (context: SelectListTruncatePrimaryContext) => string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class SelectList implements Component {
|
|
41
|
-
private items: SelectItem[] = [];
|
|
42
|
-
private filteredItems: SelectItem[] = [];
|
|
43
|
-
private selectedIndex: number = 0;
|
|
44
|
-
private mousePressedIndex: number | undefined;
|
|
45
|
-
private maxVisible: number = 5;
|
|
46
|
-
private theme: SelectListTheme;
|
|
47
|
-
private layout: SelectListLayoutOptions;
|
|
48
|
-
|
|
49
|
-
public onSelect?: (item: SelectItem) => void;
|
|
50
|
-
public onCancel?: () => void;
|
|
51
|
-
public onSelectionChange?: (item: SelectItem) => void;
|
|
52
|
-
|
|
53
|
-
constructor(items: SelectItem[], maxVisible: number, theme: SelectListTheme, layout: SelectListLayoutOptions = {}) {
|
|
54
|
-
this.items = items;
|
|
55
|
-
this.filteredItems = items;
|
|
56
|
-
this.maxVisible = maxVisible;
|
|
57
|
-
this.theme = theme;
|
|
58
|
-
this.layout = layout;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
setFilter(filter: string): void {
|
|
62
|
-
this.filteredItems = this.items.filter((item) => item.value.toLowerCase().startsWith(filter.toLowerCase()));
|
|
63
|
-
// Reset selection when filter changes
|
|
64
|
-
this.selectedIndex = 0;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
setSelectedIndex(index: number): void {
|
|
68
|
-
this.selectedIndex = Math.max(0, Math.min(index, this.filteredItems.length - 1));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
invalidate(): void {
|
|
72
|
-
// No cached state to invalidate currently
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
render(width: number): string[] {
|
|
76
|
-
const lines: string[] = [];
|
|
77
|
-
|
|
78
|
-
// If no items match filter, show message
|
|
79
|
-
if (this.filteredItems.length === 0) {
|
|
80
|
-
lines.push(this.theme.noMatch(" No matching commands"));
|
|
81
|
-
return lines;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const primaryColumnWidth = this.getPrimaryColumnWidth();
|
|
85
|
-
|
|
86
|
-
// Calculate visible range with scrolling
|
|
87
|
-
const { startIndex, endIndex } = this.getVisibleRange();
|
|
88
|
-
|
|
89
|
-
// Render visible items
|
|
90
|
-
for (let i = startIndex; i < endIndex; i++) {
|
|
91
|
-
const item = this.filteredItems[i];
|
|
92
|
-
if (!item) continue;
|
|
93
|
-
|
|
94
|
-
const isSelected = i === this.selectedIndex;
|
|
95
|
-
const descriptionSingleLine = item.description ? normalizeToSingleLine(item.description) : undefined;
|
|
96
|
-
lines.push(this.renderItem(item, isSelected, width, descriptionSingleLine, primaryColumnWidth));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Add scroll indicators if needed
|
|
100
|
-
if (startIndex > 0 || endIndex < this.filteredItems.length) {
|
|
101
|
-
const scrollText = ` (${this.selectedIndex + 1}/${this.filteredItems.length})`;
|
|
102
|
-
// Truncate if too long for terminal
|
|
103
|
-
lines.push(this.theme.scrollInfo(truncateToWidth(scrollText, width - 2, "")));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return lines;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
110
|
-
if (this.filteredItems.length === 0) return undefined;
|
|
111
|
-
if (event.type === "wheel" && event.wheelDelta) {
|
|
112
|
-
const delta = event.wheelDelta < 0 ? -1 : 1;
|
|
113
|
-
const previousIndex = this.selectedIndex;
|
|
114
|
-
this.selectedIndex = Math.max(0, Math.min(this.filteredItems.length - 1, this.selectedIndex + delta));
|
|
115
|
-
if (this.selectedIndex !== previousIndex) this.notifySelectionChange();
|
|
116
|
-
return { handled: true, render: this.selectedIndex !== previousIndex };
|
|
117
|
-
}
|
|
118
|
-
// Hover must not change selection: the visible range is centered on it.
|
|
119
|
-
if (event.button !== "left" || (event.type !== "press" && event.type !== "click")) return undefined;
|
|
120
|
-
const { startIndex, endIndex } = this.getVisibleRange();
|
|
121
|
-
const itemIndex = startIndex + event.y;
|
|
122
|
-
if (itemIndex < startIndex || itemIndex >= endIndex) return undefined;
|
|
123
|
-
|
|
124
|
-
if (event.type === "press") {
|
|
125
|
-
this.mousePressedIndex = itemIndex;
|
|
126
|
-
if (this.selectedIndex !== itemIndex) {
|
|
127
|
-
this.selectedIndex = itemIndex;
|
|
128
|
-
this.notifySelectionChange();
|
|
129
|
-
}
|
|
130
|
-
return { handled: true, focus: true };
|
|
131
|
-
}
|
|
132
|
-
if (event.type === "click") {
|
|
133
|
-
const clickedIndex = this.mousePressedIndex ?? itemIndex;
|
|
134
|
-
this.mousePressedIndex = undefined;
|
|
135
|
-
const changed = this.selectedIndex !== clickedIndex;
|
|
136
|
-
this.selectedIndex = clickedIndex;
|
|
137
|
-
if (changed) this.notifySelectionChange();
|
|
138
|
-
const selectedItem = this.filteredItems[this.selectedIndex];
|
|
139
|
-
if (selectedItem) this.onSelect?.(selectedItem);
|
|
140
|
-
return { handled: true };
|
|
141
|
-
}
|
|
142
|
-
return undefined;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
handleInput(keyData: string): void {
|
|
146
|
-
const kb = getKeybindings();
|
|
147
|
-
// Up arrow - wrap to bottom when at top
|
|
148
|
-
if (kb.matches(keyData, "tui.select.up")) {
|
|
149
|
-
this.selectedIndex = this.selectedIndex === 0 ? this.filteredItems.length - 1 : this.selectedIndex - 1;
|
|
150
|
-
this.notifySelectionChange();
|
|
151
|
-
}
|
|
152
|
-
// Down arrow - wrap to top when at bottom
|
|
153
|
-
else if (kb.matches(keyData, "tui.select.down")) {
|
|
154
|
-
this.selectedIndex = this.selectedIndex === this.filteredItems.length - 1 ? 0 : this.selectedIndex + 1;
|
|
155
|
-
this.notifySelectionChange();
|
|
156
|
-
}
|
|
157
|
-
// Enter
|
|
158
|
-
else if (kb.matches(keyData, "tui.select.confirm")) {
|
|
159
|
-
const selectedItem = this.filteredItems[this.selectedIndex];
|
|
160
|
-
if (selectedItem && this.onSelect) {
|
|
161
|
-
this.onSelect(selectedItem);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
// Escape or Ctrl+C
|
|
165
|
-
else if (kb.matches(keyData, "tui.select.cancel")) {
|
|
166
|
-
if (this.onCancel) {
|
|
167
|
-
this.onCancel();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private getVisibleRange(): { startIndex: number; endIndex: number } {
|
|
173
|
-
const startIndex = Math.max(
|
|
174
|
-
0,
|
|
175
|
-
Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredItems.length - this.maxVisible),
|
|
176
|
-
);
|
|
177
|
-
return {
|
|
178
|
-
startIndex,
|
|
179
|
-
endIndex: Math.min(startIndex + this.maxVisible, this.filteredItems.length),
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
private renderItem(
|
|
184
|
-
item: SelectItem,
|
|
185
|
-
isSelected: boolean,
|
|
186
|
-
width: number,
|
|
187
|
-
descriptionSingleLine: string | undefined,
|
|
188
|
-
primaryColumnWidth: number,
|
|
189
|
-
): string {
|
|
190
|
-
const prefix = isSelected ? "→ " : " ";
|
|
191
|
-
const prefixWidth = visibleWidth(prefix);
|
|
192
|
-
|
|
193
|
-
if (descriptionSingleLine && width > 40) {
|
|
194
|
-
const effectivePrimaryColumnWidth = Math.max(1, Math.min(primaryColumnWidth, width - prefixWidth - 4));
|
|
195
|
-
const maxPrimaryWidth = Math.max(1, effectivePrimaryColumnWidth - PRIMARY_COLUMN_GAP);
|
|
196
|
-
const truncatedValue = this.truncatePrimary(item, isSelected, maxPrimaryWidth, effectivePrimaryColumnWidth);
|
|
197
|
-
const truncatedValueWidth = visibleWidth(truncatedValue);
|
|
198
|
-
const spacing = " ".repeat(Math.max(1, effectivePrimaryColumnWidth - truncatedValueWidth));
|
|
199
|
-
const descriptionStart = prefixWidth + truncatedValueWidth + spacing.length;
|
|
200
|
-
const remainingWidth = width - descriptionStart - 2; // -2 for safety
|
|
201
|
-
|
|
202
|
-
if (remainingWidth > MIN_DESCRIPTION_WIDTH) {
|
|
203
|
-
const truncatedDesc = truncateToWidth(descriptionSingleLine, remainingWidth, "");
|
|
204
|
-
if (isSelected) {
|
|
205
|
-
return this.theme.selectedText(`${prefix}${truncatedValue}${spacing}${truncatedDesc}`);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const descText = this.theme.description(spacing + truncatedDesc);
|
|
209
|
-
return prefix + truncatedValue + descText;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const maxWidth = width - prefixWidth - 2;
|
|
214
|
-
const truncatedValue = this.truncatePrimary(item, isSelected, maxWidth, maxWidth);
|
|
215
|
-
if (isSelected) {
|
|
216
|
-
return this.theme.selectedText(`${prefix}${truncatedValue}`);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return prefix + truncatedValue;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
private getPrimaryColumnWidth(): number {
|
|
223
|
-
const { min, max } = this.getPrimaryColumnBounds();
|
|
224
|
-
const widestPrimary = this.filteredItems.reduce((widest, item) => {
|
|
225
|
-
return Math.max(widest, visibleWidth(this.getDisplayValue(item)) + PRIMARY_COLUMN_GAP);
|
|
226
|
-
}, 0);
|
|
227
|
-
|
|
228
|
-
return clamp(widestPrimary, min, max);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private getPrimaryColumnBounds(): { min: number; max: number } {
|
|
232
|
-
const rawMin =
|
|
233
|
-
this.layout.minPrimaryColumnWidth ?? this.layout.maxPrimaryColumnWidth ?? DEFAULT_PRIMARY_COLUMN_WIDTH;
|
|
234
|
-
const rawMax =
|
|
235
|
-
this.layout.maxPrimaryColumnWidth ?? this.layout.minPrimaryColumnWidth ?? DEFAULT_PRIMARY_COLUMN_WIDTH;
|
|
236
|
-
|
|
237
|
-
return {
|
|
238
|
-
min: Math.max(1, Math.min(rawMin, rawMax)),
|
|
239
|
-
max: Math.max(1, Math.max(rawMin, rawMax)),
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private truncatePrimary(item: SelectItem, isSelected: boolean, maxWidth: number, columnWidth: number): string {
|
|
244
|
-
const displayValue = this.getDisplayValue(item);
|
|
245
|
-
const truncatedValue = this.layout.truncatePrimary
|
|
246
|
-
? this.layout.truncatePrimary({
|
|
247
|
-
text: displayValue,
|
|
248
|
-
maxWidth,
|
|
249
|
-
columnWidth,
|
|
250
|
-
item,
|
|
251
|
-
isSelected,
|
|
252
|
-
})
|
|
253
|
-
: truncateToWidth(displayValue, maxWidth, "");
|
|
254
|
-
|
|
255
|
-
return truncateToWidth(truncatedValue, maxWidth, "");
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private getDisplayValue(item: SelectItem): string {
|
|
259
|
-
return item.label || item.value;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
private notifySelectionChange(): void {
|
|
263
|
-
const selectedItem = this.filteredItems[this.selectedIndex];
|
|
264
|
-
if (selectedItem && this.onSelectionChange) {
|
|
265
|
-
this.onSelectionChange(selectedItem);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
getSelectedItem(): SelectItem | null {
|
|
270
|
-
const item = this.filteredItems[this.selectedIndex];
|
|
271
|
-
return item || null;
|
|
272
|
-
}
|
|
273
|
-
}
|