@linxiraos/pi-tui 1.1.26 → 1.2.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 +51 -491
- package/README.md +13 -7
- package/dist/types/active-terminal.d.ts +30 -0
- package/dist/types/app-keybindings.d.ts +7 -2
- 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 +5 -4
- 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 +11 -1
- package/dist/types/chat/chat-transcript-builder.d.ts +3 -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 +17 -4
- package/dist/types/chrome/shared.d.ts +9 -0
- package/dist/types/chrome/status-notice.d.ts +15 -3
- 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 +23 -5
- package/dist/types/components/form.d.ts +1 -0
- package/dist/types/components/image.d.ts +26 -44
- package/dist/types/components/input.d.ts +2 -1
- 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/loader.d.ts +5 -6
- package/dist/types/components/markdown.d.ts +47 -5
- 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 +9 -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/apply.d.ts +4 -3
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +7 -3
- package/dist/types/native/encode.d.ts +8 -6
- package/dist/types/native/node.d.ts +36 -5
- package/dist/types/native/overlay.d.ts +8 -3
- package/dist/types/native/settle.d.ts +4 -3
- 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 +3 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +12 -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/btw-history-panel.d.ts +13 -1
- package/dist/types/overlays/copy-selector.d.ts +1 -0
- package/dist/types/overlays/history-search.d.ts +10 -0
- package/dist/types/overlays/hook-selector.d.ts +9 -4
- package/dist/types/overlays/hub-frame.d.ts +4 -1
- 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 +37 -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/report-panel.d.ts +68 -0
- package/dist/types/overlays/rewind-selector.d.ts +1 -0
- 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/thinking-selector.d.ts +2 -2
- package/dist/types/overlays/usage-dashboard.d.ts +12 -8
- package/dist/types/overlays/usage-display.d.ts +3 -1
- package/dist/types/prompt/composer-cache.d.ts +0 -16
- package/dist/types/prompt/composer.d.ts +18 -3
- package/dist/types/prompt/custom-editor.d.ts +25 -13
- package/dist/types/prompt/image-source.d.ts +2 -1
- package/dist/types/prompt/interactive-context-helpers.d.ts +5 -1
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/prompt/welcome.d.ts +7 -2
- 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/types.d.ts +1 -0
- package/dist/types/space-hold.d.ts +40 -25
- package/dist/types/status-line/component.d.ts +3 -0
- package/dist/types/status-line/context-usage.d.ts +7 -5
- 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 +41 -36
- package/dist/types/terminal-multiplexer.d.ts +127 -14
- package/dist/types/terminal.d.ts +23 -16
- 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/thinking.d.ts +1 -1
- package/dist/types/tmux.d.ts +11 -4
- package/dist/types/tools/bash.d.ts +7 -2
- 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 +2 -3
- package/dist/types/tools/write.d.ts +9 -2
- package/dist/types/tools/yield.d.ts +22 -0
- package/dist/types/tui.d.ts +36 -0
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/vim.d.ts +19 -5
- package/dist/types/windows-input-mode.d.ts +33 -1
- package/package.json +13 -9
- package/src/active-terminal.d.ts +30 -0
- package/src/active-terminal.ts +80 -0
- package/src/app-keybindings.d.ts +7 -2
- package/src/app-keybindings.ts +7 -2
- 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 +96 -23
- package/src/apps/git/git-tui.ts +23 -16
- package/src/apps/git/help.ts +29 -20
- package/src/apps/git/sidebar.ts +8 -3
- 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/bracketed-paste.ts +10 -3
- 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 +11 -1
- package/src/chat/assistant-message.ts +245 -54
- package/src/chat/chat-transcript-builder.d.ts +3 -0
- package/src/chat/chat-transcript-builder.ts +9 -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 +86 -7
- package/src/chat/user-message.d.ts +17 -4
- package/src/chat/user-message.ts +71 -8
- package/src/chrome/shared.ts +53 -0
- package/src/chrome/status-notice.ts +25 -7
- package/src/chrome/transcript-container.d.ts +6 -0
- package/src/chrome/transcript-container.ts +162 -19
- 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 +23 -5
- package/src/components/editor.ts +316 -47
- package/src/components/form.d.ts +1 -0
- package/src/components/form.ts +8 -0
- package/src/components/image.d.ts +26 -44
- package/src/components/image.ts +115 -152
- package/src/components/input.d.ts +2 -1
- package/src/components/input.ts +17 -7
- 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/loader.d.ts +5 -6
- package/src/components/loader.ts +10 -17
- package/src/components/markdown.d.ts +47 -5
- package/src/components/markdown.ts +786 -303
- 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 +3 -2
- package/src/fuzzy.d.ts +29 -1
- package/src/fuzzy.ts +115 -30
- package/src/hotkeys-markdown.ts +9 -2
- package/src/key-hint-format.d.ts +9 -1
- package/src/key-hint-format.ts +26 -11
- 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 +11 -5
- 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/apply.d.ts +4 -3
- package/src/native/apply.ts +4 -3
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +199 -22
- package/src/native/blobs.d.ts +7 -3
- package/src/native/blobs.ts +47 -12
- package/src/native/encode.d.ts +8 -6
- package/src/native/encode.ts +76 -34
- package/src/native/node.d.ts +36 -5
- package/src/native/node.ts +31 -5
- package/src/native/overlay.d.ts +8 -3
- package/src/native/overlay.ts +17 -4
- package/src/native/reconcile.ts +16 -4
- package/src/native/settle.d.ts +4 -3
- package/src/native/settle.ts +4 -3
- 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 +48 -29
- package/src/overlays/agent-transcript-viewer.ts +93 -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 +10 -17
- package/src/overlays/btw-history-panel.ts +136 -39
- package/src/overlays/copy-selector.d.ts +1 -0
- package/src/overlays/copy-selector.ts +32 -8
- package/src/overlays/extensions/extension-dashboard.ts +4 -4
- 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/hub-frame.ts +19 -1
- package/src/overlays/jobs-panel.ts +33 -52
- package/src/overlays/model-browser.ts +367 -122
- package/src/overlays/model-hub.ts +435 -59
- package/src/overlays/move-overlay.ts +28 -14
- package/src/overlays/oauth-selector.ts +54 -70
- package/src/overlays/pause-screen.ts +2 -2
- package/src/overlays/plan-review-overlay.ts +208 -70
- package/src/overlays/plugin-settings.ts +3 -3
- package/src/overlays/report-panel.ts +191 -0
- package/src/overlays/rewind-selector.ts +88 -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 +5 -4
- 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/thinking-selector.ts +11 -10
- package/src/overlays/tree-selector.ts +41 -20
- package/src/overlays/usage-dashboard.ts +117 -58
- package/src/overlays/usage-display.ts +10 -4
- package/src/overlays/usage-row.ts +22 -8
- package/src/prompt/attachment-chips.ts +15 -11
- package/src/prompt/composer-attachments.ts +6 -4
- package/src/prompt/composer-cache.ts +7 -86
- package/src/prompt/composer.ts +37 -26
- package/src/prompt/custom-editor.ts +282 -105
- package/src/prompt/image-source.ts +2 -1
- package/src/prompt/interactive-context-helpers.ts +32 -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/queued-messages.ts +6 -4
- package/src/prompt/tips.txt +13 -9
- package/src/prompt/welcome.ts +64 -46
- 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 +16 -1
- 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/types.ts +1 -0
- package/src/space-hold.d.ts +40 -25
- package/src/space-hold.ts +156 -52
- package/src/status-line/component.ts +292 -78
- package/src/status-line/context-usage.ts +21 -46
- 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 +41 -36
- package/src/terminal-capabilities.ts +137 -213
- package/src/terminal-multiplexer.d.ts +127 -14
- package/src/terminal-multiplexer.ts +112 -33
- package/src/terminal.d.ts +23 -16
- package/src/terminal.ts +203 -99
- 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 +11 -4
- 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/thinking.d.ts +1 -1
- package/src/thinking.ts +1 -1
- 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.d.ts +7 -2
- package/src/tools/bash.ts +116 -47
- 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 +154 -50
- package/src/tools/task.d.ts +2 -3
- package/src/tools/task.ts +115 -62
- 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 +36 -0
- package/src/tui.ts +666 -217
- 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.d.ts +33 -1
- package/src/windows-input-mode.ts +212 -6
|
@@ -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;
|
|
@@ -149,6 +149,10 @@ export class HubFrame {
|
|
|
149
149
|
readonly #stack: Stack;
|
|
150
150
|
readonly #title: string;
|
|
151
151
|
readonly #sidebarBounds: { min: number; max: number };
|
|
152
|
+
/** Whether narrow layouts may reduce the body floor to keep the sidebar usable. */
|
|
153
|
+
readonly #preserveSidebar: boolean;
|
|
154
|
+
/** Preferred body floor, reduced only as needed to retain sidebar navigation. */
|
|
155
|
+
readonly #bodyMinWidth: number;
|
|
152
156
|
|
|
153
157
|
/** Compose hub-specific sidebar and body renderers into a persistent frame. */
|
|
154
158
|
constructor(
|
|
@@ -156,9 +160,12 @@ export class HubFrame {
|
|
|
156
160
|
sidebarBounds: { min: number; max: number },
|
|
157
161
|
renderSidebar: (width: number, rows: number) => string[],
|
|
158
162
|
renderBody: (width: number, height: number | undefined) => readonly string[],
|
|
163
|
+
options: { bodyMinWidth?: number; preserveSidebar?: boolean } = {},
|
|
159
164
|
) {
|
|
160
165
|
this.#title = title;
|
|
161
166
|
this.#sidebarBounds = sidebarBounds;
|
|
167
|
+
this.#preserveSidebar = options.preserveSidebar ?? false;
|
|
168
|
+
this.#bodyMinWidth = options.bodyMinWidth ?? 0;
|
|
162
169
|
this.#split = new SplitPane({
|
|
163
170
|
left: (width, height) => {
|
|
164
171
|
const rows = Math.max(0, Math.floor(height ?? 10));
|
|
@@ -167,6 +174,7 @@ export class HubFrame {
|
|
|
167
174
|
return lines.slice(0, rows);
|
|
168
175
|
},
|
|
169
176
|
right: renderBody,
|
|
177
|
+
rightMinWidth: this.#bodyMinWidth,
|
|
170
178
|
prefix: () => `${theme.fg("border", theme.boxRound.vertical)} `,
|
|
171
179
|
divider: () => ` ${theme.fg("border", theme.boxRound.vertical)} `,
|
|
172
180
|
suffix: () => ` ${theme.fg("border", theme.boxRound.vertical)}`,
|
|
@@ -330,7 +338,17 @@ export class HubFrame {
|
|
|
330
338
|
/** Paint a complete fullscreen hub with its title, width bounds, and contextual footer. */
|
|
331
339
|
render(width: number, height: number, entries: readonly SidebarEntry<string>[], footer: string): readonly string[] {
|
|
332
340
|
const contentRows = Math.max(10, height - 4);
|
|
333
|
-
|
|
341
|
+
const sidebarWidth = this.sidebarWidth(entries);
|
|
342
|
+
this.#split.setLeftSize({
|
|
343
|
+
fixed: sidebarWidth,
|
|
344
|
+
min: this.#preserveSidebar ? this.#sidebarBounds.min : undefined,
|
|
345
|
+
});
|
|
346
|
+
if (this.#preserveSidebar && this.#bodyMinWidth > 0) {
|
|
347
|
+
const geometry = this.#split.measure(width);
|
|
348
|
+
const splitWidth = (geometry.left?.width ?? 0) + (geometry.right?.width ?? 0);
|
|
349
|
+
const rightMinWidth = Math.max(0, Math.min(this.#bodyMinWidth, splitWidth - this.#sidebarBounds.min));
|
|
350
|
+
this.#split.setRightMinWidth(rightMinWidth);
|
|
351
|
+
}
|
|
334
352
|
const leftWidth = this.#split.measure(width).left?.width ?? 0;
|
|
335
353
|
this.#top.setLines([topBorderSplit(width, this.#title, leftWidth)]);
|
|
336
354
|
this.#divider.setLines([dividerSplit(width, leftWidth)]);
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The async background jobs of a session
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* inspects the selected one (command, cwd, live pids, exit code, output tail).
|
|
2
|
+
* The async background jobs of a session as {@link JobsSheet}: the dismissable
|
|
3
|
+
* sheet the native jobs pill and `/jobs` open. It draws task jobs as `agent`
|
|
4
|
+
* nodes and the others as a status dot, the one-line label and a live
|
|
5
|
+
* `elapsed`, lists every job and inspects the selected one (command, cwd, live
|
|
6
|
+
* pids, exit code, output tail).
|
|
8
7
|
*/
|
|
9
8
|
import type { TspAgentProps, TspSpan } from "@linxiraos/pi-wire";
|
|
10
9
|
import { formatDuration } from "@linxiraos/pi-utils";
|
|
11
|
-
import {
|
|
10
|
+
import type { Component } from "../tui";
|
|
12
11
|
import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../keybinding-matchers";
|
|
13
12
|
import { matchesKey } from "../keys";
|
|
14
13
|
import { truncateToWidth } from "../utils";
|
|
@@ -92,31 +91,6 @@ const DOT: Record<JobsPanelJob["status"], TspSpan> = {
|
|
|
92
91
|
/** Output tail rows the terminal fallback shows under the selected job. */
|
|
93
92
|
const FALLBACK_TAIL_LINES = 8;
|
|
94
93
|
|
|
95
|
-
/** The `/jobs` block: `ansi` renders in the terminal, the snapshot describes the native frame. */
|
|
96
|
-
export class JobsPanel extends Container {
|
|
97
|
-
readonly #snapshot: JobsPanelSnapshot;
|
|
98
|
-
readonly #nowMs: number;
|
|
99
|
-
#native: { agent: boolean; node: NativeNode } | undefined;
|
|
100
|
-
|
|
101
|
-
constructor(snapshot: JobsPanelSnapshot, nowMs: number, ansi: readonly Component[]) {
|
|
102
|
-
super();
|
|
103
|
-
this.#snapshot = snapshot;
|
|
104
|
-
this.#nowMs = nowMs;
|
|
105
|
-
for (const child of ansi) this.addChild(child);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
override describe(cx: DescribeContext): NativeNode {
|
|
109
|
-
const agent = cx.supports("agent");
|
|
110
|
-
if (this.#native?.agent === agent) return this.#native.node;
|
|
111
|
-
const head: TspSpan[] = [span("Background jobs", "strong")];
|
|
112
|
-
const running = this.#snapshot.running.length;
|
|
113
|
-
if (running > 0) head.push(span(` · ${running} running`, "muted"));
|
|
114
|
-
const described = card({ role: "zeta.jobs", head }, describeJobs(this.#snapshot, this.#nowMs, agent));
|
|
115
|
-
this.#native = { agent, node: described };
|
|
116
|
-
return described;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
94
|
/**
|
|
121
95
|
* The jobs pill's sheet: a centred `lg` glass sheet titled Background jobs.
|
|
122
96
|
* A selectable list of every job (running first) sits over the selected
|
|
@@ -136,6 +110,8 @@ export class JobsSheet implements Component {
|
|
|
136
110
|
readonly #source: JobsSheetSource;
|
|
137
111
|
#selectedId: string | undefined;
|
|
138
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;
|
|
139
115
|
|
|
140
116
|
constructor(source: JobsSheetSource) {
|
|
141
117
|
this.#source = source;
|
|
@@ -203,8 +179,9 @@ export class JobsSheet implements Component {
|
|
|
203
179
|
detail.artifactId && `artifact://${detail.artifactId}`,
|
|
204
180
|
]);
|
|
205
181
|
if (facts.length > 0) lines.push(truncateToWidth(` ${facts.join(" · ")}`, width));
|
|
206
|
-
const
|
|
207
|
-
|
|
182
|
+
for (const line of this.#tailLines(selected.id, detail.output ?? "")) {
|
|
183
|
+
if (line) lines.push(truncateToWidth(` ${line}`, width));
|
|
184
|
+
}
|
|
208
185
|
}
|
|
209
186
|
return lines;
|
|
210
187
|
}
|
|
@@ -236,6 +213,14 @@ export class JobsSheet implements Component {
|
|
|
236
213
|
return { jobs, selected };
|
|
237
214
|
}
|
|
238
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
|
+
|
|
239
224
|
#move(delta: number): void {
|
|
240
225
|
const { jobs, selected } = this.#current();
|
|
241
226
|
if (!selected) return;
|
|
@@ -254,25 +239,21 @@ function jobAge(job: JobsPanelJob, nowMs: number): number {
|
|
|
254
239
|
return Math.max(0, (job.endTime ?? nowMs) - job.startTime);
|
|
255
240
|
}
|
|
256
241
|
|
|
257
|
-
/**
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
recent.map(job => describeJob(job, nowMs, agent)),
|
|
271
|
-
"recent",
|
|
272
|
-
),
|
|
273
|
-
);
|
|
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;
|
|
274
255
|
}
|
|
275
|
-
return
|
|
256
|
+
return lines.reverse();
|
|
276
257
|
}
|
|
277
258
|
|
|
278
259
|
/** A task job as an `agent` node when the terminal draws them, else a one-line dot row. */
|