@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
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
* Focus regions (`toc`/`body`/`actions`) cycle with Tab/Shift+Tab; arrows move
|
|
14
14
|
* within the focused region and step left into the sidebar. The default focus is
|
|
15
15
|
* `actions`, so the muscle memory of the old single-target overlay carries over:
|
|
16
|
-
* ↑/↓ select options, Enter confirms, ←/→ drives the slider
|
|
17
|
-
*
|
|
16
|
+
* ↑/↓ select options, Enter confirms, ←/→ drives the slider, g/G + PgUp/PgDn
|
|
17
|
+
* scroll, and the external-editor key opens the plan. Under Tern the options
|
|
18
|
+
* are a horizontal bar, so ←/→ select options and Shift+←/→ drive the slider.
|
|
18
19
|
*/
|
|
19
20
|
import {
|
|
20
21
|
type Component,
|
|
21
22
|
Ellipsis,
|
|
22
23
|
Editor,
|
|
24
|
+
getWidthConfigEpoch,
|
|
23
25
|
Markdown,
|
|
24
26
|
type MarkdownTheme,
|
|
25
27
|
matchesKey,
|
|
@@ -31,7 +33,7 @@ import {
|
|
|
31
33
|
} from "../index";
|
|
32
34
|
import { sanitizeText } from "@linxiraos/pi-utils";
|
|
33
35
|
import { sanitizeStatusText } from "../chrome/shared";
|
|
34
|
-
import { getEditorTheme, getMarkdownTheme, theme } from "../theme/theme";
|
|
36
|
+
import { getEditorTheme, getMarkdownTheme, getThemeEpoch, theme } from "../theme/theme";
|
|
35
37
|
import {
|
|
36
38
|
matchesAppExternalEditor,
|
|
37
39
|
matchesSelectCancel,
|
|
@@ -58,6 +60,7 @@ import type { KeyName } from "../key-hint-format";
|
|
|
58
60
|
import { col, item, keyed, md, node, row as rowNode, span, text } from "../native/describe";
|
|
59
61
|
import { leafKey, type NativeChild, type NativeNode, type NativeUiEvent } from "../native/node";
|
|
60
62
|
import { actionButton, actionHint, hintsRow, itemIndex, type NativeHint, selectList } from "../native/overlay";
|
|
63
|
+
import { isNativeRendering } from "../native/state";
|
|
61
64
|
import { getKeybindings } from "../keybindings";
|
|
62
65
|
|
|
63
66
|
/** Title shown in the overlay's top border. */
|
|
@@ -74,6 +77,8 @@ const SIDEBAR_MIN_TOTAL_WIDTH = 64;
|
|
|
74
77
|
const SIDEBAR_MIN_BODY_WIDTH = 40;
|
|
75
78
|
/** Persisted line-context cap; render-time captions clamp again to the viewport. */
|
|
76
79
|
const MAX_ANNOTATION_CONTEXT_WIDTH = 120;
|
|
80
|
+
/** Oldest delete/annotate undo snapshots are dropped past this depth (each holds the full plan text). */
|
|
81
|
+
const MAX_UNDO_ENTRIES = 100;
|
|
77
82
|
|
|
78
83
|
type Focus = "toc" | "body" | "actions";
|
|
79
84
|
|
|
@@ -146,6 +151,78 @@ interface UndoEntry {
|
|
|
146
151
|
deleted: string[];
|
|
147
152
|
}
|
|
148
153
|
|
|
154
|
+
/** Concatenated plan body rows and their anchors, reused while every render input is unchanged. */
|
|
155
|
+
interface BodyLayout {
|
|
156
|
+
width: number;
|
|
157
|
+
annotationRev: number;
|
|
158
|
+
themeEpoch: number;
|
|
159
|
+
widthEpoch: number;
|
|
160
|
+
/** Each section's `Markdown.render` result the layout was built from (compared by identity). */
|
|
161
|
+
rendered: readonly (readonly string[])[];
|
|
162
|
+
lines: readonly string[];
|
|
163
|
+
anchors: BodyRowAnchor[];
|
|
164
|
+
offsets: number[];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Line contexts per rendered-lines array; `Markdown.render` returns a stable array per (text, width). */
|
|
168
|
+
const lineContextCache = new WeakMap<readonly string[], readonly LineAnchorContext[]>();
|
|
169
|
+
/** Match keys per context list, so resolving several annotations normalizes each row once. */
|
|
170
|
+
const normalizedContextCache = new WeakMap<readonly LineAnchorContext[], readonly string[]>();
|
|
171
|
+
|
|
172
|
+
function lineContexts(rendered: readonly string[]): readonly LineAnchorContext[] {
|
|
173
|
+
let contexts = lineContextCache.get(rendered);
|
|
174
|
+
if (!contexts) {
|
|
175
|
+
contexts = rendered.map(line => {
|
|
176
|
+
const sanitized = sanitizeStatusText(line);
|
|
177
|
+
const truncated = visibleWidth(sanitized) > MAX_ANNOTATION_CONTEXT_WIDTH;
|
|
178
|
+
const text = truncateToWidth(sanitized, MAX_ANNOTATION_CONTEXT_WIDTH, Ellipsis.Unicode);
|
|
179
|
+
return { text: text || "(blank line)", truncated };
|
|
180
|
+
});
|
|
181
|
+
lineContextCache.set(rendered, contexts);
|
|
182
|
+
}
|
|
183
|
+
return contexts;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function normalizeLineContext(context: LineAnchorContext): string {
|
|
187
|
+
const normalized = sanitizeStatusText(context.text).replace(/\s+/g, " ").trim();
|
|
188
|
+
return context.truncated && normalized.endsWith("…") ? normalized.slice(0, -1) : normalized;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Row in `contexts` nearest `storedRow` whose text matches the stored context, or -1. */
|
|
192
|
+
function resolveLineRow(
|
|
193
|
+
storedRow: number,
|
|
194
|
+
storedContext: LineAnchorContext,
|
|
195
|
+
contexts: readonly LineAnchorContext[],
|
|
196
|
+
): number {
|
|
197
|
+
if (contexts.length === 0) return -1;
|
|
198
|
+
const targetRow = Math.max(0, Math.floor(storedRow));
|
|
199
|
+
const normalizedStoredContext = normalizeLineContext(storedContext);
|
|
200
|
+
if (!normalizedStoredContext) return -1;
|
|
201
|
+
let normalizedContexts = normalizedContextCache.get(contexts);
|
|
202
|
+
if (!normalizedContexts) {
|
|
203
|
+
normalizedContexts = contexts.map(normalizeLineContext);
|
|
204
|
+
normalizedContextCache.set(contexts, normalizedContexts);
|
|
205
|
+
}
|
|
206
|
+
let best = -1;
|
|
207
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
208
|
+
for (let row = 0; row < normalizedContexts.length; row++) {
|
|
209
|
+
const normalizedContext = normalizedContexts[row]!;
|
|
210
|
+
if (
|
|
211
|
+
normalizedContext !== normalizedStoredContext &&
|
|
212
|
+
!normalizedContext.includes(normalizedStoredContext) &&
|
|
213
|
+
!normalizedStoredContext.includes(normalizedContext)
|
|
214
|
+
) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const distance = Math.abs(row - targetRow);
|
|
218
|
+
if (distance < bestDistance) {
|
|
219
|
+
best = row;
|
|
220
|
+
bestDistance = distance;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return best;
|
|
224
|
+
}
|
|
225
|
+
|
|
149
226
|
export interface PlanReviewOverlayCallbacks {
|
|
150
227
|
/** Invoked with the chosen option label (never a disabled one). */
|
|
151
228
|
onPick: (label: string) => void;
|
|
@@ -197,6 +274,10 @@ export class PlanReviewOverlay implements Component {
|
|
|
197
274
|
#sectionOffsets: number[] = [];
|
|
198
275
|
/** Rendered body row to underlying plan row; callouts retain their owner's anchor. */
|
|
199
276
|
#bodyRowAnchors: BodyRowAnchor[] = [];
|
|
277
|
+
/** Memoized {@link #buildBody} result. */
|
|
278
|
+
#bodyLayout: BodyLayout | undefined;
|
|
279
|
+
/** Lines last handed to the scroll view; unchanged layouts skip the copy and keep its render cache. */
|
|
280
|
+
#scrollLines: readonly string[] | undefined;
|
|
200
281
|
#undo: UndoEntry[] = [];
|
|
201
282
|
/** Titles of sections deleted in the overlay, surfaced as Refine feedback. */
|
|
202
283
|
#deleted: string[] = [];
|
|
@@ -249,6 +330,11 @@ export class PlanReviewOverlay implements Component {
|
|
|
249
330
|
#nativeRoot: NativeNode | undefined;
|
|
250
331
|
/** Content the memoized root was built from. */
|
|
251
332
|
#nativeRootContent: NativePlanContent | undefined;
|
|
333
|
+
/**
|
|
334
|
+
* The section the last Contents jump targets under a native surface, which
|
|
335
|
+
* scrolls the body itself: `n` counts the jumps, so each one reveals it again.
|
|
336
|
+
*/
|
|
337
|
+
#jump: { section: number; n: number } | undefined;
|
|
252
338
|
|
|
253
339
|
constructor(
|
|
254
340
|
planContent: string,
|
|
@@ -437,8 +523,8 @@ export class PlanReviewOverlay implements Component {
|
|
|
437
523
|
) {
|
|
438
524
|
continue;
|
|
439
525
|
}
|
|
440
|
-
const contexts = section.md.render(MAX_ANNOTATION_CONTEXT_WIDTH)
|
|
441
|
-
const row =
|
|
526
|
+
const contexts = lineContexts(section.md.render(MAX_ANNOTATION_CONTEXT_WIDTH));
|
|
527
|
+
const row = resolveLineRow(
|
|
442
528
|
entry.target.row,
|
|
443
529
|
{ text: entry.target.context, truncated: entry.target.contextTruncated === true },
|
|
444
530
|
contexts,
|
|
@@ -661,6 +747,10 @@ export class PlanReviewOverlay implements Component {
|
|
|
661
747
|
}
|
|
662
748
|
|
|
663
749
|
#handleActions(data: string): void {
|
|
750
|
+
if (isNativeRendering()) {
|
|
751
|
+
this.#handleNativeActions(data);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
664
754
|
// Left/right always drive the slider. The sidebar sits beside the body
|
|
665
755
|
// (above this row), not the slider, so stealing left for it would strand
|
|
666
756
|
// the operator unable to step the model tier back — reach the ToC via Tab.
|
|
@@ -690,6 +780,37 @@ export class PlanReviewOverlay implements Component {
|
|
|
690
780
|
this.#handleBodyScroll(data);
|
|
691
781
|
}
|
|
692
782
|
|
|
783
|
+
/** Tern lays the options out as a horizontal decision bar: ←/→ step through
|
|
784
|
+
* it, ↑ returns to the body above, and the model slider moves to Shift+←/→. */
|
|
785
|
+
#handleNativeActions(data: string): void {
|
|
786
|
+
if (matchesKey(data, "shift+left")) {
|
|
787
|
+
this.#moveSlider(-1);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (matchesKey(data, "shift+right")) {
|
|
791
|
+
this.#moveSlider(1);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (matchesKey(data, "left") || matchesKey(data, "h")) {
|
|
795
|
+
this.#moveSelection(-1);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
if (matchesKey(data, "right") || matchesKey(data, "l")) {
|
|
799
|
+
this.#moveSelection(1);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (matchesSelectUp(data) || matchesKey(data, "k")) {
|
|
803
|
+
this.#setFocus("body");
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (matchesSelectDown(data) || matchesKey(data, "j")) return;
|
|
807
|
+
if (matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
|
|
808
|
+
this.#confirmSelection();
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
this.#handleBodyScroll(data);
|
|
812
|
+
}
|
|
813
|
+
|
|
693
814
|
#handleBody(data: string): void {
|
|
694
815
|
if (data === "a") {
|
|
695
816
|
this.#startBodyAnnotate();
|
|
@@ -798,6 +919,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
798
919
|
#scrubBodyToToc(): void {
|
|
799
920
|
const sectionIndex = this.#toc[this.#tocCursor];
|
|
800
921
|
if (sectionIndex === undefined) return;
|
|
922
|
+
this.#jump = { section: sectionIndex, n: (this.#jump?.n ?? 0) + 1 };
|
|
801
923
|
const offset = this.#sectionOffsets[sectionIndex];
|
|
802
924
|
if (offset !== undefined) {
|
|
803
925
|
this.#scrollView.setScrollOffset(offset);
|
|
@@ -823,6 +945,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
823
945
|
}
|
|
824
946
|
|
|
825
947
|
#pushUndo(): void {
|
|
948
|
+
if (this.#undo.length >= MAX_UNDO_ENTRIES) this.#undo.shift();
|
|
826
949
|
this.#undo.push({
|
|
827
950
|
text: joinPlanSections(this.#sections),
|
|
828
951
|
annotations: this.#sections.map(section =>
|
|
@@ -916,7 +1039,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
916
1039
|
seenRows.add(candidate.row);
|
|
917
1040
|
sectionAnchors.push({ text: candidate.context, truncated: candidate.contextTruncated });
|
|
918
1041
|
}
|
|
919
|
-
const resolvedRow =
|
|
1042
|
+
const resolvedRow = resolveLineRow(
|
|
920
1043
|
annotation.target.row,
|
|
921
1044
|
{ text: annotation.target.context, truncated: annotation.target.contextTruncated },
|
|
922
1045
|
sectionAnchors,
|
|
@@ -1144,23 +1267,64 @@ export class PlanReviewOverlay implements Component {
|
|
|
1144
1267
|
|
|
1145
1268
|
#layoutBody(lines: readonly string[], height: number): void {
|
|
1146
1269
|
this.#captureScrollProgress();
|
|
1147
|
-
this.#
|
|
1270
|
+
if (lines !== this.#scrollLines) {
|
|
1271
|
+
this.#scrollView.setLines(lines);
|
|
1272
|
+
this.#scrollLines = lines;
|
|
1273
|
+
}
|
|
1148
1274
|
this.#scrollView.setHeight(height);
|
|
1149
1275
|
const maxOffset = this.#scrollView.getMaxScrollOffset();
|
|
1150
1276
|
if (maxOffset > 0) this.#scrollView.setScrollOffset(Math.round(this.#scrollProgress * maxOffset));
|
|
1151
1277
|
}
|
|
1152
1278
|
|
|
1153
|
-
/**
|
|
1154
|
-
|
|
1279
|
+
/**
|
|
1280
|
+
* Build the concatenated body lines and map each rendered row back to a plan
|
|
1281
|
+
* anchor. Memoized on width, annotation revision, theme/width epochs and each
|
|
1282
|
+
* section's rendered-lines identity, so steady frames and scrolling reuse it.
|
|
1283
|
+
*/
|
|
1284
|
+
#buildBody(bodyContentWidth: number): readonly string[] {
|
|
1285
|
+
const themeEpoch = getThemeEpoch();
|
|
1286
|
+
const widthEpoch = getWidthConfigEpoch();
|
|
1287
|
+
const cached = this.#bodyLayout;
|
|
1288
|
+
if (
|
|
1289
|
+
cached?.width === bodyContentWidth &&
|
|
1290
|
+
cached.annotationRev === this.#annotationRev &&
|
|
1291
|
+
cached.themeEpoch === themeEpoch &&
|
|
1292
|
+
cached.widthEpoch === widthEpoch &&
|
|
1293
|
+
cached.rendered.length === this.#sections.length &&
|
|
1294
|
+
this.#sections.every((section, i) => section.md.render(bodyContentWidth) === cached.rendered[i])
|
|
1295
|
+
) {
|
|
1296
|
+
this.#sectionOffsets = cached.offsets;
|
|
1297
|
+
this.#bodyRowAnchors = cached.anchors;
|
|
1298
|
+
return cached.lines;
|
|
1299
|
+
}
|
|
1155
1300
|
const lines: string[] = [];
|
|
1156
1301
|
const anchors: BodyRowAnchor[] = [];
|
|
1302
|
+
const renderedSections: (readonly string[])[] = [];
|
|
1157
1303
|
// oxlint-disable-next-line unicorn/no-new-array -- length preallocation
|
|
1158
1304
|
const offsets: number[] = new Array(this.#sections.length);
|
|
1159
1305
|
for (let sectionIndex = 0; sectionIndex < this.#sections.length; sectionIndex++) {
|
|
1160
1306
|
const section = this.#sections[sectionIndex]!;
|
|
1161
1307
|
offsets[sectionIndex] = lines.length;
|
|
1162
1308
|
const rendered = section.md.render(bodyContentWidth);
|
|
1163
|
-
|
|
1309
|
+
renderedSections.push(rendered);
|
|
1310
|
+
const contexts = lineContexts(rendered);
|
|
1311
|
+
// Resolve each note's row once; notes on one row keep their array order.
|
|
1312
|
+
let notesByRow: Map<number, string[]> | undefined;
|
|
1313
|
+
for (const annotation of section.annotations) {
|
|
1314
|
+
const annotationRow =
|
|
1315
|
+
annotation.target.kind === "section"
|
|
1316
|
+
? 0
|
|
1317
|
+
: resolveLineRow(
|
|
1318
|
+
annotation.target.row,
|
|
1319
|
+
{ text: annotation.target.context, truncated: annotation.target.contextTruncated },
|
|
1320
|
+
contexts,
|
|
1321
|
+
);
|
|
1322
|
+
if (annotationRow < 0) continue;
|
|
1323
|
+
notesByRow ??= new Map();
|
|
1324
|
+
const notes = notesByRow.get(annotationRow);
|
|
1325
|
+
if (notes) notes.push(annotation.note);
|
|
1326
|
+
else notesByRow.set(annotationRow, [annotation.note]);
|
|
1327
|
+
}
|
|
1164
1328
|
for (let row = 0; row < rendered.length; row++) {
|
|
1165
1329
|
const context = contexts[row]!;
|
|
1166
1330
|
const anchor = {
|
|
@@ -1171,67 +1335,24 @@ export class PlanReviewOverlay implements Component {
|
|
|
1171
1335
|
};
|
|
1172
1336
|
lines.push(rendered[row]!);
|
|
1173
1337
|
anchors.push(anchor);
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
? 0
|
|
1178
|
-
: this.#resolveLineRow(
|
|
1179
|
-
annotation.target.row,
|
|
1180
|
-
{
|
|
1181
|
-
text: annotation.target.context,
|
|
1182
|
-
truncated: annotation.target.contextTruncated,
|
|
1183
|
-
},
|
|
1184
|
-
contexts,
|
|
1185
|
-
);
|
|
1186
|
-
if (annotationRow === row) {
|
|
1187
|
-
this.#appendAnnotationCallout(lines, anchors, annotation.note, anchor, bodyContentWidth);
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1338
|
+
const notes = notesByRow?.get(row);
|
|
1339
|
+
if (!notes) continue;
|
|
1340
|
+
for (const note of notes) this.#appendAnnotationCallout(lines, anchors, note, anchor, bodyContentWidth);
|
|
1190
1341
|
}
|
|
1191
1342
|
}
|
|
1192
1343
|
this.#sectionOffsets = offsets;
|
|
1193
1344
|
this.#bodyRowAnchors = anchors;
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
#resolveLineRow(
|
|
1205
|
-
storedRow: number,
|
|
1206
|
-
storedContext: LineAnchorContext,
|
|
1207
|
-
contexts: readonly LineAnchorContext[],
|
|
1208
|
-
): number {
|
|
1209
|
-
if (contexts.length === 0) return -1;
|
|
1210
|
-
const targetRow = Math.max(0, Math.floor(storedRow));
|
|
1211
|
-
const normalize = (context: LineAnchorContext): string => {
|
|
1212
|
-
const normalized = sanitizeStatusText(context.text).replace(/\s+/g, " ").trim();
|
|
1213
|
-
return context.truncated && normalized.endsWith("…") ? normalized.slice(0, -1) : normalized;
|
|
1345
|
+
this.#bodyLayout = {
|
|
1346
|
+
width: bodyContentWidth,
|
|
1347
|
+
annotationRev: this.#annotationRev,
|
|
1348
|
+
themeEpoch,
|
|
1349
|
+
widthEpoch,
|
|
1350
|
+
rendered: renderedSections,
|
|
1351
|
+
lines,
|
|
1352
|
+
anchors,
|
|
1353
|
+
offsets,
|
|
1214
1354
|
};
|
|
1215
|
-
|
|
1216
|
-
if (!normalizedStoredContext) return -1;
|
|
1217
|
-
let best = -1;
|
|
1218
|
-
let bestDistance = Number.POSITIVE_INFINITY;
|
|
1219
|
-
for (let row = 0; row < contexts.length; row++) {
|
|
1220
|
-
const normalizedContext = normalize(contexts[row]!);
|
|
1221
|
-
if (
|
|
1222
|
-
normalizedContext !== normalizedStoredContext &&
|
|
1223
|
-
!normalizedContext.includes(normalizedStoredContext) &&
|
|
1224
|
-
!normalizedStoredContext.includes(normalizedContext)
|
|
1225
|
-
) {
|
|
1226
|
-
continue;
|
|
1227
|
-
}
|
|
1228
|
-
const distance = Math.abs(row - targetRow);
|
|
1229
|
-
if (distance < bestDistance) {
|
|
1230
|
-
best = row;
|
|
1231
|
-
bestDistance = distance;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
return best;
|
|
1355
|
+
return lines;
|
|
1235
1356
|
}
|
|
1236
1357
|
|
|
1237
1358
|
#appendAnnotationCallout(
|
|
@@ -1404,6 +1525,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
1404
1525
|
const sig = [
|
|
1405
1526
|
this.#focus,
|
|
1406
1527
|
this.#tocCursor,
|
|
1528
|
+
this.#jump?.n ?? 0,
|
|
1407
1529
|
this.#selectedIndex,
|
|
1408
1530
|
this.#sliderIndex,
|
|
1409
1531
|
this.#committed ? `committed:${this.#committedLabel ?? ""}` : "",
|
|
@@ -1419,6 +1541,13 @@ export class PlanReviewOverlay implements Component {
|
|
|
1419
1541
|
const children: NativeChild[] = [];
|
|
1420
1542
|
const tools = this.#describeTools();
|
|
1421
1543
|
if (tools) children.push(tools);
|
|
1544
|
+
// The section a Contents jump targets scrolls to the body's top.
|
|
1545
|
+
const jump = this.#jump;
|
|
1546
|
+
const body = jump
|
|
1547
|
+
? content.body.map((section): NativeNode =>
|
|
1548
|
+
section.key === `s${jump.section}` ? { ...section, reveal: { at: "start", n: jump.n } } : section,
|
|
1549
|
+
)
|
|
1550
|
+
: content.body;
|
|
1422
1551
|
const bodyCol = node(
|
|
1423
1552
|
"col",
|
|
1424
1553
|
{ role: "zeta.plan.body", grow: 1, gap: "md", tone: this.#focus === "body" ? "accent" : undefined },
|
|
@@ -1478,7 +1607,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
1478
1607
|
return this.#nativeRoot;
|
|
1479
1608
|
}
|
|
1480
1609
|
|
|
1481
|
-
/** Head tools: Copy `c
|
|
1610
|
+
/** Head tools: Copy `c`, the external editor and Cancel, mirroring their keys. */
|
|
1482
1611
|
#describeTools(): NativeNode | undefined {
|
|
1483
1612
|
if (this.#committed) return undefined;
|
|
1484
1613
|
const buttons: NativeNode[] = [];
|
|
@@ -1489,6 +1618,12 @@ export class PlanReviewOverlay implements Component {
|
|
|
1489
1618
|
if (this.callbacks.onExternalEditor) {
|
|
1490
1619
|
buttons.push(actionButton("Edit in $EDITOR", "externalEditor", editorKeyId ? { keys: editorKeyId } : {}));
|
|
1491
1620
|
}
|
|
1621
|
+
// Cancel the review the way the cancel key does; while annotating that key
|
|
1622
|
+
// only leaves the draft/chooser, so the button is hidden there.
|
|
1623
|
+
if (!this.#annotating && !this.#annotationChooser) {
|
|
1624
|
+
const cancelKeyId = getKeybindings().getKeys("tui.select.cancel")[0];
|
|
1625
|
+
buttons.push(actionButton("Cancel", "cancel", cancelKeyId ? { keys: cancelKeyId } : {}));
|
|
1626
|
+
}
|
|
1492
1627
|
if (buttons.length === 0) return undefined;
|
|
1493
1628
|
return node("row", { role: "zeta.plan.tools", gap: "sm", align: "center", justify: "end" }, buttons, "tools");
|
|
1494
1629
|
}
|
|
@@ -1496,12 +1631,15 @@ export class PlanReviewOverlay implements Component {
|
|
|
1496
1631
|
handleNativeEvent(event: NativeUiEvent): void {
|
|
1497
1632
|
if (this.#committed) return;
|
|
1498
1633
|
if (event.type === "action") {
|
|
1499
|
-
// Head tools run exactly what `c
|
|
1634
|
+
// Head tools run exactly what `c`, the external-editor key and Esc run.
|
|
1500
1635
|
if (this.#annotating || this.#annotationChooser) return;
|
|
1501
1636
|
if (event.act === "copyPlan" && this.callbacks.onCopyPlan) {
|
|
1502
1637
|
void this.callbacks.onCopyPlan(joinPlanSections(this.#sections));
|
|
1503
1638
|
} else if (event.act === "externalEditor") {
|
|
1504
1639
|
this.callbacks.onExternalEditor?.();
|
|
1640
|
+
} else if (event.act === "cancel") {
|
|
1641
|
+
this.#committed = true;
|
|
1642
|
+
this.callbacks.onCancel();
|
|
1505
1643
|
}
|
|
1506
1644
|
return;
|
|
1507
1645
|
}
|
|
@@ -1672,8 +1810,8 @@ export class PlanReviewOverlay implements Component {
|
|
|
1672
1810
|
const hints: (NativeHint | undefined)[] = [];
|
|
1673
1811
|
switch (this.#focus) {
|
|
1674
1812
|
case "actions":
|
|
1675
|
-
hints.push(
|
|
1676
|
-
if (this.#slider) hints.push(key(["left", "right"], "model"));
|
|
1813
|
+
hints.push(key(["left", "right"], "select"), key("enter", "confirm"));
|
|
1814
|
+
if (this.#slider) hints.push(key(["shift+left", "shift+right"], "model"));
|
|
1677
1815
|
break;
|
|
1678
1816
|
case "toc":
|
|
1679
1817
|
hints.push(
|
|
@@ -378,7 +378,7 @@ export class PluginListComponent extends OverlayPanel {
|
|
|
378
378
|
lead:
|
|
379
379
|
rows.length > 0
|
|
380
380
|
? "Plugins installed for you and this project. Configure one to turn it or its features on and off."
|
|
381
|
-
: "No plugins installed. Install one with
|
|
381
|
+
: "No plugins installed. Install one with zetacode plugin install <package>, or <name>@<marketplace>.",
|
|
382
382
|
sections: rows.length > 0 ? [{ id: "installed", title: "Installed", rows }] : [],
|
|
383
383
|
focus: this.#selectList.getSelectedItem()?.value ?? null,
|
|
384
384
|
editing: null,
|
|
@@ -403,10 +403,10 @@ export class PluginListComponent extends OverlayPanel {
|
|
|
403
403
|
text([span("No plugins installed", "muted")]),
|
|
404
404
|
node("kv", {
|
|
405
405
|
items: [
|
|
406
|
-
{ k: "Install npm plugins", v: [span("
|
|
406
|
+
{ k: "Install npm plugins", v: [span("zetacode plugin install <package>", "code")] },
|
|
407
407
|
{
|
|
408
408
|
k: "Install marketplace plugins",
|
|
409
|
-
v: [span("
|
|
409
|
+
v: [span("zetacode plugin install <name>@<marketplace>", "code")],
|
|
410
410
|
},
|
|
411
411
|
],
|
|
412
412
|
}),
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { TspScrollBy, TspText } from "@linxiraos/pi-wire";
|
|
2
|
+
import { OverlayPanel } from "../chrome/overlay-box";
|
|
3
|
+
import { editorKey } from "../chrome/keybinding-hints";
|
|
4
|
+
import { Ellipsis } from "../index";
|
|
5
|
+
import { ScrollView } from "../components/scroll-view";
|
|
6
|
+
import { Spacer } from "../components/spacer";
|
|
7
|
+
import { Text } from "../components/text";
|
|
8
|
+
import {
|
|
9
|
+
matchesSelectCancel,
|
|
10
|
+
matchesSelectDown,
|
|
11
|
+
matchesSelectPageDown,
|
|
12
|
+
matchesSelectPageUp,
|
|
13
|
+
matchesSelectUp,
|
|
14
|
+
} from "../keybinding-matchers";
|
|
15
|
+
import { matchesKey } from "../keys";
|
|
16
|
+
import { routeSgrMouseInput } from "../mouse";
|
|
17
|
+
import { actionBar, actionButton } from "../native/overlay";
|
|
18
|
+
import { col } from "../native/describe";
|
|
19
|
+
import type { NativeNode, NativeScroll, NativeUiEvent } from "../native/node";
|
|
20
|
+
import { theme } from "../theme/theme";
|
|
21
|
+
import type { Component } from "../tui";
|
|
22
|
+
|
|
23
|
+
/** Options of a {@link ReportPanel}. */
|
|
24
|
+
export interface ReportPanelOptions {
|
|
25
|
+
/** Plain title: the text-mode border title, and the native sheet head unless {@link head} is given. */
|
|
26
|
+
readonly title: string;
|
|
27
|
+
/** Styled native sheet head. */
|
|
28
|
+
readonly head?: TspText;
|
|
29
|
+
/** The read-only report; it draws no frame or title of its own. */
|
|
30
|
+
readonly body: Component;
|
|
31
|
+
/**
|
|
32
|
+
* Display form of the key that dismisses the report while it sits above the
|
|
33
|
+
* editor (the editor's interrupt key, which routes to it). Once the panel
|
|
34
|
+
* holds focus ({@link ReportPanel.holdFocus}) the hint shows the key its own
|
|
35
|
+
* input closes on instead.
|
|
36
|
+
*/
|
|
37
|
+
readonly closeKey: string;
|
|
38
|
+
/** The focused panel (native sheet, text-mode full-screen page) closes on Esc / its Close button: this runs. */
|
|
39
|
+
readonly onClose: () => void;
|
|
40
|
+
/**
|
|
41
|
+
* Most rows the text-mode panel may take, borders included (the screen rows
|
|
42
|
+
* left above the editor); a taller report scrolls inside it. `undefined`
|
|
43
|
+
* leaves it uncapped.
|
|
44
|
+
*/
|
|
45
|
+
readonly maxRows?: () => number | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Text-mode rows around the scrolled body: the borders, two spacers and the footer. */
|
|
49
|
+
const TEXT_CHROME_ROWS = 5;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A read-only command report (`/changelog`, `/context`, `/tools`, …).
|
|
53
|
+
*
|
|
54
|
+
* Text mode draws a titled box with the body and an Esc hint: above the editor
|
|
55
|
+
* like the `/btw` panel while it fits there (the editor keeps focus and its
|
|
56
|
+
* Esc takes it away), or as a full-screen page on the alternate screen when it
|
|
57
|
+
* is taller, whose body scrolls on the arrow/page/Home/End keys and the wheel.
|
|
58
|
+
* Natively it is a sheet like `/usage`: a centred `lg` glass sheet titled by
|
|
59
|
+
* the report whose body the terminal scrolls (wheel, and the same keys
|
|
60
|
+
* forwarded as scroll requests), then a Close button; Esc closes it.
|
|
61
|
+
*/
|
|
62
|
+
export class ReportPanel extends OverlayPanel {
|
|
63
|
+
readonly nativeOverlay: NonNullable<Component["nativeOverlay"]>;
|
|
64
|
+
readonly #body: Component;
|
|
65
|
+
readonly #onClose: () => void;
|
|
66
|
+
readonly #maxRows: (() => number | undefined) | undefined;
|
|
67
|
+
readonly #view: ScrollView;
|
|
68
|
+
readonly #footer: Text;
|
|
69
|
+
#closeHint: string;
|
|
70
|
+
#bodyRows = 0;
|
|
71
|
+
#scroll: NativeScroll | undefined;
|
|
72
|
+
#native: { scroll: NativeScroll | undefined; node: NativeNode } | undefined;
|
|
73
|
+
|
|
74
|
+
constructor(options: ReportPanelOptions) {
|
|
75
|
+
super(options.title, "zeta.overlay.report");
|
|
76
|
+
this.nativeOverlay = {
|
|
77
|
+
role: "zeta.overlay.report",
|
|
78
|
+
size: "lg",
|
|
79
|
+
anchor: "center",
|
|
80
|
+
head: options.head ?? options.title,
|
|
81
|
+
};
|
|
82
|
+
this.#body = options.body;
|
|
83
|
+
this.#onClose = options.onClose;
|
|
84
|
+
this.#maxRows = options.maxRows;
|
|
85
|
+
this.#closeHint = `${options.closeKey} to close`;
|
|
86
|
+
this.#view = new ScrollView(options.body, {
|
|
87
|
+
height: 0,
|
|
88
|
+
scrollbar: "auto",
|
|
89
|
+
ellipsis: Ellipsis.Omit,
|
|
90
|
+
theme: { track: text => theme.fg("dim", text), thumb: text => theme.fg("accent", text) },
|
|
91
|
+
});
|
|
92
|
+
this.#footer = new Text(theme.fg("muted", this.#closeHint), 0, 0);
|
|
93
|
+
this.addChild(new Spacer(1));
|
|
94
|
+
this.addChild(this.#view);
|
|
95
|
+
this.addChild(new Spacer(1));
|
|
96
|
+
this.addChild(this.#footer);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The panel is shown with focus (native sheet, text-mode full-screen page):
|
|
101
|
+
* its own {@link handleInput} closes it, so the hint names that key.
|
|
102
|
+
*/
|
|
103
|
+
holdFocus(): void {
|
|
104
|
+
this.#closeHint = `${editorKey("tui.select.cancel")} to close`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Text mode: cap the box to {@link ReportPanelOptions.maxRows}, scrolling a
|
|
109
|
+
* taller body. The cap is a hard limit: when a resize leaves fewer rows than
|
|
110
|
+
* the box's chrome needs, its bottom (the hint and closing border) is cut
|
|
111
|
+
* rather than spilling over the editor.
|
|
112
|
+
*/
|
|
113
|
+
override render(width: number): readonly string[] {
|
|
114
|
+
const innerWidth = Math.max(1, width - 4);
|
|
115
|
+
const rows = this.#body.render(innerWidth).length;
|
|
116
|
+
const cap = this.#maxRows?.();
|
|
117
|
+
const bodyMax = cap === undefined ? rows : Math.max(1, cap - TEXT_CHROME_ROWS);
|
|
118
|
+
const overflows = rows > bodyMax;
|
|
119
|
+
this.#bodyRows = Math.min(rows, bodyMax);
|
|
120
|
+
this.#view.setHeight(this.#bodyRows);
|
|
121
|
+
const scrollHint = `${editorKey("tui.select.pageUp")}/${editorKey("tui.select.pageDown")} scroll · `;
|
|
122
|
+
this.#footer.setText(theme.fg("muted", overflows ? scrollHint + this.#closeHint : this.#closeHint));
|
|
123
|
+
const lines = super.render(width);
|
|
124
|
+
return cap !== undefined && lines.length > cap ? lines.slice(0, Math.max(0, cap)) : lines;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Text mode: rows the whole box takes at `width` when nothing caps it. */
|
|
128
|
+
heightAt(width: number): number {
|
|
129
|
+
return this.#body.render(Math.max(1, width - 4)).length + TEXT_CHROME_ROWS;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The sheet body (the overlay is the frame, and its body the scroller): the report, then Close (Esc). */
|
|
133
|
+
override describe(): NativeNode {
|
|
134
|
+
const scroll = this.#scroll;
|
|
135
|
+
if (this.#native && this.#native.scroll === scroll) return this.#native.node;
|
|
136
|
+
const body: NativeNode = { ...col([this.#body]), key: "body", scroll };
|
|
137
|
+
const node = col([body, actionBar([null, actionButton("Close", "close", { keys: "escape" })])], { gap: "md" });
|
|
138
|
+
this.#native = { scroll, node };
|
|
139
|
+
return node;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
override invalidate(): void {
|
|
143
|
+
this.#native = undefined;
|
|
144
|
+
super.invalidate();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Focused (the native sheet, or the text-mode full-screen page): Esc closes;
|
|
149
|
+
* the arrow, page, Home and End keys and the wheel scroll the body.
|
|
150
|
+
*/
|
|
151
|
+
handleInput(data: string): void {
|
|
152
|
+
if (
|
|
153
|
+
routeSgrMouseInput(data, event => {
|
|
154
|
+
if (event.wheel === null) return false;
|
|
155
|
+
this.#view.scroll(event.wheel * 3);
|
|
156
|
+
return true;
|
|
157
|
+
})
|
|
158
|
+
) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (matchesSelectCancel(data)) {
|
|
162
|
+
this.#onClose();
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const by: TspScrollBy | undefined = matchesSelectUp(data)
|
|
166
|
+
? "line-up"
|
|
167
|
+
: matchesSelectDown(data)
|
|
168
|
+
? "line-down"
|
|
169
|
+
: matchesSelectPageUp(data)
|
|
170
|
+
? "page-up"
|
|
171
|
+
: matchesSelectPageDown(data)
|
|
172
|
+
? "page-down"
|
|
173
|
+
: matchesKey(data, "home")
|
|
174
|
+
? "start"
|
|
175
|
+
: matchesKey(data, "end")
|
|
176
|
+
? "end"
|
|
177
|
+
: undefined;
|
|
178
|
+
if (by === undefined) return;
|
|
179
|
+
// Natively the terminal scrolls the sheet body; text mode scrolls the box.
|
|
180
|
+
this.#scroll = { by, n: (this.#scroll?.n ?? 0) + 1 };
|
|
181
|
+
const page = Math.max(1, this.#bodyRows - 1);
|
|
182
|
+
if (by === "start") this.#view.scrollToTop();
|
|
183
|
+
else if (by === "end") this.#view.scrollToBottom();
|
|
184
|
+
else this.#view.scroll(by === "line-up" ? -1 : by === "line-down" ? 1 : by === "page-up" ? -page : page);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Close runs what Esc runs. */
|
|
188
|
+
handleNativeEvent(event: NativeUiEvent): void {
|
|
189
|
+
if (event.type === "action" && event.act === "close") this.#onClose();
|
|
190
|
+
}
|
|
191
|
+
}
|