@linxiraos/pi-tui 1.1.27 → 1.2.1
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 +50 -461
- package/README.md +7 -5
- package/dist/types/app-keybindings.d.ts +1 -1
- package/dist/types/apps/cleanse-board.d.ts +1 -1
- package/dist/types/apps/debug/log-viewer.d.ts +4 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
- package/dist/types/apps/git/git-tui.d.ts +2 -2
- package/dist/types/apps/git/help.d.ts +3 -1
- package/dist/types/apps/if-bench-board.d.ts +2 -2
- package/dist/types/apps/setup-model-picker.d.ts +1 -1
- package/dist/types/autocomplete.d.ts +5 -2
- package/dist/types/charts/chart-plan.d.ts +674 -0
- package/dist/types/charts/chart-svg.d.ts +59 -0
- package/dist/types/charts/table-data.d.ts +175 -0
- package/dist/types/chat/assistant-message.d.ts +5 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +2 -0
- package/dist/types/chat/compaction-summary-message.d.ts +1 -0
- package/dist/types/chat/execution-shared.d.ts +6 -5
- package/dist/types/chat/fence-figure.d.ts +42 -0
- package/dist/types/chat/figure-markdown.d.ts +42 -0
- package/dist/types/chat/messages.d.ts +1 -1
- package/dist/types/chat/svg-figure.d.ts +33 -0
- package/dist/types/chat/svg-source.d.ts +49 -0
- package/dist/types/chat/table-chart.d.ts +89 -0
- package/dist/types/chat/tool-execution.d.ts +2 -0
- package/dist/types/chat/user-message.d.ts +15 -2
- package/dist/types/chrome/shared.d.ts +9 -0
- package/dist/types/chrome/transcript-container.d.ts +6 -0
- package/dist/types/components/box.d.ts +1 -0
- package/dist/types/components/disclosure.d.ts +1 -0
- package/dist/types/components/editor.d.ts +15 -4
- package/dist/types/components/form.d.ts +1 -0
- package/dist/types/components/image.d.ts +12 -44
- package/dist/types/components/key-value-list.d.ts +1 -0
- package/dist/types/components/layout/row.d.ts +1 -0
- package/dist/types/components/layout/stack.d.ts +1 -0
- package/dist/types/components/markdown.d.ts +36 -3
- package/dist/types/components/scroll-view.d.ts +1 -0
- package/dist/types/components/section.d.ts +1 -0
- package/dist/types/components/table.d.ts +1 -0
- package/dist/types/components/text.d.ts +1 -0
- package/dist/types/components/tree-view.d.ts +7 -0
- package/dist/types/components/truncated-text.d.ts +1 -0
- package/dist/types/fuzzy.d.ts +29 -1
- package/dist/types/key-hint-format.d.ts +1 -1
- package/dist/types/keybinding-matchers.d.ts +3 -0
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/kitty-graphics.d.ts +2 -2
- package/dist/types/loop-watchdog.d.ts +8 -7
- package/dist/types/multiplexers/cmux.d.ts +16 -0
- package/dist/types/multiplexers/herdr.d.ts +26 -0
- package/dist/types/multiplexers/notify.d.ts +13 -0
- package/dist/types/multiplexers/orca.d.ts +12 -0
- package/dist/types/multiplexers/screen.d.ts +9 -0
- package/dist/types/multiplexers/tmux.d.ts +29 -0
- package/dist/types/multiplexers/types.d.ts +100 -0
- package/dist/types/multiplexers/wmux.d.ts +12 -0
- package/dist/types/multiplexers/zellij.d.ts +12 -0
- package/dist/types/native/backend.d.ts +3 -2
- package/dist/types/native/blobs.d.ts +7 -3
- package/dist/types/native/encode.d.ts +3 -4
- package/dist/types/native/overlay.d.ts +5 -0
- package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
- package/dist/types/overlays/agent-hub-types.d.ts +9 -0
- package/dist/types/overlays/agent-hub.d.ts +2 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +11 -2
- package/dist/types/overlays/agents-hub.d.ts +9 -0
- package/dist/types/overlays/annotation-overlay.d.ts +19 -2
- package/dist/types/overlays/annotation-types.d.ts +2 -0
- package/dist/types/overlays/ask-dialog.d.ts +5 -2
- package/dist/types/overlays/history-search.d.ts +10 -0
- package/dist/types/overlays/hook-selector.d.ts +9 -4
- package/dist/types/overlays/jobs-panel.d.ts +1 -7
- package/dist/types/overlays/model-browser.d.ts +54 -7
- package/dist/types/overlays/model-hub.d.ts +23 -0
- package/dist/types/overlays/oauth-selector.d.ts +0 -1
- package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
- package/dist/types/overlays/session-observer-registry.d.ts +2 -0
- package/dist/types/overlays/session-selector.d.ts +6 -2
- package/dist/types/overlays/settings-defs.d.ts +1 -0
- package/dist/types/overlays/usage-dashboard.d.ts +8 -4
- package/dist/types/overlays/usage-display.d.ts +3 -1
- package/dist/types/prompt/composer.d.ts +2 -7
- package/dist/types/prompt/custom-editor.d.ts +11 -8
- package/dist/types/prompt/image-source.d.ts +2 -1
- package/dist/types/prompt/interactive-context-helpers.d.ts +4 -1
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/prompt/welcome.d.ts +19 -42
- package/dist/types/render/composite.d.ts +2 -0
- package/dist/types/render/output-pane.d.ts +1 -0
- package/dist/types/render/render-utils.d.ts +1 -0
- package/dist/types/render/tool-card.d.ts +6 -0
- package/dist/types/render/width-aware-text.d.ts +2 -0
- package/dist/types/setup/scenes/splash.d.ts +1 -1
- package/dist/types/setup/scenes/types.d.ts +1 -0
- package/dist/types/status-line/component.d.ts +3 -0
- package/dist/types/status-line/host.d.ts +4 -1
- package/dist/types/status-line/segments.d.ts +2 -0
- package/dist/types/status-line/types.d.ts +14 -3
- package/dist/types/terminal-capabilities.d.ts +27 -35
- package/dist/types/terminal-multiplexer.d.ts +127 -14
- package/dist/types/terminal.d.ts +22 -6
- package/dist/types/theme/active-symbols.d.ts +1 -1
- package/dist/types/theme/schema.d.ts +11 -2
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +7 -0
- package/dist/types/theme/theme.d.ts +1 -1
- package/dist/types/theme/tui-adapters.d.ts +3 -0
- package/dist/types/tmux.d.ts +11 -4
- package/dist/types/tools/daemon.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +22 -3
- package/dist/types/tools/glob.d.ts +4 -0
- package/dist/types/tools/native-view.d.ts +47 -0
- package/dist/types/tools/output-meta.d.ts +11 -1
- package/dist/types/tools/renderer.d.ts +37 -2
- package/dist/types/tools/streaming-output.d.ts +20 -1
- package/dist/types/tools/task.d.ts +0 -4
- package/dist/types/tools/write.d.ts +9 -2
- package/dist/types/tools/yield.d.ts +22 -0
- package/dist/types/tui.d.ts +31 -0
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/vim.d.ts +19 -5
- package/package.json +13 -9
- package/src/app-keybindings.d.ts +1 -1
- package/src/app-keybindings.ts +1 -1
- package/src/apps/autoresearch-dashboard.ts +7 -3
- package/src/apps/cleanse-board.ts +2 -2
- package/src/apps/cleanse-picker.ts +1 -1
- package/src/apps/debug/log-viewer.ts +188 -72
- package/src/apps/debug/raw-sse-buffer.ts +8 -0
- package/src/apps/debug/raw-sse.ts +39 -5
- package/src/apps/git/diff-pane.ts +26 -5
- package/src/apps/git/git-tui.ts +2 -2
- package/src/apps/git/help.ts +29 -20
- package/src/apps/if-bench-board.ts +2 -2
- package/src/apps/ps-top.ts +2 -2
- package/src/apps/setup-model-picker.ts +1 -1
- package/src/autocomplete.d.ts +5 -2
- package/src/autocomplete.ts +67 -43
- package/src/charts/chart-plan.d.ts +674 -0
- package/src/charts/chart-plan.ts +5081 -0
- package/src/charts/chart-svg.d.ts +59 -0
- package/src/charts/chart-svg.ts +4657 -0
- package/src/charts/table-data.d.ts +175 -0
- package/src/charts/table-data.ts +1478 -0
- package/src/chat/advisor-message.ts +11 -0
- package/src/chat/assistant-message.d.ts +5 -0
- package/src/chat/assistant-message.ts +219 -50
- package/src/chat/chat-transcript-builder.d.ts +2 -0
- package/src/chat/chat-transcript-builder.ts +5 -3
- package/src/chat/compaction-summary-message.d.ts +1 -0
- package/src/chat/compaction-summary-message.ts +8 -0
- package/src/chat/execution-shared.d.ts +6 -5
- package/src/chat/execution-shared.ts +42 -25
- package/src/chat/fence-figure.d.ts +42 -0
- package/src/chat/fence-figure.ts +80 -0
- package/src/chat/figure-markdown.d.ts +42 -0
- package/src/chat/figure-markdown.ts +174 -0
- package/src/chat/messages.d.ts +1 -1
- package/src/chat/messages.ts +1 -1
- package/src/chat/read-tool-group.ts +6 -4
- package/src/chat/skill-message.ts +6 -0
- package/src/chat/svg-figure.d.ts +33 -0
- package/src/chat/svg-figure.ts +399 -0
- package/src/chat/svg-source.d.ts +49 -0
- package/src/chat/svg-source.ts +188 -0
- package/src/chat/table-chart.d.ts +89 -0
- package/src/chat/table-chart.ts +294 -0
- package/src/chat/tool-execution.d.ts +2 -0
- package/src/chat/tool-execution.ts +80 -6
- package/src/chat/user-message.d.ts +15 -2
- package/src/chat/user-message.ts +55 -3
- package/src/chrome/shared.ts +53 -0
- package/src/chrome/transcript-container.d.ts +6 -0
- package/src/chrome/transcript-container.ts +93 -13
- package/src/components/box.d.ts +1 -0
- package/src/components/box.ts +5 -0
- package/src/components/disclosure.d.ts +1 -0
- package/src/components/disclosure.ts +8 -0
- package/src/components/editor.d.ts +15 -4
- package/src/components/editor.ts +294 -46
- package/src/components/form.d.ts +1 -0
- package/src/components/form.ts +8 -0
- package/src/components/image.d.ts +12 -44
- package/src/components/image.ts +47 -151
- package/src/components/key-value-list.d.ts +1 -0
- package/src/components/key-value-list.ts +4 -0
- package/src/components/layout/row.d.ts +1 -0
- package/src/components/layout/row.ts +7 -0
- package/src/components/layout/stack.d.ts +1 -0
- package/src/components/layout/stack.ts +7 -0
- package/src/components/markdown.d.ts +36 -3
- package/src/components/markdown.ts +433 -318
- package/src/components/scroll-view.d.ts +1 -0
- package/src/components/scroll-view.ts +5 -0
- package/src/components/section.d.ts +1 -0
- package/src/components/section.ts +5 -0
- package/src/components/settings-list.ts +2 -0
- package/src/components/table.d.ts +1 -0
- package/src/components/table.ts +4 -0
- package/src/components/text.d.ts +1 -0
- package/src/components/text.ts +7 -0
- package/src/components/tree-view.d.ts +7 -0
- package/src/components/tree-view.ts +38 -8
- package/src/components/truncated-text.d.ts +1 -0
- package/src/components/truncated-text.ts +5 -0
- package/src/desktop-notify.ts +1 -1
- package/src/fuzzy.d.ts +29 -1
- package/src/fuzzy.ts +115 -30
- package/src/key-hint-format.d.ts +1 -1
- package/src/key-hint-format.ts +1 -1
- package/src/keybinding-matchers.d.ts +3 -0
- package/src/keybinding-matchers.ts +6 -2
- package/src/keybindings.d.ts +1 -1
- package/src/keybindings.ts +1 -1
- package/src/kitty-graphics.d.ts +2 -2
- package/src/kitty-graphics.ts +4 -4
- package/src/loop-watchdog.d.ts +8 -7
- package/src/loop-watchdog.ts +18 -13
- package/src/multiplexers/cmux.d.ts +16 -0
- package/src/multiplexers/cmux.ts +29 -0
- package/src/multiplexers/herdr.d.ts +26 -0
- package/src/multiplexers/herdr.ts +64 -0
- package/src/multiplexers/notify.d.ts +13 -0
- package/src/multiplexers/notify.ts +24 -0
- package/src/multiplexers/orca.d.ts +12 -0
- package/src/multiplexers/orca.ts +16 -0
- package/src/multiplexers/screen.d.ts +9 -0
- package/src/multiplexers/screen.ts +15 -0
- package/src/multiplexers/tmux.d.ts +29 -0
- package/src/multiplexers/tmux.ts +40 -0
- package/src/multiplexers/types.d.ts +100 -0
- package/src/multiplexers/types.ts +110 -0
- package/src/multiplexers/wmux.d.ts +12 -0
- package/src/multiplexers/wmux.ts +16 -0
- package/src/multiplexers/zellij.d.ts +12 -0
- package/src/multiplexers/zellij.ts +24 -0
- package/src/native/backend.d.ts +3 -2
- package/src/native/backend.ts +174 -18
- package/src/native/blobs.d.ts +7 -3
- package/src/native/blobs.ts +47 -12
- package/src/native/encode.d.ts +3 -4
- package/src/native/encode.ts +58 -31
- package/src/native/overlay.d.ts +5 -0
- package/src/native/overlay.ts +13 -0
- package/src/native/reconcile.ts +4 -0
- package/src/overlays/agent-hub-projection.d.ts +6 -5
- package/src/overlays/agent-hub-projection.ts +124 -13
- package/src/overlays/agent-hub-types.d.ts +9 -0
- package/src/overlays/agent-hub-types.ts +6 -0
- package/src/overlays/agent-hub.ts +46 -31
- package/src/overlays/agent-transcript-viewer.ts +91 -23
- package/src/overlays/agents-hub.ts +28 -8
- package/src/overlays/annotation-overlay.ts +293 -90
- package/src/overlays/annotation-types.ts +2 -0
- package/src/overlays/ask-dialog.ts +7 -10
- package/src/overlays/btw-history-panel.ts +12 -1
- package/src/overlays/copy-selector.ts +30 -8
- package/src/overlays/extensions/state-manager.ts +52 -12
- package/src/overlays/history-search.ts +100 -7
- package/src/overlays/hook-selector.ts +12 -5
- package/src/overlays/jobs-panel.ts +28 -46
- package/src/overlays/model-browser.ts +341 -110
- package/src/overlays/model-hub.ts +300 -49
- package/src/overlays/move-overlay.ts +28 -14
- package/src/overlays/oauth-selector.ts +54 -70
- package/src/overlays/plan-review-overlay.ts +194 -70
- package/src/overlays/plugin-settings.ts +11 -5
- package/src/overlays/report-panel.ts +2 -2
- package/src/overlays/rewind-selector.ts +86 -17
- package/src/overlays/session-observer-registry.d.ts +2 -0
- package/src/overlays/session-observer-registry.ts +10 -2
- package/src/overlays/session-selector.ts +64 -26
- package/src/overlays/settings-defs.ts +4 -3
- package/src/overlays/settings-selector.ts +33 -8
- package/src/overlays/snapcompact-shape-preview.ts +2 -3
- package/src/overlays/stats-notice.ts +1 -1
- package/src/overlays/tree-selector.ts +41 -20
- package/src/overlays/usage-dashboard.ts +100 -50
- package/src/overlays/usage-display.ts +10 -4
- package/src/prompt/attachment-chips.ts +15 -11
- package/src/prompt/composer-attachments.ts +6 -4
- package/src/prompt/composer-cache.ts +4 -6
- package/src/prompt/composer.ts +6 -37
- package/src/prompt/custom-editor.ts +54 -26
- package/src/prompt/image-source.ts +2 -1
- package/src/prompt/interactive-context-helpers.ts +30 -18
- package/src/prompt/macos-spelling.ts +7 -0
- package/src/prompt/magic-keywords.ts +11 -2
- package/src/prompt/model-mention-autocomplete.ts +14 -14
- package/src/prompt/tips.txt +6 -5
- package/src/prompt/welcome.ts +235 -411
- package/src/prompt/word-completion.ts +38 -5
- package/src/render/composite.d.ts +2 -0
- package/src/render/composite.ts +64 -0
- package/src/render/output-pane.d.ts +1 -0
- package/src/render/output-pane.ts +6 -0
- package/src/render/render-utils.d.ts +1 -0
- package/src/render/render-utils.ts +11 -0
- package/src/render/tool-card.d.ts +6 -0
- package/src/render/tool-card.ts +15 -0
- package/src/render/width-aware-text.d.ts +2 -0
- package/src/render/width-aware-text.ts +5 -0
- package/src/setup/scenes/model.ts +2 -1
- package/src/setup/scenes/splash.ts +116 -53
- package/src/setup/scenes/types.ts +1 -0
- package/src/status-line/component.ts +292 -78
- package/src/status-line/host.ts +4 -1
- package/src/status-line/segments.ts +40 -22
- package/src/status-line/types.ts +15 -3
- package/src/terminal-capabilities.d.ts +27 -35
- package/src/terminal-capabilities.ts +75 -189
- package/src/terminal-multiplexer.d.ts +127 -14
- package/src/terminal-multiplexer.ts +112 -33
- package/src/terminal.d.ts +22 -6
- package/src/terminal.ts +139 -37
- package/src/theme/active-symbols.d.ts +1 -1
- package/src/theme/active-symbols.ts +1 -1
- package/src/theme/schema-validation.ts +1 -0
- package/src/theme/schema.d.ts +11 -2
- package/src/theme/schema.ts +14 -1
- package/src/theme/session-color.ts +1 -7
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +6 -1
- package/src/theme/theme-class.d.ts +7 -0
- package/src/theme/theme-class.ts +26 -1
- package/src/theme/theme-schema.json +5 -1
- package/src/theme/theme.d.ts +1 -1
- package/src/theme/theme.ts +8 -4
- package/src/theme/tui-adapters.d.ts +3 -0
- package/src/theme/tui-adapters.ts +68 -0
- package/src/tmux.d.ts +11 -4
- package/src/tmux.ts +47 -11
- package/src/tools/ast-edit.ts +59 -35
- package/src/tools/ast-grep.ts +35 -16
- package/src/tools/bash.ts +92 -42
- package/src/tools/daemon.d.ts +1 -1
- package/src/tools/daemon.ts +1 -1
- package/src/tools/default-renderer.ts +35 -19
- package/src/tools/edit.ts +142 -120
- package/src/tools/eval-format/index.ts +28 -2
- package/src/tools/eval-format/javascript.ts +7 -5
- package/src/tools/eval.d.ts +22 -3
- package/src/tools/eval.ts +364 -208
- package/src/tools/fetch.ts +5 -6
- package/src/tools/glob.d.ts +4 -0
- package/src/tools/glob.ts +33 -65
- package/src/tools/grep.ts +81 -48
- package/src/tools/index.ts +2 -0
- package/src/tools/lsp.ts +81 -59
- package/src/tools/mcp.ts +111 -68
- package/src/tools/native-view.d.ts +47 -0
- package/src/tools/native-view.ts +102 -1
- package/src/tools/output-meta.d.ts +11 -1
- package/src/tools/output-meta.ts +23 -3
- package/src/tools/read.ts +7 -5
- package/src/tools/renderer.d.ts +37 -2
- package/src/tools/renderer.ts +42 -2
- package/src/tools/streaming-output.d.ts +20 -1
- package/src/tools/streaming-output.ts +149 -50
- package/src/tools/task.d.ts +0 -4
- package/src/tools/task.ts +112 -59
- package/src/tools/todo.ts +119 -30
- package/src/tools/wait.ts +34 -24
- package/src/tools/web-search.ts +1 -1
- package/src/tools/write.d.ts +9 -2
- package/src/tools/write.ts +147 -95
- package/src/tools/yield.d.ts +22 -0
- package/src/tools/yield.ts +225 -0
- package/src/tui.d.ts +31 -0
- package/src/tui.ts +309 -160
- package/src/utils.d.ts +6 -0
- package/src/utils.ts +19 -0
- package/src/vim.d.ts +19 -5
- package/src/vim.ts +333 -18
- package/src/windows-input-mode.ts +8 -0
|
@@ -711,8 +711,11 @@ export class AskDialogComponent implements Component {
|
|
|
711
711
|
}
|
|
712
712
|
|
|
713
713
|
/**
|
|
714
|
-
*
|
|
715
|
-
*
|
|
714
|
+
* In the dock, in the composer's place and framed as the composer is: a
|
|
715
|
+
* `col` with the prompt editor's root role (`zeta.editor`), so the terminal
|
|
716
|
+
* gives it the composer's insets and spacing. Not a modal `overlay` sheet:
|
|
717
|
+
* one anchored at the bottom covered the transcript rows that explain the
|
|
718
|
+
* question and blocked scrolling until the question was answered.
|
|
716
719
|
*/
|
|
717
720
|
describe(cx: DescribeContext): NativeNode {
|
|
718
721
|
const inputGuard = this.options.inputGuard;
|
|
@@ -728,13 +731,7 @@ export class AskDialogComponent implements Component {
|
|
|
728
731
|
if (this.#isSubmitTab()) this.#describeSubmitBody(children);
|
|
729
732
|
else this.#describeQuestionBody(children);
|
|
730
733
|
children.push(this.#describeActions(blocked));
|
|
731
|
-
|
|
732
|
-
"overlay",
|
|
733
|
-
{ role: "zeta.overlay.ask", anchor: "bottom", size: "md", modal: true },
|
|
734
|
-
[col(children, { gap: "md" })],
|
|
735
|
-
"sheet",
|
|
736
|
-
);
|
|
737
|
-
this.#native = col([sheet]);
|
|
734
|
+
this.#native = col(children, { role: "zeta.editor", gap: "md" });
|
|
738
735
|
this.#nativeBlocked = blocked;
|
|
739
736
|
return this.#native;
|
|
740
737
|
}
|
|
@@ -1137,7 +1134,7 @@ export class AskDialogComponent implements Component {
|
|
|
1137
1134
|
const action = question?.multi
|
|
1138
1135
|
? `${formatKeyHint("space")} toggle · ${enter} ${enterAction}`
|
|
1139
1136
|
: `${enter} select · ${formatKeyHint("n")} note`;
|
|
1140
|
-
const tabs = this.#hasSubmitTab() ? ` · ${formatKeyHints(["tab", "left", "right"])}` : "";
|
|
1137
|
+
const tabs = this.#hasSubmitTab() ? ` · ${formatKeyHints(["tab", "left", "right"])} question` : "";
|
|
1141
1138
|
const expand = this.#expandHint();
|
|
1142
1139
|
if (this.#questionCanPage && indicator) {
|
|
1143
1140
|
const pageKeys = editorKeys("tui.select.pageUp", "tui.select.pageDown");
|
|
@@ -37,7 +37,7 @@ import { clampSelection, contentRowWidth, padLinesToHeight, renderScrollableList
|
|
|
37
37
|
import type { TspScrollBy, TspSpan } from "@linxiraos/pi-wire";
|
|
38
38
|
import type { NativeChild, NativeNode, NativeScroll, NativeUiEvent } from "../native/node";
|
|
39
39
|
import { col, md, node, span, text } from "../native/describe";
|
|
40
|
-
import { actionHint, hintsRow, type NativeHint, statusHintsRow } from "../native/overlay";
|
|
40
|
+
import { actionBar, actionButton, actionHint, hintsRow, type NativeHint, statusHintsRow } from "../native/overlay";
|
|
41
41
|
|
|
42
42
|
interface BtwHistoryPanelOptions {
|
|
43
43
|
records: readonly BtwHistoryRecord[];
|
|
@@ -485,6 +485,13 @@ export class BtwHistoryPanel implements Component, Focusable {
|
|
|
485
485
|
children.push(node("col", undefined, lines, "composer"));
|
|
486
486
|
}
|
|
487
487
|
children.push({ ...this.#describeFooter(record, canFollowUp, copied), key: "footer" });
|
|
488
|
+
// Clicking Close always dismisses the sheet; the Esc keycap only shows where Esc does too.
|
|
489
|
+
const running = record !== undefined && getBtwLatestTurn(record).status === "running";
|
|
490
|
+
const escapeCloses = !composer && !(running && !this.#options.escapeHides);
|
|
491
|
+
children.push({
|
|
492
|
+
...actionBar([null, actionButton("Close", "close", escapeCloses ? { keys: "escape" } : undefined)]),
|
|
493
|
+
key: "actions",
|
|
494
|
+
});
|
|
488
495
|
// The sheet is the frame (`nativeOverlay`): a borderless column.
|
|
489
496
|
const described = col(children, { gap: "md" });
|
|
490
497
|
this.#native = {
|
|
@@ -504,6 +511,10 @@ export class BtwHistoryPanel implements Component, Focusable {
|
|
|
504
511
|
}
|
|
505
512
|
|
|
506
513
|
handleNativeEvent(event: NativeUiEvent): void {
|
|
514
|
+
if (event.type === "action" && event.act === "close") {
|
|
515
|
+
this.#options.onClose();
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
507
518
|
if ((event.type !== "select" && event.type !== "activate") || this.#composer) return;
|
|
508
519
|
const index = this.#records.findIndex(record => record.id === event.item);
|
|
509
520
|
if (index === -1) return;
|
|
@@ -327,6 +327,12 @@ interface ControlRegion {
|
|
|
327
327
|
end: number;
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
+
/** A block's preview rows (highlighted, width-cut, plus the "more lines" row) and its full line count. */
|
|
331
|
+
interface BlockPreview {
|
|
332
|
+
rows: string[];
|
|
333
|
+
lineCount: number;
|
|
334
|
+
}
|
|
335
|
+
|
|
330
336
|
export class CopySelectorComponent implements Component {
|
|
331
337
|
#builder: ChatTranscriptBuilder;
|
|
332
338
|
#browser: TranscriptBrowser;
|
|
@@ -348,6 +354,8 @@ export class CopySelectorComponent implements Component {
|
|
|
348
354
|
|
|
349
355
|
/** Last described root and the state it was built from. */
|
|
350
356
|
#native: { memo: string; targets: OutlineTarget[]; blocks: CopyBlock[] | undefined; node: NativeNode } | undefined;
|
|
357
|
+
/** ANSI block previews (syntax highlight + width cut) of the exploded turn. */
|
|
358
|
+
#blockPreviews: { blocks: CopyBlock[]; inner: number; previews: readonly BlockPreview[] } | undefined;
|
|
351
359
|
/** Transcript list items, rebuilt only when the replayed targets change. */
|
|
352
360
|
#nativeItems: { targets: OutlineTarget[]; truncated: boolean; items: NativeNode[] } | undefined;
|
|
353
361
|
/** Timeline picker items of the replayed targets. */
|
|
@@ -414,6 +422,7 @@ export class CopySelectorComponent implements Component {
|
|
|
414
422
|
}
|
|
415
423
|
|
|
416
424
|
invalidate(): void {
|
|
425
|
+
this.#blockPreviews = undefined;
|
|
417
426
|
this.#builder.container.invalidate();
|
|
418
427
|
this.#browser.invalidate();
|
|
419
428
|
}
|
|
@@ -945,18 +954,13 @@ export class CopySelectorComponent implements Component {
|
|
|
945
954
|
*/
|
|
946
955
|
#composeBlocks(blocks: CopyBlock[], columnWidth: number, lineOffset: number): ComposedColumn {
|
|
947
956
|
const inner = Math.max(10, columnWidth - 4);
|
|
957
|
+
const previews = this.#previewBlocks(blocks, inner);
|
|
948
958
|
const lines: string[] = [];
|
|
949
959
|
let selStart = -1;
|
|
950
960
|
let selEnd = -1;
|
|
951
961
|
for (let index = 0; index < blocks.length; index++) {
|
|
952
962
|
const block = blocks[index]!;
|
|
953
|
-
const
|
|
954
|
-
const shown = raw.slice(0, BLOCK_PREVIEW_LINES);
|
|
955
|
-
const styled = block.language ? highlightCode(shown.join("\n"), block.language) : shown;
|
|
956
|
-
const rows = styled.map(row => truncateToWidth(replaceTabs(row), inner));
|
|
957
|
-
if (raw.length > shown.length) {
|
|
958
|
-
rows.push(theme.fg("dim", `… +${raw.length - shown.length} more lines`));
|
|
959
|
-
}
|
|
963
|
+
const { rows, lineCount } = previews[index]!;
|
|
960
964
|
const selected = index === this.#blockSelected;
|
|
961
965
|
const captionColor: ThemeColor = selected ? OUTLINE_COLOR : "dim";
|
|
962
966
|
const controls: Array<{ action: ControlRegion["action"]; text: string }> = [
|
|
@@ -965,7 +969,7 @@ export class CopySelectorComponent implements Component {
|
|
|
965
969
|
if (block.href && this.deps.onOpen) controls.push({ action: "open", text: `${theme.cmd.share} open` });
|
|
966
970
|
const controlsWidth = controls.reduce((sum, control) => sum + visibleWidth(control.text) + 2, 0);
|
|
967
971
|
const summary = truncateToWidth(
|
|
968
|
-
`${index + 1}/${blocks.length}${theme.sep.dot}${block.label}${theme.sep.dot}${
|
|
972
|
+
`${index + 1}/${blocks.length}${theme.sep.dot}${block.label}${theme.sep.dot}${lineCount} line${lineCount === 1 ? "" : "s"}`,
|
|
969
973
|
Math.max(4, inner - controlsWidth),
|
|
970
974
|
);
|
|
971
975
|
// Caption: two-space gutter, summary, then the controls, each preceded by two spaces.
|
|
@@ -998,6 +1002,24 @@ export class CopySelectorComponent implements Component {
|
|
|
998
1002
|
lines.push("");
|
|
999
1003
|
return { lines, selStart, selEnd };
|
|
1000
1004
|
}
|
|
1005
|
+
|
|
1006
|
+
/** Highlighted, width-cut previews of each block; rebuilt when the block set or width changes. */
|
|
1007
|
+
#previewBlocks(blocks: CopyBlock[], inner: number): readonly BlockPreview[] {
|
|
1008
|
+
const cached = this.#blockPreviews;
|
|
1009
|
+
if (cached?.blocks === blocks && cached.inner === inner) return cached.previews;
|
|
1010
|
+
const previews = blocks.map(block => {
|
|
1011
|
+
const raw = block.content.split("\n");
|
|
1012
|
+
const shown = raw.slice(0, BLOCK_PREVIEW_LINES);
|
|
1013
|
+
const styled = block.language ? highlightCode(shown.join("\n"), block.language) : shown;
|
|
1014
|
+
const rows = styled.map(row => truncateToWidth(replaceTabs(row), inner));
|
|
1015
|
+
if (raw.length > shown.length) {
|
|
1016
|
+
rows.push(theme.fg("dim", `… +${raw.length - shown.length} more lines`));
|
|
1017
|
+
}
|
|
1018
|
+
return { rows, lineCount: raw.length };
|
|
1019
|
+
});
|
|
1020
|
+
this.#blockPreviews = { blocks, inner, previews };
|
|
1021
|
+
return previews;
|
|
1022
|
+
}
|
|
1001
1023
|
}
|
|
1002
1024
|
|
|
1003
1025
|
/** Raw multi-line text of a user message (string or text blocks). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Pure dashboard tree, filtering, and selection state. */
|
|
2
|
-
import {
|
|
2
|
+
import { FuzzyQuery, FuzzyText } from "../../fuzzy";
|
|
3
3
|
import {
|
|
4
4
|
type DashboardState,
|
|
5
5
|
type DisabledReason,
|
|
@@ -119,6 +119,50 @@ export function flattenTree(tree: TreeNode[]): FlatTreeItem[] {
|
|
|
119
119
|
return flat;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/** Searchable fields an extension's cached {@link FuzzyText} was built from. */
|
|
123
|
+
interface ExtensionSearchMemo {
|
|
124
|
+
name: string;
|
|
125
|
+
displayName: string;
|
|
126
|
+
description: string | undefined;
|
|
127
|
+
trigger: string | undefined;
|
|
128
|
+
providerName: string;
|
|
129
|
+
kind: string;
|
|
130
|
+
text: FuzzyText;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Prepared search text per extension; rebuilt if any searchable field changes. */
|
|
134
|
+
const extensionSearchCache = new WeakMap<Extension, ExtensionSearchMemo>();
|
|
135
|
+
|
|
136
|
+
function extensionSearchText(ext: Extension): FuzzyText {
|
|
137
|
+
const memo = extensionSearchCache.get(ext);
|
|
138
|
+
if (
|
|
139
|
+
memo !== undefined &&
|
|
140
|
+
memo.name === ext.name &&
|
|
141
|
+
memo.displayName === ext.displayName &&
|
|
142
|
+
memo.description === ext.description &&
|
|
143
|
+
memo.trigger === ext.trigger &&
|
|
144
|
+
memo.providerName === ext.source.providerName &&
|
|
145
|
+
memo.kind === ext.kind
|
|
146
|
+
) {
|
|
147
|
+
return memo.text;
|
|
148
|
+
}
|
|
149
|
+
const text = new FuzzyText(
|
|
150
|
+
[ext.name, ext.displayName, ext.description || "", ext.trigger || "", ext.source.providerName, ext.kind].join(
|
|
151
|
+
" ",
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
extensionSearchCache.set(ext, {
|
|
155
|
+
name: ext.name,
|
|
156
|
+
displayName: ext.displayName,
|
|
157
|
+
description: ext.description,
|
|
158
|
+
trigger: ext.trigger,
|
|
159
|
+
providerName: ext.source.providerName,
|
|
160
|
+
kind: ext.kind,
|
|
161
|
+
text,
|
|
162
|
+
});
|
|
163
|
+
return text;
|
|
164
|
+
}
|
|
165
|
+
|
|
122
166
|
/**
|
|
123
167
|
* Apply fuzzy filter to extensions.
|
|
124
168
|
*/
|
|
@@ -127,22 +171,18 @@ export function applyFilter(extensions: Extension[], query: string): Extension[]
|
|
|
127
171
|
return extensions;
|
|
128
172
|
}
|
|
129
173
|
|
|
130
|
-
const tokens = query
|
|
174
|
+
const tokens = query
|
|
175
|
+
.toLowerCase()
|
|
176
|
+
.split(/\s+/)
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
.map(token => new FuzzyQuery(token));
|
|
131
179
|
if (tokens.length === 0) {
|
|
132
180
|
return extensions;
|
|
133
181
|
}
|
|
134
182
|
|
|
135
183
|
return extensions.filter(ext => {
|
|
136
|
-
const searchable =
|
|
137
|
-
|
|
138
|
-
ext.displayName,
|
|
139
|
-
ext.description || "",
|
|
140
|
-
ext.trigger || "",
|
|
141
|
-
ext.source.providerName,
|
|
142
|
-
ext.kind,
|
|
143
|
-
].join(" ");
|
|
144
|
-
|
|
145
|
-
return tokens.every(token => fuzzyMatch(token, searchable).matches);
|
|
184
|
+
const searchable = extensionSearchText(ext);
|
|
185
|
+
return tokens.every(token => token.match(searchable).matches);
|
|
146
186
|
});
|
|
147
187
|
}
|
|
148
188
|
|
|
@@ -65,6 +65,13 @@ const PICKER_KEY = "picker";
|
|
|
65
65
|
/** Visible result rows; also the jump distance for PageUp/PageDown. */
|
|
66
66
|
const MAX_VISIBLE = 10;
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Quiet period after the last query edit before the synchronous storage
|
|
70
|
+
* search runs. Only applied when the host wires {@link HistorySearchComponent.setOnRequestRender},
|
|
71
|
+
* since the deferred results need a repaint to show up.
|
|
72
|
+
*/
|
|
73
|
+
const SEARCH_DEBOUNCE_MS = 100;
|
|
74
|
+
|
|
68
75
|
/** Split a query the same way `HistorySource` tokenizes it, so highlights align with matches. */
|
|
69
76
|
function queryTokens(query: string): string[] {
|
|
70
77
|
return query
|
|
@@ -106,6 +113,8 @@ class HistoryResultsList implements Component {
|
|
|
106
113
|
#menu: MenuSelection<HistorySearchEntry>;
|
|
107
114
|
#tokens: string[] = [];
|
|
108
115
|
#maxVisible = MAX_VISIBLE;
|
|
116
|
+
/** Highlighted prompt text per entry, valid for one `(tokens, budget)` pair; reset on invalidate (theme). */
|
|
117
|
+
#highlightCache = new WeakMap<HistorySearchEntry, { tokens: string[]; budget: number; text: string }>();
|
|
109
118
|
|
|
110
119
|
constructor(menu: MenuSelection<HistorySearchEntry>) {
|
|
111
120
|
this.#menu = menu;
|
|
@@ -116,7 +125,17 @@ class HistoryResultsList implements Component {
|
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
invalidate(): void {
|
|
119
|
-
|
|
128
|
+
this.#highlightCache = new WeakMap();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Truncated, token-highlighted prompt of `entry` for `budget` cells. */
|
|
132
|
+
#highlightedPrompt(entry: HistorySearchEntry, budget: number): string {
|
|
133
|
+
const cached = this.#highlightCache.get(entry);
|
|
134
|
+
if (cached !== undefined && cached.tokens === this.#tokens && cached.budget === budget) return cached.text;
|
|
135
|
+
const normalized = entry.prompt.replace(/\s+/g, " ").trim();
|
|
136
|
+
const text = highlightTokens(truncateToWidth(normalized, budget), this.#tokens);
|
|
137
|
+
this.#highlightCache.set(entry, { tokens: this.#tokens, budget, text });
|
|
138
|
+
return text;
|
|
120
139
|
}
|
|
121
140
|
|
|
122
141
|
render(width: number): readonly string[] {
|
|
@@ -151,9 +170,7 @@ class HistoryResultsList implements Component {
|
|
|
151
170
|
const showTime = rowWidth >= gutterWidth + 12 + timeWidth;
|
|
152
171
|
|
|
153
172
|
const promptBudget = Math.max(4, rowWidth - gutterWidth - (showTime ? timeWidth + 1 : 0));
|
|
154
|
-
const
|
|
155
|
-
const plain = truncateToWidth(normalized, promptBudget);
|
|
156
|
-
const highlighted = highlightTokens(plain, this.#tokens);
|
|
173
|
+
const highlighted = this.#highlightedPrompt(entry, promptBudget);
|
|
157
174
|
|
|
158
175
|
const cursor = isSelected ? theme.fg("accent", cursorSymbol) : padding(gutterWidth);
|
|
159
176
|
let line = cursor + (isSelected ? theme.bold(highlighted) : highlighted);
|
|
@@ -186,12 +203,22 @@ export class HistorySearchComponent extends OverlayPanel {
|
|
|
186
203
|
#nativeHints: NativeNode | undefined;
|
|
187
204
|
#nativeMemo: HistoryNativeMemo | undefined;
|
|
188
205
|
#pickerItems: { items: readonly HistorySearchEntry[]; rows: readonly TspPickerItem[] } | undefined;
|
|
206
|
+
/** Trimmed query the current results were fetched for; `undefined` before the first fetch. */
|
|
207
|
+
#resultsQuery: string | undefined;
|
|
208
|
+
#searchTimer: NodeJS.Timeout | undefined;
|
|
209
|
+
#onRequestRender?: () => void;
|
|
189
210
|
|
|
190
211
|
constructor(historyStorage: HistorySource, onSelect: (prompt: string) => void, onCancel: () => void) {
|
|
191
212
|
super("History", "zeta.overlay.history");
|
|
192
213
|
this.#historyStorage = historyStorage;
|
|
193
|
-
this.#onSelect =
|
|
194
|
-
|
|
214
|
+
this.#onSelect = prompt => {
|
|
215
|
+
this.#cancelPendingSearch();
|
|
216
|
+
onSelect(prompt);
|
|
217
|
+
};
|
|
218
|
+
this.#onCancel = () => {
|
|
219
|
+
this.#cancelPendingSearch();
|
|
220
|
+
onCancel();
|
|
221
|
+
};
|
|
195
222
|
|
|
196
223
|
this.#menu = new MenuSelection<HistorySearchEntry>([], {
|
|
197
224
|
getKey: entry => `${entry.created_at}:${entry.prompt}`,
|
|
@@ -199,6 +226,7 @@ export class HistorySearchComponent extends OverlayPanel {
|
|
|
199
226
|
});
|
|
200
227
|
this.#searchInput = new Input();
|
|
201
228
|
this.#searchInput.onSubmit = () => {
|
|
229
|
+
this.#flushPendingSearch();
|
|
202
230
|
const selected = this.#menu.selectedItem;
|
|
203
231
|
if (selected) {
|
|
204
232
|
this.#onSelect(selected.prompt);
|
|
@@ -225,38 +253,59 @@ export class HistorySearchComponent extends OverlayPanel {
|
|
|
225
253
|
this.#updateResults();
|
|
226
254
|
}
|
|
227
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Repaint hook for debounced result updates. Once set, query edits search
|
|
258
|
+
* storage after a {@link SEARCH_DEBOUNCE_MS} quiet period instead of on
|
|
259
|
+
* every key; without it every edit searches synchronously.
|
|
260
|
+
*/
|
|
261
|
+
setOnRequestRender(callback: () => void): void {
|
|
262
|
+
this.#onRequestRender = callback;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
override dispose(): void {
|
|
266
|
+
this.#cancelPendingSearch();
|
|
267
|
+
super.dispose();
|
|
268
|
+
}
|
|
269
|
+
|
|
228
270
|
handleInput(keyData: string): void {
|
|
229
271
|
if (matchesSelectUp(keyData)) {
|
|
272
|
+
this.#flushPendingSearch();
|
|
230
273
|
this.#menu.move(-1, false);
|
|
231
274
|
return;
|
|
232
275
|
}
|
|
233
276
|
|
|
234
277
|
if (matchesSelectDown(keyData)) {
|
|
278
|
+
this.#flushPendingSearch();
|
|
235
279
|
this.#menu.move(1, false);
|
|
236
280
|
return;
|
|
237
281
|
}
|
|
238
282
|
|
|
239
283
|
if (matchesSelectPageUp(keyData)) {
|
|
284
|
+
this.#flushPendingSearch();
|
|
240
285
|
this.#menu.move(-MAX_VISIBLE, false);
|
|
241
286
|
return;
|
|
242
287
|
}
|
|
243
288
|
|
|
244
289
|
if (matchesSelectPageDown(keyData)) {
|
|
290
|
+
this.#flushPendingSearch();
|
|
245
291
|
this.#menu.move(MAX_VISIBLE, false);
|
|
246
292
|
return;
|
|
247
293
|
}
|
|
248
294
|
|
|
249
295
|
if (matchesKey(keyData, "home")) {
|
|
296
|
+
this.#flushPendingSearch();
|
|
250
297
|
this.#menu.moveToBoundary("first");
|
|
251
298
|
return;
|
|
252
299
|
}
|
|
253
300
|
|
|
254
301
|
if (matchesKey(keyData, "end")) {
|
|
302
|
+
this.#flushPendingSearch();
|
|
255
303
|
this.#menu.moveToBoundary("last");
|
|
256
304
|
return;
|
|
257
305
|
}
|
|
258
306
|
|
|
259
307
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
308
|
+
this.#flushPendingSearch();
|
|
260
309
|
const selected = this.#menu.selectedItem;
|
|
261
310
|
if (selected) {
|
|
262
311
|
this.#onSelect(selected.prompt);
|
|
@@ -270,7 +319,7 @@ export class HistorySearchComponent extends OverlayPanel {
|
|
|
270
319
|
}
|
|
271
320
|
|
|
272
321
|
this.#searchInput.handleInput(keyData);
|
|
273
|
-
this.#
|
|
322
|
+
this.#scheduleResults();
|
|
274
323
|
}
|
|
275
324
|
|
|
276
325
|
/**
|
|
@@ -372,6 +421,9 @@ export class HistorySearchComponent extends OverlayPanel {
|
|
|
372
421
|
* Picker: a row click highlights, a second click or `Insert` inserts it
|
|
373
422
|
* (Enter), `Close` cancels (Esc), `Clear search` empties the query.
|
|
374
423
|
* List: picking a result does what highlighting it and pressing Enter does.
|
|
424
|
+
* A row picked while a debounced search is pending belongs to the previous
|
|
425
|
+
* query: results are refreshed first and the pick only lands if that entry
|
|
426
|
+
* is still among them.
|
|
375
427
|
*/
|
|
376
428
|
handleNativeEvent(event: NativeUiEvent): void {
|
|
377
429
|
const ev = pickerEvent(event, PICKER_KEY);
|
|
@@ -385,6 +437,7 @@ export class HistorySearchComponent extends OverlayPanel {
|
|
|
385
437
|
}
|
|
386
438
|
return;
|
|
387
439
|
}
|
|
440
|
+
this.#flushPendingSearch();
|
|
388
441
|
const index = this.#menu.visibleItems.findIndex(entry => nativeEntryKey(entry) === ev.item);
|
|
389
442
|
if (index < 0) return;
|
|
390
443
|
this.#menu.setSelectedIndex(index);
|
|
@@ -392,6 +445,7 @@ export class HistorySearchComponent extends OverlayPanel {
|
|
|
392
445
|
return;
|
|
393
446
|
}
|
|
394
447
|
if ((event.type !== "select" && event.type !== "activate") || event.key !== "list") return;
|
|
448
|
+
this.#flushPendingSearch();
|
|
395
449
|
const index = this.#menu.visibleItems.findIndex(entry => nativeEntryKey(entry) === event.item);
|
|
396
450
|
const target = this.#menu.visibleItems[index];
|
|
397
451
|
if (!target) return;
|
|
@@ -399,8 +453,47 @@ export class HistorySearchComponent extends OverlayPanel {
|
|
|
399
453
|
this.#onSelect(target.prompt);
|
|
400
454
|
}
|
|
401
455
|
|
|
456
|
+
/**
|
|
457
|
+
* Refresh results after a query edit: no-op when the trimmed query is
|
|
458
|
+
* unchanged (cursor moves, trailing spaces), immediate for the empty query
|
|
459
|
+
* or when no repaint hook is wired, debounced otherwise.
|
|
460
|
+
*/
|
|
461
|
+
#scheduleResults(): void {
|
|
462
|
+
const query = this.#searchInput.getValue().trim();
|
|
463
|
+
if (query === this.#resultsQuery) {
|
|
464
|
+
this.#cancelPendingSearch();
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
const requestRender = this.#onRequestRender;
|
|
468
|
+
if (!query || !requestRender) {
|
|
469
|
+
this.#updateResults();
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
this.#cancelPendingSearch();
|
|
473
|
+
this.#searchTimer = setTimeout(() => {
|
|
474
|
+
this.#searchTimer = undefined;
|
|
475
|
+
this.#updateResults();
|
|
476
|
+
requestRender();
|
|
477
|
+
}, SEARCH_DEBOUNCE_MS);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Run a pending debounced search now, so selection acts on the current query's results. */
|
|
481
|
+
#flushPendingSearch(): void {
|
|
482
|
+
if (this.#searchTimer === undefined) return;
|
|
483
|
+
this.#cancelPendingSearch();
|
|
484
|
+
this.#updateResults();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
#cancelPendingSearch(): void {
|
|
488
|
+
if (this.#searchTimer === undefined) return;
|
|
489
|
+
clearTimeout(this.#searchTimer);
|
|
490
|
+
this.#searchTimer = undefined;
|
|
491
|
+
}
|
|
492
|
+
|
|
402
493
|
#updateResults(): void {
|
|
494
|
+
this.#cancelPendingSearch();
|
|
403
495
|
const query = this.#searchInput.getValue().trim();
|
|
496
|
+
this.#resultsQuery = query;
|
|
404
497
|
const results = query
|
|
405
498
|
? this.#historyStorage.search(query, this.#resultLimit)
|
|
406
499
|
: this.#historyStorage.getRecent(this.#resultLimit);
|
|
@@ -94,6 +94,10 @@ export interface HookSelectorOptions {
|
|
|
94
94
|
/** Number of leading options (original order) that receive a selection
|
|
95
95
|
* marker. Defaults to every option when {@link selectionMarker} is set. */
|
|
96
96
|
markableCount?: number;
|
|
97
|
+
/** Describe as a card in the dock (the composer slot) instead of a picker
|
|
98
|
+
* sheet, so dock content above it (e.g. a `/omfg` candidate rule) stays
|
|
99
|
+
* visible and the transcript stays scrollable on native surfaces. */
|
|
100
|
+
inline?: boolean;
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
export interface HookSelectorOption {
|
|
@@ -210,6 +214,7 @@ export class HookSelectorComponent extends OverlayPanel {
|
|
|
210
214
|
#sliderComponent: Text | undefined;
|
|
211
215
|
#lastRenderWidth: number | undefined;
|
|
212
216
|
readonly #detailLines: readonly string[];
|
|
217
|
+
readonly #inline: boolean;
|
|
213
218
|
readonly #helpText: string | undefined;
|
|
214
219
|
/** Described option rows by original index; `marker` is the radio state baked into the label. */
|
|
215
220
|
readonly #nativeItems = new Map<number, { marker: boolean; node: NativeNode }>();
|
|
@@ -264,6 +269,7 @@ export class HookSelectorComponent extends OverlayPanel {
|
|
|
264
269
|
this.#baseTitle = this.title;
|
|
265
270
|
this.#detailLines = title.split(/\r?\n/).slice(1);
|
|
266
271
|
this.#helpText = opts?.helpText;
|
|
272
|
+
this.#inline = opts?.inline ?? false;
|
|
267
273
|
this.#onLeftCallback = opts?.onLeft;
|
|
268
274
|
this.#onRightCallback = opts?.onRight;
|
|
269
275
|
this.#onExternalEditorCallback = opts?.onExternalEditor;
|
|
@@ -726,13 +732,14 @@ export class HookSelectorComponent extends OverlayPanel {
|
|
|
726
732
|
}
|
|
727
733
|
|
|
728
734
|
/**
|
|
729
|
-
* A plain option list is a picker sheet. Otherwise
|
|
730
|
-
* title (plus the countdown `elapsed` when a
|
|
731
|
-
* lines, the slider as `tabs`, the options as
|
|
732
|
-
* option index, the search query while typing,
|
|
735
|
+
* A plain option list is a picker sheet. Otherwise (inline, slider, or
|
|
736
|
+
* countdown) a card headed by the title (plus the countdown `elapsed` when a
|
|
737
|
+
* timeout runs), the extra title lines, the slider as `tabs`, the options as
|
|
738
|
+
* a `list` keyed by original option index, the search query while typing,
|
|
739
|
+
* and the key hints.
|
|
733
740
|
*/
|
|
734
741
|
override describe(cx: DescribeContext): NativeNode {
|
|
735
|
-
if (cx.supports("picker") && !this.#slider && !this.#countdown) return this.#describePicker();
|
|
742
|
+
if (cx.supports("picker") && !this.#inline && !this.#slider && !this.#countdown) return this.#describePicker();
|
|
736
743
|
const items = this.#menu.visibleItems;
|
|
737
744
|
const selected = this.#menu.selectedKey;
|
|
738
745
|
const query = this.#menu.query;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { TspAgentProps, TspSpan } from "@linxiraos/pi-wire";
|
|
9
9
|
import { formatDuration } from "@linxiraos/pi-utils";
|
|
10
|
-
import {
|
|
10
|
+
import type { Component } from "../tui";
|
|
11
11
|
import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../keybinding-matchers";
|
|
12
12
|
import { matchesKey } from "../keys";
|
|
13
13
|
import { truncateToWidth } from "../utils";
|
|
@@ -91,31 +91,6 @@ const DOT: Record<JobsPanelJob["status"], TspSpan> = {
|
|
|
91
91
|
/** Output tail rows the terminal fallback shows under the selected job. */
|
|
92
92
|
const FALLBACK_TAIL_LINES = 8;
|
|
93
93
|
|
|
94
|
-
/** The `/jobs` block: `ansi` renders in the terminal, the snapshot describes the native frame. */
|
|
95
|
-
export class JobsPanel extends Container {
|
|
96
|
-
readonly #snapshot: JobsPanelSnapshot;
|
|
97
|
-
readonly #nowMs: number;
|
|
98
|
-
#native: { agent: boolean; node: NativeNode } | undefined;
|
|
99
|
-
|
|
100
|
-
constructor(snapshot: JobsPanelSnapshot, nowMs: number, ansi: readonly Component[]) {
|
|
101
|
-
super();
|
|
102
|
-
this.#snapshot = snapshot;
|
|
103
|
-
this.#nowMs = nowMs;
|
|
104
|
-
for (const child of ansi) this.addChild(child);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
override describe(cx: DescribeContext): NativeNode {
|
|
108
|
-
const agent = cx.supports("agent");
|
|
109
|
-
if (this.#native?.agent === agent) return this.#native.node;
|
|
110
|
-
const head: TspSpan[] = [span("Background jobs", "strong")];
|
|
111
|
-
const running = this.#snapshot.running.length;
|
|
112
|
-
if (running > 0) head.push(span(` · ${running} running`, "muted"));
|
|
113
|
-
const described = card({ role: "zeta.jobs", head }, describeJobs(this.#snapshot, this.#nowMs, agent));
|
|
114
|
-
this.#native = { agent, node: described };
|
|
115
|
-
return described;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
94
|
/**
|
|
120
95
|
* The jobs pill's sheet: a centred `lg` glass sheet titled Background jobs.
|
|
121
96
|
* A selectable list of every job (running first) sits over the selected
|
|
@@ -135,6 +110,8 @@ export class JobsSheet implements Component {
|
|
|
135
110
|
readonly #source: JobsSheetSource;
|
|
136
111
|
#selectedId: string | undefined;
|
|
137
112
|
#native: { key: string; output: string | undefined; node: NativeNode } | undefined;
|
|
113
|
+
/** Fallback output tail, reused while the selected job's output is unchanged. */
|
|
114
|
+
#tail: { id: string; output: string; lines: readonly string[] } | undefined;
|
|
138
115
|
|
|
139
116
|
constructor(source: JobsSheetSource) {
|
|
140
117
|
this.#source = source;
|
|
@@ -202,8 +179,9 @@ export class JobsSheet implements Component {
|
|
|
202
179
|
detail.artifactId && `artifact://${detail.artifactId}`,
|
|
203
180
|
]);
|
|
204
181
|
if (facts.length > 0) lines.push(truncateToWidth(` ${facts.join(" · ")}`, width));
|
|
205
|
-
const
|
|
206
|
-
|
|
182
|
+
for (const line of this.#tailLines(selected.id, detail.output ?? "")) {
|
|
183
|
+
if (line) lines.push(truncateToWidth(` ${line}`, width));
|
|
184
|
+
}
|
|
207
185
|
}
|
|
208
186
|
return lines;
|
|
209
187
|
}
|
|
@@ -235,6 +213,14 @@ export class JobsSheet implements Component {
|
|
|
235
213
|
return { jobs, selected };
|
|
236
214
|
}
|
|
237
215
|
|
|
216
|
+
#tailLines(id: string, output: string): readonly string[] {
|
|
217
|
+
const cached = this.#tail;
|
|
218
|
+
if (cached?.id === id && cached.output === output) return cached.lines;
|
|
219
|
+
const lines = tailLines(output, FALLBACK_TAIL_LINES);
|
|
220
|
+
this.#tail = { id, output, lines };
|
|
221
|
+
return lines;
|
|
222
|
+
}
|
|
223
|
+
|
|
238
224
|
#move(delta: number): void {
|
|
239
225
|
const { jobs, selected } = this.#current();
|
|
240
226
|
if (!selected) return;
|
|
@@ -253,25 +239,21 @@ function jobAge(job: JobsPanelJob, nowMs: number): number {
|
|
|
253
239
|
return Math.max(0, (job.endTime ?? nowMs) - job.startTime);
|
|
254
240
|
}
|
|
255
241
|
|
|
256
|
-
/**
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
recent.map(job => describeJob(job, nowMs, agent)),
|
|
270
|
-
"recent",
|
|
271
|
-
),
|
|
272
|
-
);
|
|
242
|
+
/**
|
|
243
|
+
* The last `count` pieces of `text.trimEnd().split("\n")`, found by scanning
|
|
244
|
+
* back from the end so a long running output is never split as a whole.
|
|
245
|
+
*/
|
|
246
|
+
function tailLines(text: string, count: number): string[] {
|
|
247
|
+
const trimmed = text.trimEnd();
|
|
248
|
+
const lines: string[] = [];
|
|
249
|
+
let end = trimmed.length;
|
|
250
|
+
while (lines.length < count) {
|
|
251
|
+
const newline = end > 0 ? trimmed.lastIndexOf("\n", end - 1) : -1;
|
|
252
|
+
lines.push(trimmed.slice(newline + 1, end));
|
|
253
|
+
if (newline < 0) break;
|
|
254
|
+
end = newline;
|
|
273
255
|
}
|
|
274
|
-
return
|
|
256
|
+
return lines.reverse();
|
|
275
257
|
}
|
|
276
258
|
|
|
277
259
|
/** A task job as an `agent` node when the terminal draws them, else a one-line dot row. */
|