@oh-my-pi/pi-tui 18.2.4 → 18.2.6
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 +23 -0
- package/README.md +43 -0
- package/dist/types/app-keybindings.d.ts +395 -0
- package/dist/types/apps/autoresearch-dashboard.d.ts +43 -0
- package/dist/types/apps/autoresearch-data.d.ts +15 -0
- package/dist/types/apps/cleanse-board.d.ts +66 -0
- package/dist/types/apps/cleanse-picker.d.ts +23 -0
- package/dist/types/apps/debug/log-formatting.d.ts +6 -0
- package/dist/types/apps/debug/log-viewer.d.ts +81 -0
- package/dist/types/apps/debug/protocol-probe.d.ts +42 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +58 -0
- package/dist/types/apps/debug/raw-sse.d.ts +26 -0
- package/dist/types/apps/debug/terminal-info.d.ts +34 -0
- package/dist/types/apps/debug/viewer-frame.d.ts +52 -0
- package/dist/types/apps/git/avatar.d.ts +9 -0
- package/dist/types/apps/git/colors.d.ts +49 -0
- package/dist/types/apps/git/diff-pane.d.ts +188 -0
- package/dist/types/apps/git/git-tui.d.ts +66 -0
- package/dist/types/apps/git/sidebar.d.ts +149 -0
- package/dist/types/apps/git/state.d.ts +117 -0
- package/dist/types/apps/if-bench-board.d.ts +75 -0
- package/dist/types/apps/live-visualizer.d.ts +33 -0
- package/dist/types/apps/ps-data.d.ts +51 -0
- package/dist/types/apps/ps-top.d.ts +33 -0
- package/dist/types/apps/session-picker.d.ts +26 -0
- package/dist/types/apps/setup-model-picker.d.ts +13 -0
- package/dist/types/apps/standalone-picker.d.ts +42 -0
- package/dist/types/autocomplete.d.ts +4 -0
- package/dist/types/chat/advisor-message.d.ts +11 -0
- package/dist/types/chat/assistant-message.d.ts +106 -0
- package/dist/types/chat/background-tan-message.d.ts +9 -0
- package/dist/types/chat/bash-execution.d.ts +47 -0
- package/dist/types/chat/cache-invalidation-marker.d.ts +50 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +50 -0
- package/dist/types/chat/collab-prompt-message.d.ts +10 -0
- package/dist/types/chat/compaction-summary-message.d.ts +54 -0
- package/dist/types/chat/custom-message.d.ts +10 -0
- package/dist/types/chat/display-preferences.d.ts +13 -0
- package/dist/types/chat/eval-execution.d.ts +29 -0
- package/dist/types/chat/execution-shared.d.ts +51 -0
- package/dist/types/chat/extension-types.d.ts +28 -0
- package/dist/types/chat/hook-message.d.ts +10 -0
- package/dist/types/chat/image-loading.d.ts +46 -0
- package/dist/types/chat/late-diagnostics-message.d.ts +21 -0
- package/dist/types/chat/messages.d.ts +189 -0
- package/dist/types/chat/reaction.d.ts +30 -0
- package/dist/types/chat/read-target.d.ts +4 -0
- package/dist/types/chat/read-tool-group.d.ts +67 -0
- package/dist/types/chat/served-model-marker.d.ts +47 -0
- package/dist/types/chat/skill-message.d.ts +33 -0
- package/dist/types/chat/skill-title-input.d.ts +14 -0
- package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +12 -0
- package/dist/types/chat/thinking-display.d.ts +12 -0
- package/dist/types/chat/todo-reminder.d.ts +13 -0
- package/dist/types/chat/tool-execution.d.ts +138 -0
- package/dist/types/chat/transcript-browser.d.ts +93 -0
- package/dist/types/chat/transcript-entry.d.ts +45 -0
- package/dist/types/chat/transcript-outline.d.ts +86 -0
- package/dist/types/chat/transcript-render-helpers.d.ts +92 -0
- package/dist/types/chat/ttsr-notification.d.ts +23 -0
- package/dist/types/chat/user-message.d.ts +52 -0
- package/dist/types/chrome/chat-block.d.ts +64 -0
- package/dist/types/chrome/collab-qrcode.d.ts +28 -0
- package/dist/types/chrome/context-thresholds.d.ts +10 -0
- package/dist/types/chrome/countdown-timer.d.ts +14 -0
- package/dist/types/chrome/diff.d.ts +14 -0
- package/dist/types/chrome/dynamic-border.d.ts +16 -0
- package/dist/types/chrome/error-block.d.ts +17 -0
- package/dist/types/chrome/form-theme.d.ts +3 -0
- package/dist/types/chrome/format.d.ts +12 -0
- package/dist/types/chrome/index.d.ts +20 -0
- package/dist/types/chrome/keybinding-hints.d.ts +40 -0
- package/dist/types/chrome/live-board.d.ts +23 -0
- package/dist/types/chrome/local-date.d.ts +4 -0
- package/dist/types/chrome/message-divider.d.ts +22 -0
- package/dist/types/chrome/message-frame.d.ts +52 -0
- package/dist/types/chrome/message-notice.d.ts +35 -0
- package/dist/types/chrome/overlay-box.d.ts +74 -0
- package/dist/types/chrome/qrcode.d.ts +48 -0
- package/dist/types/chrome/segment-track.d.ts +27 -0
- package/dist/types/chrome/select-list-mouse-routing.d.ts +4 -0
- package/dist/types/chrome/selector-helpers.d.ts +52 -0
- package/dist/types/chrome/shared.d.ts +5 -0
- package/dist/types/chrome/streaming-panel.d.ts +19 -0
- package/dist/types/chrome/tool-activity.d.ts +17 -0
- package/dist/types/chrome/transcript-container.d.ts +129 -0
- package/dist/types/chrome/transcript-status.d.ts +12 -0
- package/dist/types/chrome/visual-truncate.d.ts +19 -0
- package/dist/types/components/disclosure.d.ts +42 -0
- package/dist/types/components/form.d.ts +130 -0
- package/dist/types/components/key-value-list.d.ts +24 -0
- package/dist/types/components/layout/geometry.d.ts +73 -0
- package/dist/types/components/layout/row.d.ts +55 -0
- package/dist/types/components/layout/split-pane.d.ts +80 -0
- package/dist/types/components/layout/stack.d.ts +50 -0
- package/dist/types/components/menu-selection.d.ts +71 -0
- package/dist/types/components/metric.d.ts +44 -0
- package/dist/types/components/progress-bar.d.ts +36 -0
- package/dist/types/components/scroll-view.d.ts +53 -19
- package/dist/types/components/scroll-viewport.d.ts +46 -0
- package/dist/types/components/section.d.ts +19 -0
- package/dist/types/components/select-list.d.ts +46 -2
- package/dist/types/components/settings-list.d.ts +20 -0
- package/dist/types/components/table.d.ts +32 -0
- package/dist/types/components/tree-view.d.ts +130 -0
- package/dist/types/components/wizard-step.d.ts +60 -0
- package/dist/types/hotkeys-markdown.d.ts +7 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/keybinding-matchers.d.ts +35 -0
- package/dist/types/keys.d.ts +2 -2
- package/dist/types/lang-from-path.d.ts +9 -0
- package/dist/types/overlays/advisor-config.d.ts +114 -0
- package/dist/types/overlays/agent-activity.d.ts +41 -0
- package/dist/types/overlays/agent-hub-projection.d.ts +33 -0
- package/dist/types/overlays/agent-hub-renderer.d.ts +53 -0
- package/dist/types/overlays/agent-hub-types.d.ts +99 -0
- package/dist/types/overlays/agent-hub.d.ts +126 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +71 -0
- package/dist/types/overlays/agents-hub.d.ts +72 -0
- package/dist/types/overlays/ask-dialog.d.ts +88 -0
- package/dist/types/overlays/bordered-loader.d.ts +11 -0
- package/dist/types/overlays/btw-history-panel.d.ts +29 -0
- package/dist/types/overlays/btw-history.d.ts +17 -0
- package/dist/types/overlays/btw-panel.d.ts +26 -0
- package/dist/types/overlays/cleanse-panel.d.ts +36 -0
- package/dist/types/overlays/codex-reset-fireworks.d.ts +52 -0
- package/dist/types/overlays/composer-shape-preview.d.ts +47 -0
- package/dist/types/overlays/composer-shape-registry.d.ts +50 -0
- package/dist/types/overlays/copy-selector.d.ts +48 -0
- package/dist/types/overlays/copy-targets.d.ts +63 -0
- package/dist/types/overlays/error-banner.d.ts +13 -0
- package/dist/types/overlays/extensions/display-text.d.ts +10 -0
- package/dist/types/overlays/extensions/extension-dashboard.d.ts +63 -0
- package/dist/types/overlays/extensions/extension-list.d.ts +54 -0
- package/dist/types/overlays/extensions/inspector-model.d.ts +109 -0
- package/dist/types/overlays/extensions/inspector-panel.d.ts +17 -0
- package/dist/types/overlays/extensions/live-tool-session.d.ts +28 -0
- package/dist/types/overlays/extensions/mcp-runtime.d.ts +107 -0
- package/dist/types/overlays/extensions/state-manager.d.ts +42 -0
- package/dist/types/overlays/extensions/types.d.ts +165 -0
- package/dist/types/overlays/history-search.d.ts +16 -0
- package/dist/types/overlays/hook-editor.d.ts +44 -0
- package/dist/types/overlays/hook-input.d.ts +20 -0
- package/dist/types/overlays/hook-selector.d.ts +71 -0
- package/dist/types/overlays/hub-frame.d.ts +77 -0
- package/dist/types/overlays/login-dialog.d.ts +43 -0
- package/dist/types/overlays/logout-account-selector.d.ts +15 -0
- package/dist/types/overlays/mcp-add-wizard.d.ts +87 -0
- package/dist/types/overlays/model-browser.d.ts +213 -0
- package/dist/types/overlays/model-hub.d.ts +72 -0
- package/dist/types/overlays/model-picker.d.ts +74 -0
- package/dist/types/overlays/model-selector.d.ts +52 -0
- package/dist/types/overlays/move-overlay.d.ts +39 -0
- package/dist/types/overlays/oauth-selector.d.ts +41 -0
- package/dist/types/overlays/omfg-panel.d.ts +20 -0
- package/dist/types/overlays/pause-screen.d.ts +43 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +89 -0
- package/dist/types/overlays/plan-save-overlay.d.ts +19 -0
- package/dist/types/overlays/plan-toc.d.ts +41 -0
- package/dist/types/overlays/plugin-selector.d.ts +28 -0
- package/dist/types/overlays/plugin-settings.d.ts +180 -0
- package/dist/types/overlays/queue-mode-selector.d.ts +11 -0
- package/dist/types/overlays/reset-usage-selector.d.ts +23 -0
- package/dist/types/overlays/rewind-selector.d.ts +61 -0
- package/dist/types/overlays/running-subagent-badge.d.ts +6 -0
- package/dist/types/overlays/session-account-selector.d.ts +24 -0
- package/dist/types/overlays/session-info-overlay.d.ts +17 -0
- package/dist/types/overlays/session-observer-registry.d.ts +77 -0
- package/dist/types/overlays/session-selector.d.ts +131 -0
- package/dist/types/overlays/settings-defs.d.ts +119 -0
- package/dist/types/overlays/settings-selector.d.ts +73 -0
- package/dist/types/overlays/show-images-selector.d.ts +11 -0
- package/dist/types/overlays/snapcompact-shape-preview.d.ts +31 -0
- package/dist/types/overlays/theme-selector.d.ts +12 -0
- package/dist/types/overlays/thinking-selector.d.ts +12 -0
- package/dist/types/overlays/tiny-title-download-progress.d.ts +21 -0
- package/dist/types/overlays/tree-selector.d.ts +92 -0
- package/dist/types/overlays/usage-dashboard.d.ts +94 -0
- package/dist/types/overlays/usage-display.d.ts +5 -0
- package/dist/types/overlays/usage-row.d.ts +17 -0
- package/dist/types/prompt/attachment-chips.d.ts +19 -0
- package/dist/types/prompt/composer-attachments.d.ts +71 -0
- package/dist/types/prompt/composer-cache.d.ts +37 -0
- package/dist/types/prompt/composer.d.ts +159 -0
- package/dist/types/prompt/custom-editor.d.ts +261 -0
- package/dist/types/prompt/editor-top-gap.d.ts +18 -0
- package/dist/types/prompt/emoji-autocomplete.d.ts +16 -0
- package/dist/types/prompt/github-ref-autocomplete.d.ts +35 -0
- package/dist/types/prompt/gradient-highlight.d.ts +32 -0
- package/dist/types/prompt/image-format.d.ts +4 -0
- package/dist/types/prompt/image-references.d.ts +26 -0
- package/dist/types/prompt/interactive-context-helpers.d.ts +43 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +56 -0
- package/dist/types/prompt/macos-spelling.d.ts +44 -0
- package/dist/types/prompt/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/prompt/magic-keywords.d.ts +26 -0
- package/dist/types/prompt/markdown-prose.d.ts +15 -0
- package/dist/types/prompt/model-mention-autocomplete.d.ts +25 -0
- package/dist/types/prompt/model-mention-syntax.d.ts +24 -0
- package/dist/types/prompt/orchestrate.d.ts +13 -0
- package/dist/types/prompt/prompt-action-autocomplete.d.ts +56 -0
- package/dist/types/prompt/queue-input.d.ts +8 -0
- package/dist/types/prompt/skill-tokens.d.ts +13 -0
- package/dist/types/prompt/tools-markdown.d.ts +12 -0
- package/dist/types/prompt/ultrathink.d.ts +13 -0
- package/dist/types/prompt/usage-amounts.d.ts +26 -0
- package/dist/types/prompt/video.d.ts +18 -0
- package/dist/types/prompt/welcome.d.ts +83 -0
- package/dist/types/prompt/workflow.d.ts +13 -0
- package/dist/types/render/code-cell.d.ts +51 -0
- package/dist/types/render/file-list.d.ts +22 -0
- package/dist/types/render/hyperlink.d.ts +80 -0
- package/dist/types/render/index.d.ts +16 -0
- package/dist/types/render/output-block.d.ts +54 -0
- package/dist/types/render/output-pane.d.ts +80 -0
- package/dist/types/render/render-utils.d.ts +312 -0
- package/dist/types/render/sixel.d.ts +21 -0
- package/dist/types/render/status-line.d.ts +23 -0
- package/dist/types/render/tool-card.d.ts +71 -0
- package/dist/types/render/tree-list.d.ts +29 -0
- package/dist/types/render/types.d.ts +17 -0
- package/dist/types/render/utils.d.ts +43 -0
- package/dist/types/render/width-aware-text.d.ts +23 -0
- package/dist/types/setup/lazy.d.ts +3 -0
- package/dist/types/setup/scenes/composer.d.ts +3 -0
- package/dist/types/setup/scenes/glyph.d.ts +3 -0
- package/dist/types/setup/scenes/model.d.ts +3 -0
- package/dist/types/setup/scenes/outro.d.ts +2 -0
- package/dist/types/setup/scenes/providers.d.ts +3 -0
- package/dist/types/setup/scenes/sign-in.d.ts +21 -0
- package/dist/types/setup/scenes/splash.d.ts +11 -0
- package/dist/types/setup/scenes/theme.d.ts +3 -0
- package/dist/types/setup/scenes/types.d.ts +110 -0
- package/dist/types/setup/scenes/web-search.d.ts +22 -0
- package/dist/types/setup/setup-version.d.ts +11 -0
- package/dist/types/setup/startup-splash.d.ts +9 -0
- package/dist/types/setup/wizard-overlay.d.ts +16 -0
- package/dist/types/setup/wizard.d.ts +25 -0
- package/dist/types/status-line/component.d.ts +182 -0
- package/dist/types/status-line/context-usage.d.ts +156 -0
- package/dist/types/status-line/footer.d.ts +30 -0
- package/dist/types/status-line/git-utils.d.ts +22 -0
- package/dist/types/status-line/host.d.ts +129 -0
- package/dist/types/status-line/index.d.ts +6 -0
- package/dist/types/status-line/loop.d.ts +18 -0
- package/dist/types/status-line/metrics.d.ts +21 -0
- package/dist/types/status-line/presets.d.ts +3 -0
- package/dist/types/status-line/schema.d.ts +15 -0
- package/dist/types/status-line/segments.d.ts +5 -0
- package/dist/types/status-line/separators.d.ts +3 -0
- package/dist/types/status-line/types.d.ts +236 -0
- package/dist/types/stdin-buffer.d.ts +4 -0
- package/dist/types/theme/color.d.ts +24 -0
- package/dist/types/theme/defaults/index.d.ts +9489 -0
- package/dist/types/theme/index.d.ts +4 -0
- package/dist/types/theme/loader.d.ts +23 -0
- package/dist/types/theme/mermaid-cache.d.ts +26 -0
- package/dist/types/theme/schema-validation.d.ts +3 -0
- package/dist/types/theme/schema.d.ts +25 -0
- package/dist/types/theme/session-color.d.ts +43 -0
- package/dist/types/theme/shimmer.d.ts +44 -0
- package/dist/types/theme/symbols.d.ts +34 -0
- package/dist/types/theme/theme-class.d.ts +299 -0
- package/dist/types/theme/theme.d.ts +111 -0
- package/dist/types/theme/tui-adapters.d.ts +30 -0
- package/dist/types/thinking.d.ts +165 -0
- package/dist/types/tools/agent-tree.d.ts +36 -0
- package/dist/types/tools/ask.d.ts +67 -0
- package/dist/types/tools/ast-edit.d.ts +53 -0
- package/dist/types/tools/ast-grep.d.ts +52 -0
- package/dist/types/tools/autoresearch.d.ts +156 -0
- package/dist/types/tools/bash-interactive.d.ts +28 -0
- package/dist/types/tools/bash.d.ts +103 -0
- package/dist/types/tools/conflict-detect.d.ts +43 -0
- package/dist/types/tools/debug.d.ts +60 -0
- package/dist/types/tools/default-renderer.d.ts +24 -0
- package/dist/types/tools/edit.d.ts +158 -0
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +5 -0
- package/dist/types/tools/eval-format/index.d.ts +5 -0
- package/dist/types/tools/eval-format/javascript.d.ts +2 -0
- package/dist/types/tools/eval-format/python.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +94 -0
- package/dist/types/tools/fetch.d.ts +42 -0
- package/dist/types/tools/gh-format.d.ts +8 -0
- package/dist/types/tools/github.d.ts +144 -0
- package/dist/types/tools/glob.d.ts +44 -0
- package/dist/types/tools/goal.d.ts +42 -0
- package/dist/types/tools/grep.d.ts +64 -0
- package/dist/types/tools/grouped-file-output.d.ts +71 -0
- package/dist/types/tools/hashline-format.d.ts +28 -0
- package/dist/types/tools/hub.d.ts +340 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/json-tree.d.ts +59 -0
- package/dist/types/tools/line-ranges.d.ts +29 -0
- package/dist/types/tools/list-limit.d.ts +15 -0
- package/dist/types/tools/lsp.d.ts +72 -0
- package/dist/types/tools/match-line-format.d.ts +11 -0
- package/dist/types/tools/mcp.d.ts +81 -0
- package/dist/types/tools/memory.d.ts +67 -0
- package/dist/types/tools/output-meta.d.ts +151 -0
- package/dist/types/tools/read.d.ts +139 -0
- package/dist/types/tools/renderer.d.ts +98 -0
- package/dist/types/tools/report-tool-issue.d.ts +8 -0
- package/dist/types/tools/resolve.d.ts +47 -0
- package/dist/types/tools/streaming-output.d.ts +282 -0
- package/dist/types/tools/subprocess.d.ts +24 -0
- package/dist/types/tools/task-repair-args.d.ts +53 -0
- package/dist/types/tools/task-yield-assembly.d.ts +20 -0
- package/dist/types/tools/task.d.ts +425 -0
- package/dist/types/tools/terminal-output.d.ts +8 -0
- package/dist/types/tools/think.d.ts +13 -0
- package/dist/types/tools/todo.d.ts +118 -0
- package/dist/types/tools/tool-errors.d.ts +7 -0
- package/dist/types/tools/vibe.d.ts +120 -0
- package/dist/types/tools/web-search.d.ts +195 -0
- package/dist/types/tools/write.d.ts +76 -0
- package/dist/types/tools/xd-url.d.ts +13 -0
- package/dist/types/tools/xdev.d.ts +52 -0
- package/dist/types/tui.d.ts +18 -0
- package/dist/types/utils.d.ts +2 -0
- package/package.json +37 -3
- package/src/app-keybindings.ts +760 -0
- package/src/apps/autoresearch-dashboard.ts +500 -0
- package/src/apps/autoresearch-data.ts +64 -0
- package/src/apps/cleanse-board.ts +341 -0
- package/src/apps/cleanse-picker.ts +57 -0
- package/src/apps/debug/log-formatting.ts +53 -0
- package/src/apps/debug/log-viewer.ts +868 -0
- package/src/apps/debug/protocol-probe.ts +267 -0
- package/src/apps/debug/raw-sse-buffer.ts +449 -0
- package/src/apps/debug/raw-sse.ts +282 -0
- package/src/apps/debug/terminal-info.ts +115 -0
- package/src/apps/debug/viewer-frame.ts +174 -0
- package/src/apps/git/avatar.ts +52 -0
- package/src/apps/git/colors.ts +109 -0
- package/src/apps/git/diff-pane.ts +1512 -0
- package/src/apps/git/git-tui.ts +889 -0
- package/src/apps/git/sidebar.ts +1204 -0
- package/src/apps/git/state.ts +113 -0
- package/src/apps/if-bench-board.ts +324 -0
- package/src/apps/live-visualizer.ts +238 -0
- package/src/apps/ps-data.ts +116 -0
- package/src/apps/ps-top.ts +422 -0
- package/src/apps/session-picker.ts +95 -0
- package/src/apps/setup-model-picker.ts +21 -0
- package/src/apps/standalone-picker.ts +111 -0
- package/src/autocomplete.ts +9 -13
- package/src/chat/advisor-message.ts +175 -0
- package/src/chat/assistant-message.ts +1214 -0
- package/src/chat/background-tan-message.ts +35 -0
- package/src/chat/bash-execution.ts +394 -0
- package/src/chat/cache-invalidation-marker.ts +94 -0
- package/src/chat/chat-transcript-builder.ts +593 -0
- package/src/chat/collab-prompt-message.ts +34 -0
- package/src/chat/compaction-summary-message.ts +261 -0
- package/src/chat/custom-message.ts +23 -0
- package/src/chat/display-preferences.ts +24 -0
- package/src/chat/eval-execution.ts +172 -0
- package/src/chat/execution-shared.ts +113 -0
- package/src/chat/extension-types.ts +46 -0
- package/src/chat/hook-message.ts +20 -0
- package/src/chat/image-loading.ts +122 -0
- package/src/chat/late-diagnostics-message.ts +92 -0
- package/src/chat/messages.ts +305 -0
- package/src/chat/reaction.ts +65 -0
- package/src/chat/read-target.ts +7 -0
- package/src/chat/read-tool-group.ts +873 -0
- package/src/chat/served-model-marker.ts +91 -0
- package/src/chat/skill-message.ts +220 -0
- package/src/chat/skill-title-input.ts +41 -0
- package/src/chat/stripped-tool-calls-placeholder.ts +35 -0
- package/src/chat/thinking-display.ts +312 -0
- package/src/chat/todo-reminder.ts +33 -0
- package/src/chat/tool-execution.ts +1395 -0
- package/src/chat/transcript-browser.ts +268 -0
- package/src/chat/transcript-entry.ts +96 -0
- package/src/chat/transcript-outline.ts +260 -0
- package/src/chat/transcript-render-helpers.ts +315 -0
- package/src/chat/ttsr-notification.ts +123 -0
- package/src/chat/user-message.ts +287 -0
- package/src/chrome/chat-block.ts +110 -0
- package/src/chrome/collab-qrcode.ts +74 -0
- package/src/chrome/context-thresholds.ts +85 -0
- package/src/chrome/countdown-timer.ts +74 -0
- package/src/chrome/diff.ts +266 -0
- package/src/chrome/dynamic-border.ts +36 -0
- package/src/chrome/error-block.ts +63 -0
- package/src/chrome/form-theme.ts +10 -0
- package/src/chrome/format.ts +66 -0
- package/src/chrome/index.ts +21 -0
- package/src/chrome/keybinding-hints.ts +55 -0
- package/src/chrome/live-board.ts +123 -0
- package/src/chrome/local-date.ts +20 -0
- package/src/chrome/message-divider.ts +51 -0
- package/src/chrome/message-frame.ts +164 -0
- package/src/chrome/message-notice.ts +103 -0
- package/src/chrome/overlay-box.ts +301 -0
- package/src/chrome/qrcode.ts +539 -0
- package/src/chrome/segment-track.ts +86 -0
- package/src/chrome/select-list-mouse-routing.ts +11 -0
- package/src/chrome/selector-helpers.ts +119 -0
- package/src/chrome/shared.ts +27 -0
- package/src/chrome/streaming-panel.ts +72 -0
- package/src/chrome/tool-activity.ts +44 -0
- package/src/chrome/transcript-container.ts +902 -0
- package/src/chrome/transcript-status.ts +27 -0
- package/src/chrome/visual-truncate.ts +70 -0
- package/src/components/disclosure.ts +235 -0
- package/src/components/editor.ts +10 -12
- package/src/components/form.ts +566 -0
- package/src/components/input.ts +6 -27
- package/src/components/key-value-list.ts +88 -0
- package/src/components/layout/geometry.ts +202 -0
- package/src/components/layout/row.ts +283 -0
- package/src/components/layout/split-pane.ts +349 -0
- package/src/components/layout/stack.ts +261 -0
- package/src/components/markdown.ts +16 -12
- package/src/components/menu-selection.ts +248 -0
- package/src/components/metric.ts +132 -0
- package/src/components/progress-bar.ts +140 -0
- package/src/components/scroll-view.ts +372 -70
- package/src/components/scroll-viewport.ts +154 -0
- package/src/components/section.ts +77 -0
- package/src/components/select-list.ts +238 -159
- package/src/components/settings-list.ts +103 -80
- package/src/components/table.ts +127 -0
- package/src/components/tree-view.ts +481 -0
- package/src/components/wizard-step.ts +229 -0
- package/src/deccara.ts +1 -3
- package/src/hotkeys-markdown.ts +100 -0
- package/src/index.ts +18 -0
- package/src/keybinding-matchers.ts +99 -0
- package/src/keys.ts +2 -2
- package/src/lang-from-path.ts +252 -0
- package/src/overlays/advisor-config.ts +761 -0
- package/src/overlays/agent-activity.ts +113 -0
- package/src/overlays/agent-hub-projection.ts +250 -0
- package/src/overlays/agent-hub-renderer.ts +281 -0
- package/src/overlays/agent-hub-types.ts +74 -0
- package/src/overlays/agent-hub.ts +1562 -0
- package/src/overlays/agent-transcript-viewer.ts +669 -0
- package/src/overlays/agents-hub.ts +1203 -0
- package/src/overlays/ask-dialog.ts +1202 -0
- package/src/overlays/bordered-loader.ts +41 -0
- package/src/overlays/btw-history-panel.ts +591 -0
- package/src/overlays/btw-history.ts +33 -0
- package/src/overlays/btw-panel.ts +167 -0
- package/src/overlays/cleanse-panel.ts +167 -0
- package/src/overlays/codex-reset-fireworks.ts +375 -0
- package/src/overlays/composer-shape-preview.ts +137 -0
- package/src/overlays/composer-shape-registry.ts +82 -0
- package/src/overlays/copy-selector.ts +618 -0
- package/src/overlays/copy-targets.ts +210 -0
- package/src/overlays/error-banner.ts +39 -0
- package/src/overlays/extensions/display-text.ts +39 -0
- package/src/overlays/extensions/extension-dashboard.ts +645 -0
- package/src/overlays/extensions/extension-list.ts +667 -0
- package/src/overlays/extensions/inspector-model.ts +484 -0
- package/src/overlays/extensions/inspector-panel.ts +669 -0
- package/src/overlays/extensions/live-tool-session.ts +67 -0
- package/src/overlays/extensions/mcp-runtime.ts +289 -0
- package/src/overlays/extensions/state-manager.ts +337 -0
- package/src/overlays/extensions/types.ts +208 -0
- package/src/overlays/history-search.ts +268 -0
- package/src/overlays/hook-editor.ts +272 -0
- package/src/overlays/hook-input.ts +84 -0
- package/src/overlays/hook-selector.ts +635 -0
- package/src/overlays/hub-frame.ts +262 -0
- package/src/overlays/login-dialog.ts +231 -0
- package/src/overlays/logout-account-selector.ts +115 -0
- package/src/overlays/mcp-add-wizard.ts +1436 -0
- package/src/overlays/model-browser.ts +1176 -0
- package/src/overlays/model-hub.ts +2279 -0
- package/src/overlays/model-picker.ts +285 -0
- package/src/overlays/model-selector.ts +107 -0
- package/src/overlays/move-overlay.ts +197 -0
- package/src/overlays/oauth-selector.ts +446 -0
- package/src/overlays/omfg-panel.ts +136 -0
- package/src/overlays/pause-screen.ts +198 -0
- package/src/overlays/plan-review-overlay.ts +1227 -0
- package/src/overlays/plan-save-overlay.ts +74 -0
- package/src/overlays/plan-toc.ts +139 -0
- package/src/overlays/plugin-selector.ts +97 -0
- package/src/overlays/plugin-settings.ts +839 -0
- package/src/overlays/queue-mode-selector.ts +55 -0
- package/src/overlays/reset-usage-selector.ts +170 -0
- package/src/overlays/rewind-selector.ts +499 -0
- package/src/overlays/running-subagent-badge.ts +19 -0
- package/src/overlays/session-account-selector.ts +68 -0
- package/src/overlays/session-info-overlay.ts +96 -0
- package/src/overlays/session-observer-registry.ts +289 -0
- package/src/overlays/session-selector.ts +1137 -0
- package/src/overlays/settings-defs.ts +315 -0
- package/src/overlays/settings-selector.ts +1415 -0
- package/src/overlays/show-images-selector.ts +44 -0
- package/src/overlays/snapcompact-shape-preview-doc.md +14 -0
- package/src/overlays/snapcompact-shape-preview.ts +192 -0
- package/src/overlays/theme-selector.ts +62 -0
- package/src/overlays/thinking-selector.ts +51 -0
- package/src/overlays/tiny-title-download-progress.ts +102 -0
- package/src/overlays/tree-selector.ts +1121 -0
- package/src/overlays/usage-dashboard.ts +629 -0
- package/src/overlays/usage-display.ts +45 -0
- package/src/overlays/usage-row.ts +118 -0
- package/src/prompt/attachment-chips.ts +201 -0
- package/src/prompt/composer-attachments.ts +280 -0
- package/src/prompt/composer-cache.ts +304 -0
- package/src/prompt/composer.ts +934 -0
- package/src/prompt/custom-editor.ts +1392 -0
- package/src/prompt/data/emojis.json +1 -0
- package/src/prompt/editor-top-gap.ts +28 -0
- package/src/prompt/emoji-autocomplete.ts +285 -0
- package/src/prompt/github-ref-autocomplete.ts +75 -0
- package/src/prompt/gradient-highlight.ts +101 -0
- package/src/prompt/image-format.ts +28 -0
- package/src/prompt/image-references.ts +102 -0
- package/src/prompt/interactive-context-helpers.ts +126 -0
- package/src/prompt/internal-url-autocomplete.ts +155 -0
- package/src/prompt/macos-spelling.ts +456 -0
- package/src/prompt/magic-keyword-boundary.ts +23 -0
- package/src/prompt/magic-keywords.ts +42 -0
- package/src/prompt/markdown-prose.ts +245 -0
- package/src/prompt/model-mention-autocomplete.ts +94 -0
- package/src/prompt/model-mention-syntax.ts +41 -0
- package/src/prompt/orchestrate.ts +26 -0
- package/src/prompt/prompt-action-autocomplete.ts +337 -0
- package/src/prompt/queue-input.ts +132 -0
- package/src/prompt/skill-tokens.ts +36 -0
- package/src/prompt/tips.txt +27 -0
- package/src/prompt/tools-markdown.ts +29 -0
- package/src/prompt/ultrathink.ts +26 -0
- package/src/prompt/usage-amounts.ts +99 -0
- package/src/prompt/video.ts +47 -0
- package/src/prompt/welcome.ts +605 -0
- package/src/prompt/workflow.ts +26 -0
- package/src/render/code-cell.ts +244 -0
- package/src/render/file-list.ts +58 -0
- package/src/render/hyperlink.ts +192 -0
- package/src/render/index.ts +17 -0
- package/src/render/output-block.ts +267 -0
- package/src/render/output-pane.ts +299 -0
- package/src/render/render-utils.ts +1182 -0
- package/src/render/sixel.ts +69 -0
- package/src/render/status-line.ts +55 -0
- package/src/render/tool-card.ts +272 -0
- package/src/render/tree-list.ts +188 -0
- package/src/render/types.ts +19 -0
- package/src/render/utils.ts +109 -0
- package/src/render/width-aware-text.ts +60 -0
- package/src/setup/lazy.ts +17 -0
- package/src/setup/scenes/composer.ts +127 -0
- package/src/setup/scenes/glyph.ts +120 -0
- package/src/setup/scenes/model.ts +138 -0
- package/src/setup/scenes/outro.ts +24 -0
- package/src/setup/scenes/providers.ts +107 -0
- package/src/setup/scenes/sign-in.ts +373 -0
- package/src/setup/scenes/splash.ts +192 -0
- package/src/setup/scenes/theme.ts +343 -0
- package/src/setup/scenes/types.ts +111 -0
- package/src/setup/scenes/web-search.ts +165 -0
- package/src/setup/setup-version.ts +11 -0
- package/src/setup/startup-splash.ts +107 -0
- package/src/setup/wizard-overlay.ts +319 -0
- package/src/setup/wizard.ts +101 -0
- package/src/status-line/component.ts +3003 -0
- package/src/status-line/context-usage.ts +688 -0
- package/src/status-line/footer.ts +323 -0
- package/src/status-line/git-utils.ts +42 -0
- package/src/status-line/host.ts +97 -0
- package/src/status-line/index.ts +6 -0
- package/src/status-line/loop.ts +28 -0
- package/src/status-line/metrics.ts +94 -0
- package/src/status-line/presets.ts +107 -0
- package/src/status-line/schema.ts +57 -0
- package/src/status-line/segments.ts +934 -0
- package/src/status-line/separators.ts +55 -0
- package/src/status-line/types.ts +227 -0
- package/src/stdin-buffer.ts +4 -2
- package/src/theme/color.ts +95 -0
- package/src/theme/dark.json +95 -0
- package/src/theme/defaults/alabaster.json +93 -0
- package/src/theme/defaults/amethyst.json +96 -0
- package/src/theme/defaults/anthracite.json +93 -0
- package/src/theme/defaults/basalt.json +91 -0
- package/src/theme/defaults/birch.json +95 -0
- package/src/theme/defaults/dark-abyss.json +91 -0
- package/src/theme/defaults/dark-arctic.json +104 -0
- package/src/theme/defaults/dark-aurora.json +95 -0
- package/src/theme/defaults/dark-catppuccin.json +107 -0
- package/src/theme/defaults/dark-cavern.json +91 -0
- package/src/theme/defaults/dark-celestial.json +106 -0
- package/src/theme/defaults/dark-copper.json +95 -0
- package/src/theme/defaults/dark-cosmos.json +90 -0
- package/src/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/theme/defaults/dark-dracula.json +98 -0
- package/src/theme/defaults/dark-eclipse.json +91 -0
- package/src/theme/defaults/dark-ember.json +95 -0
- package/src/theme/defaults/dark-equinox.json +90 -0
- package/src/theme/defaults/dark-forest.json +96 -0
- package/src/theme/defaults/dark-github.json +105 -0
- package/src/theme/defaults/dark-gruvbox.json +112 -0
- package/src/theme/defaults/dark-lavender.json +95 -0
- package/src/theme/defaults/dark-lunar.json +89 -0
- package/src/theme/defaults/dark-midnight.json +95 -0
- package/src/theme/defaults/dark-monochrome.json +94 -0
- package/src/theme/defaults/dark-monokai.json +98 -0
- package/src/theme/defaults/dark-nebula.json +90 -0
- package/src/theme/defaults/dark-nord.json +98 -0
- package/src/theme/defaults/dark-ocean.json +101 -0
- package/src/theme/defaults/dark-one.json +100 -0
- package/src/theme/defaults/dark-poimandres.json +115 -0
- package/src/theme/defaults/dark-rainforest.json +91 -0
- package/src/theme/defaults/dark-reef.json +91 -0
- package/src/theme/defaults/dark-retro.json +92 -0
- package/src/theme/defaults/dark-rose-pine.json +96 -0
- package/src/theme/defaults/dark-sakura.json +95 -0
- package/src/theme/defaults/dark-slate.json +95 -0
- package/src/theme/defaults/dark-solarized.json +97 -0
- package/src/theme/defaults/dark-solstice.json +90 -0
- package/src/theme/defaults/dark-starfall.json +91 -0
- package/src/theme/defaults/dark-sunset.json +99 -0
- package/src/theme/defaults/dark-swamp.json +90 -0
- package/src/theme/defaults/dark-synthwave.json +103 -0
- package/src/theme/defaults/dark-taiga.json +91 -0
- package/src/theme/defaults/dark-terminal.json +95 -0
- package/src/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/theme/defaults/dark-tundra.json +91 -0
- package/src/theme/defaults/dark-twilight.json +91 -0
- package/src/theme/defaults/dark-volcanic.json +91 -0
- package/src/theme/defaults/graphite.json +92 -0
- package/src/theme/defaults/index.ts +201 -0
- package/src/theme/defaults/light-arctic.json +107 -0
- package/src/theme/defaults/light-aurora-day.json +91 -0
- package/src/theme/defaults/light-canyon.json +91 -0
- package/src/theme/defaults/light-catppuccin.json +106 -0
- package/src/theme/defaults/light-cirrus.json +90 -0
- package/src/theme/defaults/light-coral.json +95 -0
- package/src/theme/defaults/light-cyberpunk.json +96 -0
- package/src/theme/defaults/light-dawn.json +90 -0
- package/src/theme/defaults/light-dunes.json +91 -0
- package/src/theme/defaults/light-eucalyptus.json +95 -0
- package/src/theme/defaults/light-forest.json +100 -0
- package/src/theme/defaults/light-frost.json +95 -0
- package/src/theme/defaults/light-github.json +115 -0
- package/src/theme/defaults/light-glacier.json +91 -0
- package/src/theme/defaults/light-gruvbox.json +108 -0
- package/src/theme/defaults/light-haze.json +90 -0
- package/src/theme/defaults/light-honeycomb.json +95 -0
- package/src/theme/defaults/light-lagoon.json +91 -0
- package/src/theme/defaults/light-lavender.json +95 -0
- package/src/theme/defaults/light-meadow.json +91 -0
- package/src/theme/defaults/light-mint.json +95 -0
- package/src/theme/defaults/light-monochrome.json +101 -0
- package/src/theme/defaults/light-ocean.json +99 -0
- package/src/theme/defaults/light-one.json +99 -0
- package/src/theme/defaults/light-opal.json +91 -0
- package/src/theme/defaults/light-orchard.json +91 -0
- package/src/theme/defaults/light-paper.json +95 -0
- package/src/theme/defaults/light-poimandres.json +115 -0
- package/src/theme/defaults/light-prism.json +90 -0
- package/src/theme/defaults/light-retro.json +98 -0
- package/src/theme/defaults/light-sand.json +95 -0
- package/src/theme/defaults/light-savanna.json +91 -0
- package/src/theme/defaults/light-solarized.json +102 -0
- package/src/theme/defaults/light-soleil.json +90 -0
- package/src/theme/defaults/light-sunset.json +99 -0
- package/src/theme/defaults/light-synthwave.json +98 -0
- package/src/theme/defaults/light-tokyo-night.json +111 -0
- package/src/theme/defaults/light-wetland.json +91 -0
- package/src/theme/defaults/light-zenith.json +89 -0
- package/src/theme/defaults/limestone.json +94 -0
- package/src/theme/defaults/mahogany.json +97 -0
- package/src/theme/defaults/marble.json +93 -0
- package/src/theme/defaults/obsidian.json +91 -0
- package/src/theme/defaults/onyx.json +91 -0
- package/src/theme/defaults/pearl.json +93 -0
- package/src/theme/defaults/porcelain.json +91 -0
- package/src/theme/defaults/quartz.json +96 -0
- package/src/theme/defaults/sandstone.json +95 -0
- package/src/theme/defaults/titanium.json +90 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/light.json +93 -0
- package/src/theme/loader.ts +200 -0
- package/src/theme/mermaid-cache.ts +92 -0
- package/src/theme/schema-validation.ts +119 -0
- package/src/theme/schema.ts +168 -0
- package/src/theme/session-color.ts +253 -0
- package/src/theme/shimmer.ts +307 -0
- package/src/theme/symbols.ts +1433 -0
- package/src/theme/theme-class.ts +787 -0
- package/src/theme/theme-schema.json +463 -0
- package/src/theme/theme.ts +810 -0
- package/src/theme/tui-adapters.ts +339 -0
- package/src/thinking.ts +380 -0
- package/src/tools/agent-tree.ts +128 -0
- package/src/tools/ask.ts +482 -0
- package/src/tools/ast-edit.ts +210 -0
- package/src/tools/ast-grep.ts +171 -0
- package/src/tools/autoresearch.ts +287 -0
- package/src/tools/bash-interactive.ts +295 -0
- package/src/tools/bash.ts +468 -0
- package/src/tools/conflict-detect.ts +77 -0
- package/src/tools/debug.ts +167 -0
- package/src/tools/default-renderer.ts +172 -0
- package/src/tools/edit.ts +1258 -0
- package/src/tools/eval-format/code-mode-declarations.ts +61 -0
- package/src/tools/eval-format/index.ts +16 -0
- package/src/tools/eval-format/javascript.ts +952 -0
- package/src/tools/eval-format/python.ts +544 -0
- package/src/tools/eval.ts +835 -0
- package/src/tools/fetch.ts +226 -0
- package/src/tools/gh-format.ts +18 -0
- package/src/tools/github.ts +686 -0
- package/src/tools/glob.ts +213 -0
- package/src/tools/goal.ts +153 -0
- package/src/tools/grep.ts +385 -0
- package/src/tools/grouped-file-output.ts +211 -0
- package/src/tools/hashline-format.ts +59 -0
- package/src/tools/hub.ts +1488 -0
- package/src/tools/index.ts +71 -0
- package/src/tools/json-tree.ts +388 -0
- package/src/tools/line-ranges.ts +98 -0
- package/src/tools/list-limit.ts +43 -0
- package/src/tools/lsp.ts +702 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/mcp.ts +309 -0
- package/src/tools/memory.ts +220 -0
- package/src/tools/output-meta.ts +367 -0
- package/src/tools/read.ts +628 -0
- package/src/tools/renderer.ts +107 -0
- package/src/tools/report-tool-issue.ts +13 -0
- package/src/tools/resolve.ts +139 -0
- package/src/tools/streaming-output.ts +1576 -0
- package/src/tools/subprocess.ts +39 -0
- package/src/tools/task-repair-args.ts +118 -0
- package/src/tools/task-yield-assembly.ts +147 -0
- package/src/tools/task.ts +2110 -0
- package/src/tools/terminal-output.ts +153 -0
- package/src/tools/think.ts +28 -0
- package/src/tools/todo.ts +580 -0
- package/src/tools/tool-errors.ts +15 -0
- package/src/tools/vibe.ts +420 -0
- package/src/tools/web-search.ts +409 -0
- package/src/tools/write.ts +511 -0
- package/src/tools/xd-url.ts +28 -0
- package/src/tools/xdev.ts +201 -0
- package/src/tui.ts +57 -4
- package/src/utils.ts +8 -0
|
@@ -0,0 +1,1415 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { Effort } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import {
|
|
4
|
+
type Component,
|
|
5
|
+
Container,
|
|
6
|
+
extractPrintableText,
|
|
7
|
+
fuzzyRank,
|
|
8
|
+
getKeybindings,
|
|
9
|
+
getSettingItemFilterText,
|
|
10
|
+
type ImageBudget,
|
|
11
|
+
Input,
|
|
12
|
+
matchesKey,
|
|
13
|
+
routeSelectListMouse,
|
|
14
|
+
routeSgrMouseInput,
|
|
15
|
+
type SelectItem,
|
|
16
|
+
SelectList,
|
|
17
|
+
type SettingItem,
|
|
18
|
+
SettingsList,
|
|
19
|
+
type SgrMouseEvent,
|
|
20
|
+
type Tab,
|
|
21
|
+
TabBar,
|
|
22
|
+
truncateToWidth,
|
|
23
|
+
visibleWidth,
|
|
24
|
+
} from "../index";
|
|
25
|
+
import type { ShapeTarget } from "@oh-my-pi/snapcompact";
|
|
26
|
+
import type {
|
|
27
|
+
ContextLineMode,
|
|
28
|
+
StatusLinePreset,
|
|
29
|
+
StatusLineSegmentId,
|
|
30
|
+
StatusLineSeparatorStyle,
|
|
31
|
+
} from "../status-line/schema";
|
|
32
|
+
import {
|
|
33
|
+
SETTING_TABS,
|
|
34
|
+
TAB_METADATA,
|
|
35
|
+
type SettingTab,
|
|
36
|
+
type SettingsHost,
|
|
37
|
+
type SettingsDisplayEntry,
|
|
38
|
+
} from "./settings-defs";
|
|
39
|
+
import { getCurrentThemeName, getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme";
|
|
40
|
+
import { AUTO_THINKING, type ConfiguredThinkingLevel } from "../thinking";
|
|
41
|
+
import { getTabBarTheme } from "../chrome/shared";
|
|
42
|
+
import { type ComposerPreviewStatusSource, ComposerShapePreview } from "./composer-shape-preview";
|
|
43
|
+
import { getComposerShapeOptions } from "./composer-shape-registry";
|
|
44
|
+
import { bottomBorder, divider, row, topBorder } from "../chrome/overlay-box";
|
|
45
|
+
import { PluginSettingsComponent, type PluginSettingsHost } from "./plugin-settings";
|
|
46
|
+
import { getSettingDef, getSettingsForTab, type SettingDef } from "./settings-defs";
|
|
47
|
+
import { SnapcompactShapePreview } from "./snapcompact-shape-preview";
|
|
48
|
+
import { getPreset } from "../status-line/presets";
|
|
49
|
+
import { FormField, SelectFormField, TextFormField } from "../components/form";
|
|
50
|
+
import { formTheme } from "../chrome/form-theme";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Free-text string setting field backed by the shared text form field.
|
|
54
|
+
* Current values prefill, including secrets retained behind Input masking;
|
|
55
|
+
* submitting an empty string clears the setting and validation errors stay inline.
|
|
56
|
+
*/
|
|
57
|
+
function createSettingsTextField(
|
|
58
|
+
label: string,
|
|
59
|
+
description: string,
|
|
60
|
+
currentValue: string,
|
|
61
|
+
secret: boolean,
|
|
62
|
+
onSubmit: (value: string) => void | Promise<void>,
|
|
63
|
+
onCancel: () => void,
|
|
64
|
+
requestRender?: () => void,
|
|
65
|
+
): TextFormField {
|
|
66
|
+
return new TextFormField({
|
|
67
|
+
theme: formTheme,
|
|
68
|
+
label,
|
|
69
|
+
description: description || undefined,
|
|
70
|
+
secret,
|
|
71
|
+
initialValue: currentValue || undefined,
|
|
72
|
+
empty: "submit",
|
|
73
|
+
hint: " Enter to save · Esc to cancel · Clear field to unset",
|
|
74
|
+
onSubmit,
|
|
75
|
+
onCancel,
|
|
76
|
+
requestRender,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Single-choice setting field backed by the shared select form field.
|
|
82
|
+
* Preserves the current selection, live async previews, footer previews,
|
|
83
|
+
* and select/cancel dispatch of the bespoke submenu it replaces.
|
|
84
|
+
*/
|
|
85
|
+
function createSettingsSelectField(
|
|
86
|
+
title: string,
|
|
87
|
+
description: string,
|
|
88
|
+
options: ReadonlyArray<SelectItem>,
|
|
89
|
+
currentValue: string,
|
|
90
|
+
onSelect: (value: string) => void,
|
|
91
|
+
onCancel: () => void,
|
|
92
|
+
onSelectionChange?: (value: string) => void | Promise<void>,
|
|
93
|
+
getPreview?: () => string,
|
|
94
|
+
footer?: Component,
|
|
95
|
+
requestRender?: () => void,
|
|
96
|
+
): SelectFormField {
|
|
97
|
+
return new SelectFormField({
|
|
98
|
+
theme: formTheme,
|
|
99
|
+
label: title,
|
|
100
|
+
description: description || undefined,
|
|
101
|
+
items: options,
|
|
102
|
+
currentValue,
|
|
103
|
+
maxVisible: 10,
|
|
104
|
+
selectTheme: getSelectListTheme(),
|
|
105
|
+
getPreview,
|
|
106
|
+
onSelectionChange,
|
|
107
|
+
onSubmit: onSelect,
|
|
108
|
+
onCancel,
|
|
109
|
+
hint: " Enter to select · Esc to go back",
|
|
110
|
+
footer,
|
|
111
|
+
requestRender,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Submenu for array-of-enum settings: every option is a toggle row. Enter or
|
|
117
|
+
* Space flips membership; ordered lists render 1-based positions and reorder
|
|
118
|
+
* the highlighted member with ←/→. Changes apply live; Esc goes back.
|
|
119
|
+
*/
|
|
120
|
+
class MultiSelectSubmenu extends Container {
|
|
121
|
+
#selectList!: SelectList;
|
|
122
|
+
#field!: FormField;
|
|
123
|
+
#value: string[];
|
|
124
|
+
#cursor = 0;
|
|
125
|
+
#pressedItemId: string | undefined;
|
|
126
|
+
#dropItemId: string | undefined;
|
|
127
|
+
readonly #title: string;
|
|
128
|
+
readonly #description: string;
|
|
129
|
+
readonly #options: ReadonlyArray<SelectItem>;
|
|
130
|
+
readonly #ordered: boolean;
|
|
131
|
+
readonly #onApply: (value: string[]) => void;
|
|
132
|
+
readonly #onClose: () => void;
|
|
133
|
+
|
|
134
|
+
constructor(
|
|
135
|
+
title: string,
|
|
136
|
+
description: string,
|
|
137
|
+
options: ReadonlyArray<SelectItem>,
|
|
138
|
+
initial: readonly string[],
|
|
139
|
+
ordered: boolean,
|
|
140
|
+
onApply: (value: string[]) => void,
|
|
141
|
+
onClose: () => void,
|
|
142
|
+
) {
|
|
143
|
+
super();
|
|
144
|
+
this.#title = title;
|
|
145
|
+
this.#description = description;
|
|
146
|
+
this.#options = options;
|
|
147
|
+
this.#ordered = ordered;
|
|
148
|
+
this.#onApply = onApply;
|
|
149
|
+
this.#onClose = onClose;
|
|
150
|
+
// Drop stale ids (renamed/removed providers) so positions stay contiguous.
|
|
151
|
+
this.#value = initial.filter(id => options.some(option => option.value === id));
|
|
152
|
+
this.#rebuild();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#rebuild(): void {
|
|
156
|
+
this.clear();
|
|
157
|
+
|
|
158
|
+
const items = this.#options.map((option): SelectItem => {
|
|
159
|
+
const position = this.#value.indexOf(option.value);
|
|
160
|
+
const mark =
|
|
161
|
+
position === -1
|
|
162
|
+
? theme.fg("dim", this.#ordered ? " · " : " ○ ")
|
|
163
|
+
: this.#ordered
|
|
164
|
+
? theme.fg("accent", `${String(position + 1).padStart(2)}.`)
|
|
165
|
+
: theme.fg("accent", " ● ");
|
|
166
|
+
return { value: option.value, label: `${mark} ${option.label}`, description: option.description };
|
|
167
|
+
});
|
|
168
|
+
this.#selectList = new SelectList(items, Math.min(items.length, 12), getSelectListTheme());
|
|
169
|
+
this.#selectList.setSelectedIndex(this.#cursor);
|
|
170
|
+
this.#selectList.onSelect = item => this.#toggle(item.value);
|
|
171
|
+
this.#selectList.onSelectionChange = item => {
|
|
172
|
+
this.#cursor = this.#options.findIndex(option => option.value === item.value);
|
|
173
|
+
};
|
|
174
|
+
this.#selectList.onCancel = this.#onClose;
|
|
175
|
+
const hint = this.#ordered
|
|
176
|
+
? " Click to toggle · drag selected items to reorder · ←/→ move · 1-9 place · Esc to go back"
|
|
177
|
+
: " Click/Enter/Space to toggle · Esc to go back";
|
|
178
|
+
this.#field = new FormField(this.#selectList, {
|
|
179
|
+
theme: formTheme,
|
|
180
|
+
label: this.#title,
|
|
181
|
+
description: this.#description || undefined,
|
|
182
|
+
hint,
|
|
183
|
+
});
|
|
184
|
+
this.addChild(this.#field);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#apply(next: string[]): void {
|
|
188
|
+
this.#value = next;
|
|
189
|
+
this.#onApply([...next]);
|
|
190
|
+
this.#rebuild();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#toggle(id: string): void {
|
|
194
|
+
const next = this.#value.includes(id) ? this.#value.filter(v => v !== id) : [...this.#value, id];
|
|
195
|
+
this.#apply(next);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#move(id: string, delta: -1 | 1): void {
|
|
199
|
+
const from = this.#value.indexOf(id);
|
|
200
|
+
if (from === -1) return;
|
|
201
|
+
const to = from + delta;
|
|
202
|
+
if (to < 0 || to >= this.#value.length) return;
|
|
203
|
+
const next = [...this.#value];
|
|
204
|
+
next[from] = next[to]!;
|
|
205
|
+
next[to] = id;
|
|
206
|
+
this.#apply(next);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Move a selected item before another selected item, retaining every other preference. */
|
|
210
|
+
#moveBefore(id: string, beforeId: string): void {
|
|
211
|
+
if (id === beforeId) return;
|
|
212
|
+
const next = this.#value.filter(value => value !== id);
|
|
213
|
+
const target = next.indexOf(beforeId);
|
|
214
|
+
if (target === -1) return;
|
|
215
|
+
next.splice(target, 0, id);
|
|
216
|
+
this.#apply(next);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Splice the option into the 1-based `position` of the selection (adding it if unselected). */
|
|
220
|
+
#placeAt(id: string, position: number): void {
|
|
221
|
+
const next = this.#value.filter(v => v !== id);
|
|
222
|
+
next.splice(Math.min(position - 1, next.length), 0, id);
|
|
223
|
+
this.#apply(next);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
routeMouse(event: SgrMouseEvent, line: number, _col: number): void {
|
|
227
|
+
const controlLine = this.#field.controlLineAt(line);
|
|
228
|
+
if (controlLine === undefined) return;
|
|
229
|
+
const itemIndex = this.#selectList.hitTest(controlLine);
|
|
230
|
+
if (event.wheel !== null) {
|
|
231
|
+
routeSelectListMouse(this.#selectList, event, controlLine);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (event.motion) {
|
|
235
|
+
this.#selectList.setHoverIndex(itemIndex ?? null);
|
|
236
|
+
const target = itemIndex === undefined ? undefined : this.#options[itemIndex]?.value;
|
|
237
|
+
if (
|
|
238
|
+
this.#ordered &&
|
|
239
|
+
this.#pressedItemId !== undefined &&
|
|
240
|
+
target !== undefined &&
|
|
241
|
+
target !== this.#pressedItemId &&
|
|
242
|
+
this.#value.includes(target)
|
|
243
|
+
) {
|
|
244
|
+
this.#dropItemId = target;
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (event.leftClick && itemIndex !== undefined) {
|
|
249
|
+
const item = this.#options[itemIndex];
|
|
250
|
+
if (!item) return;
|
|
251
|
+
this.#cursor = itemIndex;
|
|
252
|
+
this.#selectList.setSelectedIndex(itemIndex);
|
|
253
|
+
this.#pressedItemId = item.value;
|
|
254
|
+
this.#dropItemId = item.value;
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (!event.release) return;
|
|
258
|
+
|
|
259
|
+
const pressedItemId = this.#pressedItemId;
|
|
260
|
+
const dropItemId = this.#dropItemId;
|
|
261
|
+
this.#pressedItemId = undefined;
|
|
262
|
+
this.#dropItemId = undefined;
|
|
263
|
+
if (!pressedItemId) return;
|
|
264
|
+
if (this.#ordered && dropItemId !== undefined && dropItemId !== pressedItemId) {
|
|
265
|
+
this.#moveBefore(pressedItemId, dropItemId);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
this.#toggle(pressedItemId);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
handleInput(data: string): void {
|
|
272
|
+
const current = this.#options[this.#cursor]?.value;
|
|
273
|
+
if (data === " " && current !== undefined) {
|
|
274
|
+
this.#toggle(current);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (this.#ordered && current !== undefined && (data === "\x1b[D" || data === "\x1b[C")) {
|
|
278
|
+
this.#move(current, data === "\x1b[D" ? -1 : 1);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (this.#ordered && current !== undefined && data.length === 1 && data >= "1" && data <= "9") {
|
|
282
|
+
this.#placeAt(current, Number(data));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
this.#selectList.handleInput(data);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
class ProviderLimitsSubmenu extends Container {
|
|
290
|
+
#listField: SelectFormField | undefined;
|
|
291
|
+
readonly #settings: SettingsHost;
|
|
292
|
+
readonly #providers: readonly string[];
|
|
293
|
+
readonly #onChange: (value: Record<string, number>) => void;
|
|
294
|
+
readonly #onCancel: () => void;
|
|
295
|
+
readonly #requestRender: (() => void) | undefined;
|
|
296
|
+
|
|
297
|
+
constructor(
|
|
298
|
+
settings: SettingsHost,
|
|
299
|
+
providers: readonly string[],
|
|
300
|
+
onChange: (value: Record<string, number>) => void,
|
|
301
|
+
onCancel: () => void,
|
|
302
|
+
requestRender?: () => void,
|
|
303
|
+
) {
|
|
304
|
+
super();
|
|
305
|
+
this.#settings = settings;
|
|
306
|
+
this.#providers = providers;
|
|
307
|
+
this.#onChange = onChange;
|
|
308
|
+
this.#onCancel = onCancel;
|
|
309
|
+
this.#requestRender = requestRender;
|
|
310
|
+
this.#showProviderList();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
#providerIds(): string[] {
|
|
314
|
+
const limits = this.#settings.normalizeProviderLimits(this.#settings.get("providers.maxInFlightRequests"));
|
|
315
|
+
return [...new Set([...this.#providers, ...Object.keys(limits)])].sort((a, b) => a.localeCompare(b));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
#showProviderList(): void {
|
|
319
|
+
this.clear();
|
|
320
|
+
|
|
321
|
+
const limits = this.#settings.normalizeProviderLimits(this.#settings.get("providers.maxInFlightRequests"));
|
|
322
|
+
const providerItems = this.#providerIds().map((provider): SelectItem => {
|
|
323
|
+
const limit = limits[provider];
|
|
324
|
+
return {
|
|
325
|
+
value: provider,
|
|
326
|
+
label: provider,
|
|
327
|
+
description: limit === undefined ? "Unlimited" : `Limit: ${limit}`,
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
const clearItem: SelectItem[] =
|
|
331
|
+
Object.keys(limits).length === 0
|
|
332
|
+
? []
|
|
333
|
+
: [{ value: "__clear_all", label: "Clear all limits", description: "Make every provider unlimited" }];
|
|
334
|
+
const items = [...providerItems, ...clearItem];
|
|
335
|
+
this.#listField = new SelectFormField({
|
|
336
|
+
theme: formTheme,
|
|
337
|
+
label: "Max In-Flight Requests",
|
|
338
|
+
description:
|
|
339
|
+
"Select a provider, enter a positive number to cap concurrent LLM requests, or clear it for unlimited.",
|
|
340
|
+
items,
|
|
341
|
+
maxVisible: 12,
|
|
342
|
+
selectTheme: getSelectListTheme(),
|
|
343
|
+
hint: " Enter to edit provider · Esc to go back",
|
|
344
|
+
onSubmit: value => {
|
|
345
|
+
if (value === "__clear_all") {
|
|
346
|
+
this.#settings.set("providers.maxInFlightRequests", {});
|
|
347
|
+
this.#onChange({});
|
|
348
|
+
this.#showProviderList();
|
|
349
|
+
this.#requestRender?.();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
this.#showProviderEditor(value);
|
|
353
|
+
},
|
|
354
|
+
onCancel: this.#onCancel,
|
|
355
|
+
requestRender: this.#requestRender,
|
|
356
|
+
});
|
|
357
|
+
this.addChild(this.#listField);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
#showProviderEditor(provider: string): void {
|
|
361
|
+
const limits = this.#settings.normalizeProviderLimits(this.#settings.get("providers.maxInFlightRequests"));
|
|
362
|
+
this.clear();
|
|
363
|
+
this.#listField = undefined;
|
|
364
|
+
this.addChild(
|
|
365
|
+
new TextFormField({
|
|
366
|
+
theme: formTheme,
|
|
367
|
+
label: `Max In-Flight Requests: ${provider}`,
|
|
368
|
+
description:
|
|
369
|
+
"Enter a positive number. Decimals round down. Clear the field to make this provider unlimited.",
|
|
370
|
+
initialValue: limits[provider]?.toString() ?? undefined,
|
|
371
|
+
empty: "submit",
|
|
372
|
+
hint: " Enter to save · Esc to cancel · Clear field to unset",
|
|
373
|
+
validate: value => {
|
|
374
|
+
if (value.trim() === "") return undefined;
|
|
375
|
+
const limit = Number(value.trim());
|
|
376
|
+
if (!Number.isFinite(limit) || limit <= 0) return "Limit must be a positive number.";
|
|
377
|
+
return undefined;
|
|
378
|
+
},
|
|
379
|
+
onSubmit: value => {
|
|
380
|
+
const next = { ...limits };
|
|
381
|
+
const trimmed = value.trim();
|
|
382
|
+
if (trimmed === "") {
|
|
383
|
+
delete next[provider];
|
|
384
|
+
} else {
|
|
385
|
+
const limit = Number(trimmed);
|
|
386
|
+
if (!Number.isFinite(limit) || limit <= 0) throw new Error("Limit must be a positive number.");
|
|
387
|
+
next[provider] = Math.max(1, Math.floor(limit));
|
|
388
|
+
}
|
|
389
|
+
const normalized = this.#settings.validateProviderLimits(next);
|
|
390
|
+
this.#settings.set("providers.maxInFlightRequests", normalized);
|
|
391
|
+
this.#onChange(normalized);
|
|
392
|
+
this.#showProviderList();
|
|
393
|
+
this.#requestRender?.();
|
|
394
|
+
},
|
|
395
|
+
onCancel: () => {
|
|
396
|
+
this.#showProviderList();
|
|
397
|
+
this.#requestRender?.();
|
|
398
|
+
},
|
|
399
|
+
requestRender: this.#requestRender,
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
routeMouse(event: SgrMouseEvent, line: number, col: number): void {
|
|
405
|
+
this.#listField?.routeMouse(event, line, col);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
handleInput(data: string): void {
|
|
409
|
+
if (this.#listField) {
|
|
410
|
+
this.#listField.handleInput(data);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
this.children[0]?.handleInput?.(data);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** Stable sidebar width derived from the host's complete schema. */
|
|
418
|
+
function settingsSidebarWidth(entries: readonly SettingsDisplayEntry[]): number {
|
|
419
|
+
let nameWidth = 0;
|
|
420
|
+
for (const tab of SETTING_TABS) {
|
|
421
|
+
for (const def of getSettingsForTab(entries, tab)) {
|
|
422
|
+
if (def.group) nameWidth = Math.max(nameWidth, visibleWidth(def.group));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return Math.min(22, nameWidth) + 4;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function getSettingsTabs(): Tab[] {
|
|
429
|
+
return [
|
|
430
|
+
...SETTING_TABS.map(id => {
|
|
431
|
+
const meta = TAB_METADATA[id];
|
|
432
|
+
const icon = theme.symbol(meta.icon);
|
|
433
|
+
return { id, label: `${icon} ${meta.label}`, short: icon };
|
|
434
|
+
}),
|
|
435
|
+
{ id: "plugins", label: `${theme.icon.package} Plugins`, short: theme.icon.package },
|
|
436
|
+
];
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Dynamic context for settings that need runtime data.
|
|
441
|
+
* Some settings (like thinking level) are managed by the session, not Settings.
|
|
442
|
+
*/
|
|
443
|
+
export interface SettingsRuntimeContext {
|
|
444
|
+
settings: SettingsHost;
|
|
445
|
+
plugins: PluginSettingsHost;
|
|
446
|
+
/** Available thinking levels (from session) */
|
|
447
|
+
availableThinkingLevels: Effort[];
|
|
448
|
+
/** Current thinking level (from session) */
|
|
449
|
+
thinkingLevel: ThinkingLevel | undefined;
|
|
450
|
+
/** Available themes */
|
|
451
|
+
availableThemes: string[];
|
|
452
|
+
/** Provider/source ids shown in /model. */
|
|
453
|
+
providers: string[];
|
|
454
|
+
/** Active model (api + id); resolves what the snapcompact `auto` shape maps to. */
|
|
455
|
+
model?: ShapeTarget;
|
|
456
|
+
/** Shared TUI image budget (graphics ids + transmit-once) for image previews. */
|
|
457
|
+
imageBudget?: ImageBudget;
|
|
458
|
+
/** Schedules a re-render after async preview work completes. */
|
|
459
|
+
requestRender?: () => void;
|
|
460
|
+
/** Live status renderer for composer-shape previews (the session's status line). */
|
|
461
|
+
composerPreviewStatus?: ComposerPreviewStatusSource;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Status line settings subset for preview */
|
|
465
|
+
export interface StatusLinePreviewSettings {
|
|
466
|
+
preset?: StatusLinePreset;
|
|
467
|
+
contextLine?: ContextLineMode;
|
|
468
|
+
leftSegments?: StatusLineSegmentId[];
|
|
469
|
+
rightSegments?: StatusLineSegmentId[];
|
|
470
|
+
separator?: StatusLineSeparatorStyle;
|
|
471
|
+
sessionAccent?: boolean;
|
|
472
|
+
transparent?: boolean;
|
|
473
|
+
compactThinkingLevel?: boolean;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export interface SettingsCallbacks {
|
|
477
|
+
/** Called when any setting value changes */
|
|
478
|
+
onChange: (path: string, newValue: unknown) => void;
|
|
479
|
+
/** Called for theme preview while browsing */
|
|
480
|
+
onThemePreview?: (theme: string) => void | Promise<void>;
|
|
481
|
+
/** Called for status line preview while configuring */
|
|
482
|
+
onStatusLinePreview?: (settings: StatusLinePreviewSettings) => void;
|
|
483
|
+
/** Get current rendered status line for inline preview */
|
|
484
|
+
getStatusLinePreview?: () => string;
|
|
485
|
+
/** Called when plugins change */
|
|
486
|
+
onPluginsChanged?: () => void | Promise<void>;
|
|
487
|
+
/** Called when settings panel is closed */
|
|
488
|
+
onCancel: () => void;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Main tabbed settings selector component.
|
|
493
|
+
* Uses declarative settings definitions from settings-defs.ts.
|
|
494
|
+
*/
|
|
495
|
+
export class SettingsSelectorComponent implements Component {
|
|
496
|
+
#tabBar: TabBar;
|
|
497
|
+
#currentList: SettingsList | null = null;
|
|
498
|
+
#searchList: SettingsList | null = null;
|
|
499
|
+
#pluginComponent: PluginSettingsComponent | null = null;
|
|
500
|
+
#currentTabId: SettingTab | "plugins" = "appearance";
|
|
501
|
+
#preSearchTabId: SettingTab | "plugins" = "appearance";
|
|
502
|
+
#searchQuery = "";
|
|
503
|
+
/** Single-line editor backing the search banner (cursor, word ops, paste). */
|
|
504
|
+
#searchInput = new Input();
|
|
505
|
+
#searchMatchCount = 0;
|
|
506
|
+
/** First matching item id per tab id, for Tab-key jumps while searching. */
|
|
507
|
+
#searchFirstMatch = new Map<string, string>();
|
|
508
|
+
#textInputActive = false;
|
|
509
|
+
#hasSectionJump = false;
|
|
510
|
+
// Frame geometry from the last render, for mouse hit-testing (the
|
|
511
|
+
// fullscreen overlay paints from screen row 0, so mouse rows map 1:1).
|
|
512
|
+
#tabRowStart = 0;
|
|
513
|
+
#tabRowCount = 0;
|
|
514
|
+
#contentRowStart = 0;
|
|
515
|
+
#contentRowCount = 0;
|
|
516
|
+
#sidebarWidth: number;
|
|
517
|
+
readonly #context: SettingsRuntimeContext;
|
|
518
|
+
readonly #callbacks: SettingsCallbacks;
|
|
519
|
+
|
|
520
|
+
constructor(context: SettingsRuntimeContext, callbacks: SettingsCallbacks) {
|
|
521
|
+
this.#context = context;
|
|
522
|
+
this.#callbacks = callbacks;
|
|
523
|
+
this.#sidebarWidth = settingsSidebarWidth(context.settings.entries);
|
|
524
|
+
// No label prefix (the frame title already says Settings) and no
|
|
525
|
+
// "(tab to cycle)" hint (folded into the footer hint line).
|
|
526
|
+
this.#tabBar = new TabBar("", getSettingsTabs(), getTabBarTheme());
|
|
527
|
+
this.#tabBar.showHint = false;
|
|
528
|
+
this.#tabBar.onTabChange = () => {
|
|
529
|
+
const tabId = this.#tabBar.getActiveTab().id as SettingTab | "plugins";
|
|
530
|
+
if (this.#searchList) {
|
|
531
|
+
// While searching, tabs act as jump targets into the result list.
|
|
532
|
+
const firstId = this.#searchFirstMatch.get(tabId);
|
|
533
|
+
if (firstId) this.#searchList.selectItem(firstId);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
this.#switchToTab(tabId);
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// Initialize with first tab
|
|
540
|
+
this.#switchToTab("appearance");
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
invalidate(): void {
|
|
544
|
+
this.#tabBar.invalidate();
|
|
545
|
+
this.#currentList?.invalidate();
|
|
546
|
+
this.#searchList?.invalidate();
|
|
547
|
+
this.#pluginComponent?.invalidate();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Swap the active content (per-tab list, search list, or plugins). */
|
|
551
|
+
#setContent(build: () => void): void {
|
|
552
|
+
this.#currentList = null;
|
|
553
|
+
this.#searchList = null;
|
|
554
|
+
this.#pluginComponent = null;
|
|
555
|
+
build();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
#switchToTab(tabId: SettingTab | "plugins"): void {
|
|
559
|
+
this.#currentTabId = tabId;
|
|
560
|
+
this.#setContent(() => {
|
|
561
|
+
if (tabId === "plugins") {
|
|
562
|
+
this.#showPluginsTab();
|
|
563
|
+
} else {
|
|
564
|
+
this.#showSettingsTab(tabId);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
#footerHintText(): string {
|
|
570
|
+
if (this.#searchList) {
|
|
571
|
+
return "Enter to change · Tab to jump tabs · Esc to exit search";
|
|
572
|
+
}
|
|
573
|
+
if (this.#currentTabId === "plugins") {
|
|
574
|
+
return "Tab to switch tabs · Esc to close";
|
|
575
|
+
}
|
|
576
|
+
if (this.#currentList?.sectionFocused) {
|
|
577
|
+
return "↑/↓ to jump sections · Tab/Enter to settings · ←/→ to switch tabs · Esc to close";
|
|
578
|
+
}
|
|
579
|
+
const nav = this.#hasSectionJump ? "Tab to jump sections · ←/→ to switch tabs" : "Tab to switch tabs";
|
|
580
|
+
return `Enter/Space to change · ${nav} · Type to search · Esc to close`;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/** Single-line search banner: accent icon, editable query with live cursor, right-aligned match count. */
|
|
584
|
+
#renderSearchBanner(width: number): string {
|
|
585
|
+
const icon = theme.symbol("icon.search");
|
|
586
|
+
const countText = this.#searchMatchCount === 1 ? "1 match" : `${this.#searchMatchCount} matches`;
|
|
587
|
+
const rightWidth = visibleWidth(countText) + 1; // trailing margin
|
|
588
|
+
const prefix = ` ${theme.fg("accent", icon)} `;
|
|
589
|
+
// The input pads itself to exactly this width and keeps the cursor in view.
|
|
590
|
+
const inputWidth = Math.max(4, width - visibleWidth(prefix) - rightWidth - 1);
|
|
591
|
+
const inputLine = this.#searchInput.render(inputWidth)[0] ?? "";
|
|
592
|
+
const count = theme.fg(this.#searchMatchCount > 0 ? "dim" : "warning", countText);
|
|
593
|
+
return truncateToWidth(`${prefix}${theme.bold(inputLine)} ${count} `, width);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Fullscreen frame: title border, tab row, divider, optional search banner,
|
|
598
|
+
* the active content sized to fill the terminal, the appearance preview,
|
|
599
|
+
* then a footer hint pinned above the bottom border.
|
|
600
|
+
*/
|
|
601
|
+
render(width: number): readonly string[] {
|
|
602
|
+
const height = Math.max(14, process.stdout.rows || 40);
|
|
603
|
+
const innerWidth = Math.max(1, width - 4);
|
|
604
|
+
|
|
605
|
+
const tabLines = this.#tabBar.render(innerWidth);
|
|
606
|
+
const searching = this.#searchList !== null;
|
|
607
|
+
const showPreview = !searching && this.#currentTabId === "appearance";
|
|
608
|
+
const previewLines = showPreview ? ["", theme.fg("muted", "Preview:"), this.#getStatusPreviewString()] : [];
|
|
609
|
+
|
|
610
|
+
// Fixed chrome: top border, tabs, divider, [search row], divider, hint, bottom border.
|
|
611
|
+
const fixedRows = 1 + tabLines.length + 1 + (searching ? 1 : 0) + 1 + 1 + 1;
|
|
612
|
+
const contentRows = Math.max(7, height - fixedRows - previewLines.length);
|
|
613
|
+
|
|
614
|
+
const list = this.#searchList ?? this.#currentList;
|
|
615
|
+
let contentLines: readonly string[];
|
|
616
|
+
if (list) {
|
|
617
|
+
// SettingsList pads itself to viewport + blank + 3 description rows.
|
|
618
|
+
list.setMaxVisible(contentRows - 4);
|
|
619
|
+
contentLines = list.render(innerWidth);
|
|
620
|
+
} else if (this.#pluginComponent) {
|
|
621
|
+
contentLines = this.#pluginComponent.render(innerWidth);
|
|
622
|
+
} else {
|
|
623
|
+
contentLines = [];
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const out: string[] = [];
|
|
627
|
+
out.push(topBorder(width, "Settings"));
|
|
628
|
+
this.#tabRowStart = out.length;
|
|
629
|
+
this.#tabRowCount = tabLines.length;
|
|
630
|
+
for (const line of tabLines) {
|
|
631
|
+
out.push(row(line, width));
|
|
632
|
+
}
|
|
633
|
+
out.push(divider(width));
|
|
634
|
+
if (searching) {
|
|
635
|
+
out.push(row(this.#renderSearchBanner(innerWidth), width));
|
|
636
|
+
}
|
|
637
|
+
this.#contentRowStart = out.length;
|
|
638
|
+
this.#contentRowCount = contentRows;
|
|
639
|
+
for (let i = 0; i < contentRows; i++) {
|
|
640
|
+
out.push(row(contentLines[i] ?? "", width));
|
|
641
|
+
}
|
|
642
|
+
for (const line of previewLines) {
|
|
643
|
+
out.push(row(line, width));
|
|
644
|
+
}
|
|
645
|
+
out.push(divider(width));
|
|
646
|
+
out.push(row(theme.fg("dim", this.#footerHintText()), width));
|
|
647
|
+
out.push(bottomBorder(width));
|
|
648
|
+
return out;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Route an SGR mouse report against the frame geometry of the last render.
|
|
653
|
+
* Wheel scrolls the focused list, motion drives the hover highlights (tabs
|
|
654
|
+
* and rows), and a left click activates: tabs switch (or jump, while
|
|
655
|
+
* searching), a row click selects, and a click on the already-selected row
|
|
656
|
+
* activates it (toggle / open submenu).
|
|
657
|
+
*/
|
|
658
|
+
#handleMouse(data: string): boolean {
|
|
659
|
+
return routeSgrMouseInput(data, event => this.#routeMouseEvent(event));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
#routeMouseEvent(event: SgrMouseEvent): boolean {
|
|
663
|
+
const list = this.#searchList ?? this.#currentList;
|
|
664
|
+
// row() insets content by the border column plus a space.
|
|
665
|
+
const contentColInset = 2;
|
|
666
|
+
const innerCol = event.col - contentColInset;
|
|
667
|
+
const contentLine = event.row - this.#contentRowStart;
|
|
668
|
+
|
|
669
|
+
// An open submenu owns the pointer: wheel, hover, and clicks route into
|
|
670
|
+
// it (text-input submenus ignore routed events).
|
|
671
|
+
if (list?.hasOpenSubmenu()) {
|
|
672
|
+
list.routeSubmenuMouse(event, contentLine, innerCol);
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const tabLine = event.row - this.#tabRowStart;
|
|
677
|
+
const overTabs = tabLine >= 0 && tabLine < this.#tabRowCount;
|
|
678
|
+
const overContent = contentLine >= 0 && contentLine < this.#contentRowCount;
|
|
679
|
+
|
|
680
|
+
if (event.wheel !== null) {
|
|
681
|
+
if (overContent) {
|
|
682
|
+
list?.handleWheelAt(event.wheel, contentLine, innerCol);
|
|
683
|
+
}
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (event.motion) {
|
|
688
|
+
const hovered = overTabs ? this.#tabBar.tabAt(tabLine, innerCol) : undefined;
|
|
689
|
+
this.#tabBar.setHoverTab(hovered && !hovered.muted ? hovered.id : null);
|
|
690
|
+
// hoverTest: never light up pane rows while the pointer is on the
|
|
691
|
+
// sidebar — only rows the pointer is actually on.
|
|
692
|
+
list?.setHoverItem(overContent ? (list.hoverTest(contentLine, innerCol) ?? null) : null);
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
if (!event.leftClick) return true;
|
|
696
|
+
|
|
697
|
+
if (overTabs) {
|
|
698
|
+
const tab = this.#tabBar.tabAt(tabLine, innerCol);
|
|
699
|
+
if (tab) this.#tabBar.selectTab(tab.id);
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
if (overContent && list) {
|
|
703
|
+
const itemId = list.hoverTest(contentLine, innerCol);
|
|
704
|
+
const id = itemId ?? list.hitTest(contentLine, innerCol);
|
|
705
|
+
if (id !== undefined) {
|
|
706
|
+
const wasSelected = list.getSelectedItem()?.id === id;
|
|
707
|
+
list.selectItem(id);
|
|
708
|
+
// Only repeated setting-row clicks activate. Sidebar section clicks navigate.
|
|
709
|
+
if (wasSelected && itemId !== undefined) list.handleInput("\n");
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
716
|
+
// Global search (type-to-search across every tab)
|
|
717
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
718
|
+
|
|
719
|
+
/** Swap the tab content for the global search result list. */
|
|
720
|
+
#startSearch(initialQuery: string): void {
|
|
721
|
+
this.#preSearchTabId = this.#currentTabId;
|
|
722
|
+
this.#searchInput = new Input();
|
|
723
|
+
this.#searchInput.prompt = "";
|
|
724
|
+
this.#searchInput.setValue(initialQuery);
|
|
725
|
+
const list = new SettingsList(
|
|
726
|
+
[],
|
|
727
|
+
10,
|
|
728
|
+
getSettingsListTheme(),
|
|
729
|
+
(id, newValue) => this.#onSearchSettingChange(id, newValue),
|
|
730
|
+
() => this.#callbacks.onCancel(),
|
|
731
|
+
{
|
|
732
|
+
layout: "flat",
|
|
733
|
+
typeToSearch: false,
|
|
734
|
+
emptyText: "No matching settings",
|
|
735
|
+
hint: "",
|
|
736
|
+
},
|
|
737
|
+
);
|
|
738
|
+
// Keep the footer tab highlight on the tab owning the selected result.
|
|
739
|
+
list.onSelectionChange = item => this.#syncTabBarToSelection(item);
|
|
740
|
+
this.#setContent(() => {
|
|
741
|
+
this.#searchList = list;
|
|
742
|
+
});
|
|
743
|
+
this.#setSearchQuery(initialQuery);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Recompute matches across every settings tab. Results render as one flat
|
|
748
|
+
* list with a heading row per tab; the footer tab bar reorders to show
|
|
749
|
+
* matching tabs (with counts) first and the rest muted at the end.
|
|
750
|
+
*/
|
|
751
|
+
#setSearchQuery(query: string): void {
|
|
752
|
+
if (!this.#searchList) return;
|
|
753
|
+
if (query.length === 0) {
|
|
754
|
+
this.#endSearch(false);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
this.#searchQuery = query;
|
|
758
|
+
|
|
759
|
+
const counts = new Map<SettingTab, number>();
|
|
760
|
+
const items: SettingItem[] = [];
|
|
761
|
+
const tabResults: { tab: SettingTab; matched: SettingItem[]; bestScore: number; order: number }[] = [];
|
|
762
|
+
this.#searchFirstMatch.clear();
|
|
763
|
+
let total = 0;
|
|
764
|
+
for (const tab of SETTING_TABS) {
|
|
765
|
+
const candidates: SettingItem[] = [];
|
|
766
|
+
for (const def of getSettingsForTab(this.#context.settings.entries, tab)) {
|
|
767
|
+
const item = this.#defToItem(def);
|
|
768
|
+
if (item) candidates.push(item);
|
|
769
|
+
}
|
|
770
|
+
const ranked = fuzzyRank(candidates, query, getSettingItemFilterText);
|
|
771
|
+
const matched = ranked.map(result => result.item);
|
|
772
|
+
counts.set(tab, matched.length);
|
|
773
|
+
if (matched.length === 0) continue;
|
|
774
|
+
total += matched.length;
|
|
775
|
+
tabResults.push({
|
|
776
|
+
tab,
|
|
777
|
+
matched,
|
|
778
|
+
bestScore: ranked[0]?.score ?? 0,
|
|
779
|
+
order: SETTING_TABS.indexOf(tab),
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
tabResults.sort((a, b) => a.bestScore - b.bestScore || a.order - b.order);
|
|
784
|
+
for (const result of tabResults) {
|
|
785
|
+
const meta = TAB_METADATA[result.tab];
|
|
786
|
+
items.push({
|
|
787
|
+
id: `__tab:${result.tab}`,
|
|
788
|
+
label: `${theme.symbol(meta.icon)} ${meta.label}`,
|
|
789
|
+
currentValue: "",
|
|
790
|
+
heading: true,
|
|
791
|
+
});
|
|
792
|
+
this.#searchFirstMatch.set(result.tab, result.matched[0]?.id ?? "");
|
|
793
|
+
items.push(...result.matched);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
this.#searchList.setItems(items);
|
|
797
|
+
this.#searchMatchCount = total;
|
|
798
|
+
this.#tabBar.setTabs(
|
|
799
|
+
this.#buildSearchTabs(
|
|
800
|
+
counts,
|
|
801
|
+
tabResults.map(result => result.tab),
|
|
802
|
+
),
|
|
803
|
+
);
|
|
804
|
+
this.#syncTabBarToSelection(this.#searchList.getSelectedItem());
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Leave search mode. With `jumpToSelection`, land on the tab containing
|
|
809
|
+
* the selected result and keep it selected there — search doubles as
|
|
810
|
+
* navigation. Otherwise restore the pre-search tab.
|
|
811
|
+
*/
|
|
812
|
+
#endSearch(jumpToSelection: boolean): void {
|
|
813
|
+
if (!this.#searchList) return;
|
|
814
|
+
const selected = jumpToSelection ? this.#searchList.getSelectedItem() : undefined;
|
|
815
|
+
const selectedDef = selected ? getSettingDef(this.#context.settings.entries, selected.id) : undefined;
|
|
816
|
+
const targetTab: SettingTab | "plugins" = selectedDef?.tab ?? this.#preSearchTabId;
|
|
817
|
+
|
|
818
|
+
this.#searchQuery = "";
|
|
819
|
+
this.#searchFirstMatch.clear();
|
|
820
|
+
this.#searchMatchCount = 0;
|
|
821
|
+
this.#tabBar.setTabs(getSettingsTabs(), targetTab);
|
|
822
|
+
this.#switchToTab(targetTab);
|
|
823
|
+
if (selectedDef) {
|
|
824
|
+
this.#currentList?.selectItem(selectedDef.path);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/** Matching tabs first (counts attached), ordered by best result score; the rest stay muted at the end. */
|
|
829
|
+
#buildSearchTabs(counts: Map<SettingTab, number>, matchedTabOrder: readonly SettingTab[]): Tab[] {
|
|
830
|
+
const matched: Tab[] = [];
|
|
831
|
+
const empty: Tab[] = [];
|
|
832
|
+
const matchedIds = new Set<SettingTab>(matchedTabOrder);
|
|
833
|
+
for (const id of matchedTabOrder) {
|
|
834
|
+
const meta = TAB_METADATA[id];
|
|
835
|
+
const icon = theme.symbol(meta.icon);
|
|
836
|
+
const count = counts.get(id) ?? 0;
|
|
837
|
+
if (count > 0) {
|
|
838
|
+
matched.push({ id, label: `${icon} ${meta.label} (${count})`, short: `${icon} ${count}` });
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
for (const id of SETTING_TABS) {
|
|
842
|
+
if (matchedIds.has(id)) continue;
|
|
843
|
+
const meta = TAB_METADATA[id];
|
|
844
|
+
const icon = theme.symbol(meta.icon);
|
|
845
|
+
empty.push({ id, label: `${icon} ${meta.label}`, short: icon, muted: true });
|
|
846
|
+
}
|
|
847
|
+
// Plugins hosts its own UI; it is not part of the schema-backed search.
|
|
848
|
+
empty.push({
|
|
849
|
+
id: "plugins",
|
|
850
|
+
label: `${theme.icon.package} Plugins`,
|
|
851
|
+
short: theme.icon.package,
|
|
852
|
+
muted: true,
|
|
853
|
+
});
|
|
854
|
+
return [...matched, ...empty];
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
#syncTabBarToSelection(item: SettingItem | undefined): void {
|
|
858
|
+
if (!this.#searchList || !item) return;
|
|
859
|
+
const def = getSettingDef(this.#context.settings.entries, item.id);
|
|
860
|
+
if (def) this.#tabBar.setActiveById(def.tab);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/** Value-change dispatch for the search result list (any tab's setting). */
|
|
864
|
+
#onSearchSettingChange(path: string, newValue: string): void {
|
|
865
|
+
const def = getSettingDef(this.#context.settings.entries, path);
|
|
866
|
+
if (!def) return;
|
|
867
|
+
if (def.type === "boolean") {
|
|
868
|
+
const boolValue = newValue === "true";
|
|
869
|
+
this.#context.settings.set(path, boolValue);
|
|
870
|
+
this.#callbacks.onChange(path, boolValue);
|
|
871
|
+
} else if (def.type === "enum") {
|
|
872
|
+
this.#context.settings.set(path, newValue);
|
|
873
|
+
this.#callbacks.onChange(path, newValue);
|
|
874
|
+
}
|
|
875
|
+
// Submenu/text types already persisted inside their own done callbacks.
|
|
876
|
+
if (def.tab === "appearance") {
|
|
877
|
+
this.#triggerStatusLinePreview();
|
|
878
|
+
}
|
|
879
|
+
// Values feed the searchable text and condition gates may have flipped:
|
|
880
|
+
// recompute results in place (selection is preserved by item id).
|
|
881
|
+
this.#setSearchQuery(this.#searchQuery);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/**
|
|
885
|
+
* Convert a setting definition to a SettingItem for the UI.
|
|
886
|
+
*/
|
|
887
|
+
#defToItem(def: SettingDef): SettingItem | null {
|
|
888
|
+
// Check condition: applies to every variant — booleans, enums, submenus, text inputs.
|
|
889
|
+
if (def.condition && !def.condition()) {
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const currentValue = this.#getCurrentValue(def);
|
|
894
|
+
const item = {
|
|
895
|
+
id: def.path,
|
|
896
|
+
label: def.label,
|
|
897
|
+
description: def.description,
|
|
898
|
+
warning: def.warning,
|
|
899
|
+
changed: this.#isChanged(def, currentValue),
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
switch (def.type) {
|
|
903
|
+
case "boolean":
|
|
904
|
+
return { ...item, currentValue: currentValue ? "true" : "false", values: ["true", "false"] };
|
|
905
|
+
|
|
906
|
+
case "enum":
|
|
907
|
+
return { ...item, currentValue: String(currentValue ?? ""), values: [...def.values] };
|
|
908
|
+
|
|
909
|
+
case "submenu":
|
|
910
|
+
return {
|
|
911
|
+
...item,
|
|
912
|
+
currentValue: this.#getSubmenuCurrentValue(def.path, currentValue),
|
|
913
|
+
submenu: (cv, done) => this.#createSubmenu(def, cv, done),
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
case "text":
|
|
917
|
+
return {
|
|
918
|
+
...item,
|
|
919
|
+
currentValue: this.#formatTextInputValue(def, currentValue),
|
|
920
|
+
submenu: (cv, done) => this.#createTextInput(def, cv, done),
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
case "providerLimits":
|
|
924
|
+
return {
|
|
925
|
+
...item,
|
|
926
|
+
currentValue: this.#formatProviderLimitsValue(currentValue),
|
|
927
|
+
submenu: (_cv, done) => this.#createProviderLimitsInput(done),
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
case "multiselect":
|
|
931
|
+
return {
|
|
932
|
+
...item,
|
|
933
|
+
currentValue: this.#formatMultiSelectValue(def, currentValue),
|
|
934
|
+
submenu: (_cv, done) => this.#createMultiSelect(def, done),
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Get the current value for a setting.
|
|
941
|
+
*/
|
|
942
|
+
#getCurrentValue(def: SettingDef): unknown {
|
|
943
|
+
return this.#context.settings.get(def.path);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
#isChanged(def: SettingDef, currentValue: unknown): boolean {
|
|
947
|
+
const defaultValue: unknown = def.defaultValue;
|
|
948
|
+
if (Array.isArray(currentValue) && Array.isArray(defaultValue)) {
|
|
949
|
+
return (
|
|
950
|
+
currentValue.length !== defaultValue.length ||
|
|
951
|
+
currentValue.some((entry, index) => entry !== defaultValue[index])
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
return !Object.is(currentValue, defaultValue);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
#getSubmenuCurrentValue(path: string, value: unknown): string {
|
|
958
|
+
const rawValue = String(value ?? "");
|
|
959
|
+
if (path === "compaction.thresholdPercent" && (rawValue === "-1" || rawValue === "")) {
|
|
960
|
+
return "default";
|
|
961
|
+
}
|
|
962
|
+
if (path === "compaction.thresholdTokens" && (rawValue === "-1" || rawValue === "")) {
|
|
963
|
+
return "default";
|
|
964
|
+
}
|
|
965
|
+
return rawValue;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/**
|
|
969
|
+
* Create a submenu for a submenu-type setting.
|
|
970
|
+
*/
|
|
971
|
+
#createSubmenu(
|
|
972
|
+
def: SettingDef & { type: "submenu" },
|
|
973
|
+
currentValue: string,
|
|
974
|
+
done: (value?: string) => void,
|
|
975
|
+
): Component {
|
|
976
|
+
let options = def.options;
|
|
977
|
+
|
|
978
|
+
// Special case: inject runtime options for thinking level
|
|
979
|
+
if (def.path === "defaultThinkingLevel") {
|
|
980
|
+
// Prepend `auto`; the rest are the model's runtime-supported efforts.
|
|
981
|
+
const levels: ConfiguredThinkingLevel[] = [AUTO_THINKING, ...this.#context.availableThinkingLevels];
|
|
982
|
+
options = levels.map(level => {
|
|
983
|
+
const baseOpt = options.find(o => o.value === level);
|
|
984
|
+
return baseOpt || { value: level, label: level };
|
|
985
|
+
});
|
|
986
|
+
} else if (def.path === "theme.dark" || def.path === "theme.light") {
|
|
987
|
+
options = this.#context.availableThemes.map(t => ({ value: t, label: t }));
|
|
988
|
+
} else if (def.path === "composer.shape") {
|
|
989
|
+
options = getComposerShapeOptions();
|
|
990
|
+
}
|
|
991
|
+
// Preview handlers
|
|
992
|
+
let onPreview: ((value: string) => void | Promise<void>) | undefined;
|
|
993
|
+
let onPreviewCancel: (() => void) | undefined;
|
|
994
|
+
let footer: Component | undefined;
|
|
995
|
+
|
|
996
|
+
const activeThemeBeforePreview = getCurrentThemeName() ?? currentValue;
|
|
997
|
+
if (def.path === "theme.dark" || def.path === "theme.light") {
|
|
998
|
+
onPreview = value => {
|
|
999
|
+
return this.#callbacks.onThemePreview?.(value);
|
|
1000
|
+
};
|
|
1001
|
+
onPreviewCancel = () => {
|
|
1002
|
+
this.#callbacks.onThemePreview?.(activeThemeBeforePreview);
|
|
1003
|
+
};
|
|
1004
|
+
} else if (def.path === "statusLine.preset") {
|
|
1005
|
+
onPreview = value => {
|
|
1006
|
+
const presetDef = getPreset(
|
|
1007
|
+
value as "default" | "minimal" | "compact" | "full" | "nerd" | "ascii" | "custom",
|
|
1008
|
+
);
|
|
1009
|
+
this.#callbacks.onStatusLinePreview?.({
|
|
1010
|
+
preset: value as StatusLinePreset,
|
|
1011
|
+
leftSegments: presetDef.leftSegments,
|
|
1012
|
+
rightSegments: presetDef.rightSegments,
|
|
1013
|
+
separator: presetDef.separator,
|
|
1014
|
+
});
|
|
1015
|
+
};
|
|
1016
|
+
onPreviewCancel = () => {
|
|
1017
|
+
const currentPreset = this.#context.settings.get("statusLine.preset") as StatusLinePreset;
|
|
1018
|
+
const presetDef = getPreset(currentPreset);
|
|
1019
|
+
this.#callbacks.onStatusLinePreview?.({
|
|
1020
|
+
preset: currentPreset,
|
|
1021
|
+
leftSegments: presetDef.leftSegments,
|
|
1022
|
+
rightSegments: presetDef.rightSegments,
|
|
1023
|
+
separator: presetDef.separator,
|
|
1024
|
+
});
|
|
1025
|
+
};
|
|
1026
|
+
} else if (def.path === "statusLine.separator") {
|
|
1027
|
+
onPreview = value => {
|
|
1028
|
+
this.#callbacks.onStatusLinePreview?.({ separator: value as StatusLineSeparatorStyle });
|
|
1029
|
+
};
|
|
1030
|
+
onPreviewCancel = () => {
|
|
1031
|
+
const separator = this.#context.settings.get("statusLine.separator") as StatusLineSeparatorStyle;
|
|
1032
|
+
this.#callbacks.onStatusLinePreview?.({ separator });
|
|
1033
|
+
};
|
|
1034
|
+
} else if (def.path === "statusLine.contextLine") {
|
|
1035
|
+
onPreview = value => {
|
|
1036
|
+
this.#callbacks.onStatusLinePreview?.({ contextLine: value as ContextLineMode });
|
|
1037
|
+
};
|
|
1038
|
+
onPreviewCancel = () => {
|
|
1039
|
+
this.#callbacks.onStatusLinePreview?.({
|
|
1040
|
+
contextLine: this.#context.settings.get("statusLine.contextLine") as ContextLineMode,
|
|
1041
|
+
});
|
|
1042
|
+
};
|
|
1043
|
+
} else if (def.path === "snapcompact.shape") {
|
|
1044
|
+
const shapePreview = new SnapcompactShapePreview(currentValue, {
|
|
1045
|
+
model: this.#context.model,
|
|
1046
|
+
imageBudget: this.#context.imageBudget,
|
|
1047
|
+
requestRender: this.#context.requestRender,
|
|
1048
|
+
});
|
|
1049
|
+
onPreview = value => shapePreview.setValue(value);
|
|
1050
|
+
footer = shapePreview;
|
|
1051
|
+
} else if (def.path === "composer.shape") {
|
|
1052
|
+
const shapePreview = new ComposerShapePreview(String(currentValue ?? "band"), {
|
|
1053
|
+
requestRender: this.#context.requestRender,
|
|
1054
|
+
status: this.#context.composerPreviewStatus,
|
|
1055
|
+
});
|
|
1056
|
+
onPreview = value => shapePreview.setValue(value);
|
|
1057
|
+
footer = shapePreview;
|
|
1058
|
+
}
|
|
1059
|
+
// Provide status line preview for theme selection
|
|
1060
|
+
const isThemeSetting = def.path === "theme.dark" || def.path === "theme.light";
|
|
1061
|
+
const getPreview = isThemeSetting ? this.#callbacks.getStatusLinePreview : undefined;
|
|
1062
|
+
|
|
1063
|
+
return createSettingsSelectField(
|
|
1064
|
+
def.label,
|
|
1065
|
+
def.description,
|
|
1066
|
+
options,
|
|
1067
|
+
currentValue,
|
|
1068
|
+
value => {
|
|
1069
|
+
this.#setSettingValue(def.path, value);
|
|
1070
|
+
this.#callbacks.onChange(def.path, value);
|
|
1071
|
+
done(value);
|
|
1072
|
+
},
|
|
1073
|
+
() => {
|
|
1074
|
+
onPreviewCancel?.();
|
|
1075
|
+
done();
|
|
1076
|
+
},
|
|
1077
|
+
onPreview,
|
|
1078
|
+
getPreview,
|
|
1079
|
+
footer,
|
|
1080
|
+
this.#context.requestRender,
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Create a text input submenu for a plain string setting.
|
|
1086
|
+
*/
|
|
1087
|
+
#createTextInput(
|
|
1088
|
+
def: SettingDef & { type: "text" },
|
|
1089
|
+
_currentValue: string,
|
|
1090
|
+
done: (value?: string) => void,
|
|
1091
|
+
): Component {
|
|
1092
|
+
this.#textInputActive = true;
|
|
1093
|
+
const wrappedDone = (value?: string) => {
|
|
1094
|
+
this.#textInputActive = false;
|
|
1095
|
+
done(value);
|
|
1096
|
+
};
|
|
1097
|
+
return createSettingsTextField(
|
|
1098
|
+
def.label,
|
|
1099
|
+
def.description,
|
|
1100
|
+
this.#formatTextInputEditValue(def.path, this.#context.settings.get(def.path)),
|
|
1101
|
+
def.secret,
|
|
1102
|
+
value => {
|
|
1103
|
+
// Empty string clears the setting; undefined-typed string settings
|
|
1104
|
+
// store "" which the browser.ts expandPath ignores (no-op fallback).
|
|
1105
|
+
this.#setSettingValue(def.path, value);
|
|
1106
|
+
this.#callbacks.onChange(def.path, this.#context.settings.get(def.path));
|
|
1107
|
+
wrappedDone(this.#formatTextInputValue(def, this.#context.settings.get(def.path)));
|
|
1108
|
+
},
|
|
1109
|
+
() => wrappedDone(),
|
|
1110
|
+
this.#context.requestRender,
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
#createProviderLimitsInput(done: (value?: string) => void): Container {
|
|
1115
|
+
return new ProviderLimitsSubmenu(
|
|
1116
|
+
this.#context.settings,
|
|
1117
|
+
this.#context.providers,
|
|
1118
|
+
value => {
|
|
1119
|
+
this.#callbacks.onChange("providers.maxInFlightRequests", value);
|
|
1120
|
+
done(this.#formatProviderLimitsValue(value));
|
|
1121
|
+
},
|
|
1122
|
+
() => done(),
|
|
1123
|
+
this.#context.requestRender,
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
#formatProviderLimitsValue(value: unknown): string {
|
|
1128
|
+
const limits = this.#context.settings.normalizeProviderLimits(value);
|
|
1129
|
+
const entries = Object.entries(limits).sort(([a], [b]) => a.localeCompare(b));
|
|
1130
|
+
if (entries.length === 0) return "Unlimited";
|
|
1131
|
+
return entries.map(([provider, limit]) => `${provider}: ${limit}`).join(", ");
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
#getMultiSelectOptions(def: SettingDef & { type: "multiselect" }) {
|
|
1135
|
+
if (def.path !== "providers.webSearchOrder") return def.options;
|
|
1136
|
+
const excluded: unknown = this.#context.settings.get("providers.webSearchExclude");
|
|
1137
|
+
if (!Array.isArray(excluded)) return def.options;
|
|
1138
|
+
return def.options.filter(option => !excluded.includes(option.value));
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
#createMultiSelect(def: SettingDef & { type: "multiselect" }, done: (value?: string) => void): Container {
|
|
1142
|
+
const options = this.#getMultiSelectOptions(def);
|
|
1143
|
+
const current: unknown = this.#context.settings.get(def.path);
|
|
1144
|
+
const initial = Array.isArray(current)
|
|
1145
|
+
? current.filter((entry): entry is string => typeof entry === "string")
|
|
1146
|
+
: [];
|
|
1147
|
+
return new MultiSelectSubmenu(
|
|
1148
|
+
def.label,
|
|
1149
|
+
def.description,
|
|
1150
|
+
options,
|
|
1151
|
+
initial,
|
|
1152
|
+
def.ordered,
|
|
1153
|
+
value => {
|
|
1154
|
+
this.#context.settings.set(def.path, value);
|
|
1155
|
+
this.#callbacks.onChange(def.path, value);
|
|
1156
|
+
},
|
|
1157
|
+
() => done(this.#formatMultiSelectValue(def, this.#context.settings.get(def.path))),
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
#formatMultiSelectValue(def: SettingDef & { type: "multiselect" }, value: unknown): string {
|
|
1162
|
+
const options = this.#getMultiSelectOptions(def);
|
|
1163
|
+
const labels = Array.isArray(value)
|
|
1164
|
+
? value.flatMap(entry => {
|
|
1165
|
+
if (typeof entry !== "string") return [];
|
|
1166
|
+
const option = options.find(candidate => candidate.value === entry);
|
|
1167
|
+
return option ? [option.label] : [];
|
|
1168
|
+
})
|
|
1169
|
+
: [];
|
|
1170
|
+
if (labels.length === 0) return def.ordered ? "default" : "none";
|
|
1171
|
+
return def.ordered ? labels.join(" → ") : labels.join(", ");
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
#formatTextInputValue(def: SettingDef & { type: "text" }, value: unknown): string {
|
|
1175
|
+
if (def.secret) return value ? "••••••••" : "";
|
|
1176
|
+
return this.#formatTextInputEditValue(def.path, value);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
#formatTextInputEditValue(_path: string, value: unknown): string {
|
|
1180
|
+
if (value === undefined || value === null) return "";
|
|
1181
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
1182
|
+
return String(value);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Set a setting value, handling type conversion.
|
|
1187
|
+
*/
|
|
1188
|
+
#setSettingValue(path: string, value: string): void {
|
|
1189
|
+
const currentValue = this.#context.settings.get(path);
|
|
1190
|
+
const schemaType = getSettingDef(this.#context.settings.entries, path)?.schemaType;
|
|
1191
|
+
if (path === "compaction.thresholdPercent" && value === "default") {
|
|
1192
|
+
this.#context.settings.set(path, -1);
|
|
1193
|
+
} else if (path === "compaction.thresholdTokens" && value === "default") {
|
|
1194
|
+
this.#context.settings.set(path, -1);
|
|
1195
|
+
} else if (schemaType === "record") {
|
|
1196
|
+
let parsed: unknown;
|
|
1197
|
+
try {
|
|
1198
|
+
parsed = JSON.parse(value || "{}");
|
|
1199
|
+
} catch {
|
|
1200
|
+
throw new Error(`Invalid record JSON for ${path}`);
|
|
1201
|
+
}
|
|
1202
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1203
|
+
throw new Error(`Invalid record JSON for ${path}`);
|
|
1204
|
+
}
|
|
1205
|
+
if (path === "providers.maxInFlightRequests") {
|
|
1206
|
+
parsed = this.#context.settings.validateProviderLimits(parsed);
|
|
1207
|
+
}
|
|
1208
|
+
this.#context.settings.set(path, parsed);
|
|
1209
|
+
} else if (typeof currentValue === "number") {
|
|
1210
|
+
this.#context.settings.set(path, Number(value));
|
|
1211
|
+
} else if (typeof currentValue === "boolean") {
|
|
1212
|
+
this.#context.settings.set(path, value === "true");
|
|
1213
|
+
} else {
|
|
1214
|
+
this.#context.settings.set(path, value);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* Show a settings tab using definitions.
|
|
1220
|
+
*/
|
|
1221
|
+
#showSettingsTab(tabId: SettingTab): void {
|
|
1222
|
+
const defs = getSettingsForTab(this.#context.settings.entries, tabId);
|
|
1223
|
+
|
|
1224
|
+
const items = this.#buildItemsForDefs(defs);
|
|
1225
|
+
// Mirror SettingsList's section detection (leading ungrouped items form
|
|
1226
|
+
// an implicit section) so the footer hint only advertises PgUp/PgDn
|
|
1227
|
+
// when the jump actually changes sections.
|
|
1228
|
+
const sectionCount = items.filter(item => item.heading).length + (items.length > 0 && !items[0].heading ? 1 : 0);
|
|
1229
|
+
this.#hasSectionJump = sectionCount >= 2;
|
|
1230
|
+
|
|
1231
|
+
this.#currentList = new SettingsList(
|
|
1232
|
+
items,
|
|
1233
|
+
10,
|
|
1234
|
+
getSettingsListTheme(),
|
|
1235
|
+
(id, newValue) => {
|
|
1236
|
+
const def = defs.find(d => d.path === id);
|
|
1237
|
+
if (!def) return;
|
|
1238
|
+
|
|
1239
|
+
const path = def.path;
|
|
1240
|
+
|
|
1241
|
+
if (def.type === "boolean") {
|
|
1242
|
+
const boolValue = newValue === "true";
|
|
1243
|
+
this.#context.settings.set(path, boolValue);
|
|
1244
|
+
this.#callbacks.onChange(path, boolValue);
|
|
1245
|
+
|
|
1246
|
+
if (tabId === "appearance") {
|
|
1247
|
+
this.#triggerStatusLinePreview();
|
|
1248
|
+
}
|
|
1249
|
+
} else if (def.type === "enum") {
|
|
1250
|
+
this.#context.settings.set(path, newValue);
|
|
1251
|
+
this.#callbacks.onChange(path, newValue);
|
|
1252
|
+
}
|
|
1253
|
+
// Submenu/text types already persisted the value inside their own
|
|
1254
|
+
// done callbacks before SettingsList re-dispatches here. Re-run the
|
|
1255
|
+
// definition-to-item mapping so condition-gated settings (e.g. the
|
|
1256
|
+
// Hindsight cluster guarded by memory.backend) appear/disappear
|
|
1257
|
+
// immediately instead of waiting for the next tab switch.
|
|
1258
|
+
this.#refreshCurrentTabItems(defs);
|
|
1259
|
+
},
|
|
1260
|
+
() => this.#callbacks.onCancel(),
|
|
1261
|
+
// The selector owns type-to-search and the footer hint; pin the
|
|
1262
|
+
// split sidebar width so the divider never jumps between tabs.
|
|
1263
|
+
{ typeToSearch: false, hint: "", sidebarWidth: this.#sidebarWidth },
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* Map a definition list to UI items, dropping any whose condition is false.
|
|
1269
|
+
* Inserts a heading row whenever the (group-sorted) definition list crosses
|
|
1270
|
+
* into a new group; groups whose items are all condition-hidden emit none.
|
|
1271
|
+
*/
|
|
1272
|
+
#buildItemsForDefs(defs: SettingDef[]): SettingItem[] {
|
|
1273
|
+
const items: SettingItem[] = [];
|
|
1274
|
+
let lastGroup: string | undefined;
|
|
1275
|
+
for (const def of defs) {
|
|
1276
|
+
const item = this.#defToItem(def);
|
|
1277
|
+
if (!item) continue;
|
|
1278
|
+
if (def.group && def.group !== lastGroup) {
|
|
1279
|
+
items.push({ id: `__heading:${def.group}`, label: def.group, currentValue: "", heading: true });
|
|
1280
|
+
lastGroup = def.group;
|
|
1281
|
+
}
|
|
1282
|
+
items.push(item);
|
|
1283
|
+
}
|
|
1284
|
+
return items;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/** Re-evaluate condition gates against the current settings and refresh the active list. */
|
|
1288
|
+
#refreshCurrentTabItems(defs: SettingDef[]): void {
|
|
1289
|
+
if (this.#currentTabId === "plugins" || !this.#currentList) return;
|
|
1290
|
+
this.#currentList.setItems(this.#buildItemsForDefs(defs));
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
/**
|
|
1294
|
+
* Get the status line preview string.
|
|
1295
|
+
*/
|
|
1296
|
+
#getStatusPreviewString(): string {
|
|
1297
|
+
if (this.#callbacks.getStatusLinePreview) {
|
|
1298
|
+
return this.#callbacks.getStatusLinePreview();
|
|
1299
|
+
}
|
|
1300
|
+
return theme.fg("dim", "(preview not available)");
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* Trigger status line preview with current settings.
|
|
1305
|
+
*/
|
|
1306
|
+
#triggerStatusLinePreview(): void {
|
|
1307
|
+
const statusLineSettings: StatusLinePreviewSettings = {
|
|
1308
|
+
preset: this.#context.settings.get("statusLine.preset") as StatusLinePreset,
|
|
1309
|
+
leftSegments: this.#context.settings.get("statusLine.leftSegments") as StatusLineSegmentId[],
|
|
1310
|
+
rightSegments: this.#context.settings.get("statusLine.rightSegments") as StatusLineSegmentId[],
|
|
1311
|
+
separator: this.#context.settings.get("statusLine.separator") as StatusLineSeparatorStyle,
|
|
1312
|
+
sessionAccent: this.#context.settings.get("statusLine.sessionAccent") as boolean,
|
|
1313
|
+
transparent: this.#context.settings.get("statusLine.transparent") as boolean,
|
|
1314
|
+
};
|
|
1315
|
+
this.#callbacks.onStatusLinePreview?.(statusLineSettings);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
#showPluginsTab(): void {
|
|
1319
|
+
this.#pluginComponent = new PluginSettingsComponent(this.#context.plugins, {
|
|
1320
|
+
onClose: () => this.#callbacks.onCancel(),
|
|
1321
|
+
onPluginChanged: () => this.#callbacks.onPluginsChanged?.(),
|
|
1322
|
+
requestRender: this.#context.requestRender,
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
handleInput(data: string): void {
|
|
1327
|
+
// SGR mouse reports (the fullscreen overlay enables tracking).
|
|
1328
|
+
if (data.startsWith("\x1b[<")) {
|
|
1329
|
+
this.#handleMouse(data);
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// Text-input submenus take every byte: arrow keys must reach the
|
|
1334
|
+
// cursor and Tab must not switch tabs.
|
|
1335
|
+
if (this.#textInputActive) {
|
|
1336
|
+
(this.#searchList ?? this.#currentList)?.handleInput(data);
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
const activeList = this.#searchList ?? this.#currentList;
|
|
1341
|
+
|
|
1342
|
+
// An open submenu owns input entirely — Tab/arrows/typing belong to it.
|
|
1343
|
+
if (activeList?.hasOpenSubmenu()) {
|
|
1344
|
+
activeList.handleInput(data);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if (this.#searchList) {
|
|
1349
|
+
this.#handleSearchModeInput(data, this.#searchList);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Tab toggles keyboard focus between section headings and setting rows
|
|
1354
|
+
// (fast section hopping); tabs without sections keep Tab switching tabs.
|
|
1355
|
+
if (matchesKey(data, "tab") || matchesKey(data, "shift+tab")) {
|
|
1356
|
+
if (this.#currentList?.hasSectionFocusTargets()) {
|
|
1357
|
+
this.#currentList.toggleSectionFocus();
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
this.#tabBar.handleInput(data);
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
if (matchesKey(data, "left") || matchesKey(data, "right")) {
|
|
1364
|
+
this.#tabBar.handleInput(data);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// Printable characters start a search across every settings tab. The
|
|
1369
|
+
// plugins tab keeps its own local filtering instead.
|
|
1370
|
+
if (this.#currentTabId !== "plugins") {
|
|
1371
|
+
const printable = extractPrintableText(data);
|
|
1372
|
+
if (printable !== undefined && printable.trim().length > 0) {
|
|
1373
|
+
this.#startSearch(printable);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
if (this.#currentList) {
|
|
1379
|
+
this.#currentList.handleInput(data);
|
|
1380
|
+
} else if (this.#pluginComponent) {
|
|
1381
|
+
this.#pluginComponent.handleInput(data);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
#handleSearchModeInput(data: string, list: SettingsList): void {
|
|
1386
|
+
const kb = getKeybindings();
|
|
1387
|
+
if (kb.matches(data, "tui.select.cancel")) {
|
|
1388
|
+
// Exit search, landing on the tab of the selected result.
|
|
1389
|
+
this.#endSearch(true);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
if (matchesKey(data, "tab") || matchesKey(data, "shift+tab")) {
|
|
1393
|
+
// Jump between tabs that have matches (muted tabs are skipped).
|
|
1394
|
+
this.#tabBar.handleInput(data);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
// Selection, paging, and activation stay with the result list.
|
|
1398
|
+
if (
|
|
1399
|
+
kb.matches(data, "tui.select.up") ||
|
|
1400
|
+
kb.matches(data, "tui.select.down") ||
|
|
1401
|
+
kb.matches(data, "tui.select.pageUp") ||
|
|
1402
|
+
kb.matches(data, "tui.select.pageDown") ||
|
|
1403
|
+
kb.matches(data, "tui.select.confirm") ||
|
|
1404
|
+
data === "\n"
|
|
1405
|
+
) {
|
|
1406
|
+
list.handleInput(data);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
// Everything else edits the query like a regular single-line editor:
|
|
1410
|
+
// cursor movement, word ops, kill ring, undo, paste.
|
|
1411
|
+
this.#searchInput.handleInput(data);
|
|
1412
|
+
const value = this.#searchInput.getValue();
|
|
1413
|
+
if (value !== this.#searchQuery) this.#setSearchQuery(value);
|
|
1414
|
+
}
|
|
1415
|
+
}
|