apex-code 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +257 -5
- package/README.md +624 -0
- package/dist/bun/cli.d.ts +3 -1
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +3 -9
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/runtime-setup.d.ts +2 -0
- package/dist/bun/runtime-setup.d.ts.map +1 -0
- package/dist/bun/runtime-setup.js +9 -0
- package/dist/bun/runtime-setup.js.map +1 -0
- package/dist/bun/sandbox-env-setup.d.ts +2 -0
- package/dist/bun/sandbox-env-setup.d.ts.map +1 -0
- package/dist/bun/sandbox-env-setup.js +4 -0
- package/dist/bun/sandbox-env-setup.js.map +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +16 -5
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +2 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/file-processor.d.ts +1 -1
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli/session-picker.d.ts +1 -1
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/cli/setup.d.ts +2 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +13 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +16 -8
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +85 -15
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +685 -266
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +4 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +55 -0
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bug-report.d.ts +176 -0
- package/dist/core/bug-report.d.ts.map +1 -0
- package/dist/core/bug-report.js +291 -0
- package/dist/core/bug-report.js.map +1 -0
- package/dist/core/cache-stats.d.ts.map +1 -1
- package/dist/core/cache-stats.js +12 -1
- package/dist/core/cache-stats.js.map +1 -1
- package/dist/core/cache-warmer.d.ts +103 -0
- package/dist/core/cache-warmer.d.ts.map +1 -0
- package/dist/core/cache-warmer.js +356 -0
- package/dist/core/cache-warmer.js.map +1 -0
- package/dist/core/compaction/branch-summarization.d.ts +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +4 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +5 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +163 -66
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/context/pipeline.d.ts +8 -8
- package/dist/core/context/pipeline.d.ts.map +1 -1
- package/dist/core/context/pipeline.js +21 -12
- package/dist/core/context/pipeline.js.map +1 -1
- package/dist/core/crash-log.d.ts +27 -0
- package/dist/core/crash-log.d.ts.map +1 -0
- package/dist/core/crash-log.js +139 -0
- package/dist/core/crash-log.js.map +1 -0
- package/dist/core/export-html/template.js +6 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/jiti-loader.d.ts +2 -0
- package/dist/core/extensions/jiti-loader.d.ts.map +1 -0
- package/dist/core/extensions/jiti-loader.js +4 -0
- package/dist/core/extensions/jiti-loader.js.map +1 -0
- package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
- package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
- package/dist/core/extensions/jiti-static-loader.js +4 -0
- package/dist/core/extensions/jiti-static-loader.js.map +1 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +40 -59
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +24 -8
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +195 -72
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +142 -55
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/virtual-modules.d.ts +3 -0
- package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
- package/dist/core/extensions/virtual-modules.js +41 -0
- package/dist/core/extensions/virtual-modules.js.map +1 -0
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js +1 -20
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +2 -0
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts +6 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +5 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/messages.d.ts +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +1 -0
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-config.d.ts +168 -20
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +44 -18
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +8 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +4 -3
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +1 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +12 -6
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/permissions/store.d.ts.map +1 -1
- package/dist/core/permissions/store.js +11 -8
- package/dist/core/permissions/store.js.map +1 -1
- package/dist/core/prompt-templates.d.ts +6 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +61 -35
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/provider-composer.d.ts +4 -2
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +29 -2
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/radius.d.ts +3 -0
- package/dist/core/radius.d.ts.map +1 -1
- package/dist/core/radius.js +6 -0
- package/dist/core/radius.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +6 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +63 -42
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-export.d.ts +6 -2
- package/dist/core/session-export.d.ts.map +1 -1
- package/dist/core/session-export.js +14 -13
- package/dist/core/session-export.js.map +1 -1
- package/dist/core/session-manager.d.ts +66 -15
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +295 -115
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +27 -4
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +55 -7
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +5 -4
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts +49 -6
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +140 -79
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +7 -8
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +20 -115
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts +1 -12
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +6 -204
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +4 -55
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +4 -60
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +4 -49
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts +4 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +10 -125
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/renderers/bash.d.ts +18 -0
- package/dist/core/tools/renderers/bash.d.ts.map +1 -0
- package/dist/core/tools/renderers/bash.js +126 -0
- package/dist/core/tools/renderers/bash.js.map +1 -0
- package/dist/core/tools/renderers/edit.d.ts +23 -0
- package/dist/core/tools/renderers/edit.d.ts.map +1 -0
- package/dist/core/tools/renderers/edit.js +207 -0
- package/dist/core/tools/renderers/edit.js.map +1 -0
- package/dist/core/tools/renderers/find.d.ts +10 -0
- package/dist/core/tools/renderers/find.d.ts.map +1 -0
- package/dist/core/tools/renderers/find.js +64 -0
- package/dist/core/tools/renderers/find.js.map +1 -0
- package/dist/core/tools/renderers/grep.d.ts +10 -0
- package/dist/core/tools/renderers/grep.d.ts.map +1 -0
- package/dist/core/tools/renderers/grep.js +69 -0
- package/dist/core/tools/renderers/grep.js.map +1 -0
- package/dist/core/tools/renderers/index.d.ts +34 -0
- package/dist/core/tools/renderers/index.d.ts.map +1 -0
- package/dist/core/tools/renderers/index.js +47 -0
- package/dist/core/tools/renderers/index.js.map +1 -0
- package/dist/core/tools/renderers/ls.d.ts +10 -0
- package/dist/core/tools/renderers/ls.d.ts.map +1 -0
- package/dist/core/tools/renderers/ls.js +58 -0
- package/dist/core/tools/renderers/ls.js.map +1 -0
- package/dist/core/tools/renderers/read.d.ts +11 -0
- package/dist/core/tools/renderers/read.d.ts.map +1 -0
- package/dist/core/tools/renderers/read.js +132 -0
- package/dist/core/tools/renderers/read.js.map +1 -0
- package/dist/core/tools/renderers/write.d.ts +10 -0
- package/dist/core/tools/renderers/write.d.ts.map +1 -0
- package/dist/core/tools/renderers/write.js +152 -0
- package/dist/core/tools/renderers/write.js.map +1 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +6 -146
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/usage-totals.d.ts +1 -1
- package/dist/core/usage-totals.d.ts.map +1 -1
- package/dist/core/usage-totals.js +5 -1
- package/dist/core/usage-totals.js.map +1 -1
- package/dist/extensions/llama/client.d.ts +2 -0
- package/dist/extensions/llama/client.d.ts.map +1 -1
- package/dist/extensions/llama/client.js +7 -3
- package/dist/extensions/llama/client.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +20 -5
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +21 -13
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/bug-report.d.ts +16 -0
- package/dist/modes/interactive/bug-report.d.ts.map +1 -0
- package/dist/modes/interactive/bug-report.js +167 -0
- package/dist/modes/interactive/bug-report.js.map +1 -0
- package/dist/modes/interactive/chat-viewport.d.ts +20 -0
- package/dist/modes/interactive/chat-viewport.d.ts.map +1 -0
- package/dist/modes/interactive/chat-viewport.js +28 -0
- package/dist/modes/interactive/chat-viewport.js.map +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts +7 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +63 -17
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +12 -5
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +12 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +38 -0
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/error-summary.d.ts +11 -0
- package/dist/modes/interactive/components/error-summary.d.ts.map +1 -0
- package/dist/modes/interactive/components/error-summary.js +31 -0
- package/dist/modes/interactive/components/error-summary.js.map +1 -0
- package/dist/modes/interactive/components/extension-editor.d.ts +4 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +6 -1
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-input.d.ts +2 -0
- package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-input.js +6 -0
- package/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +2 -0
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +4 -0
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +4 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +3 -3
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +13 -15
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +5 -5
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +74 -48
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +5 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +35 -10
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-indicator.js +10 -3
- package/dist/modes/interactive/components/status-indicator.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +6 -6
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +27 -5
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +64 -42
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +9 -0
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/trust-selector.js +2 -2
- package/dist/modes/interactive/components/trust-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +43 -29
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +431 -166
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +2 -1
- package/dist/modes/interactive/theme/light.json +2 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +5 -0
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-json.d.ts +84 -0
- package/dist/modes/interactive/theme/theme-json.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme-json.js +130 -0
- package/dist/modes/interactive/theme/theme-json.js.map +1 -0
- package/dist/modes/interactive/theme/theme-schema.json +6 -2
- package/dist/modes/interactive/theme/theme.d.ts +14 -5
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +18 -119
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/interactive/tui-renderer.d.ts +21 -0
- package/dist/modes/interactive/tui-renderer.d.ts.map +1 -0
- package/dist/modes/interactive/tui-renderer.js +66 -0
- package/dist/modes/interactive/tui-renderer.js.map +1 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -2
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/testing/replay/recorded-provider.d.ts +2 -2
- package/dist/testing/replay/recorded-provider.d.ts.map +1 -1
- package/dist/testing/replay/recorded-provider.js +2 -5
- package/dist/testing/replay/recorded-provider.js.map +1 -1
- package/dist/testing/replay/runner.d.ts.map +1 -1
- package/dist/testing/replay/runner.js +0 -1
- package/dist/testing/replay/runner.js.map +1 -1
- package/dist/utils/clipboard-command.d.ts +7 -0
- package/dist/utils/clipboard-command.d.ts.map +1 -0
- package/dist/utils/clipboard-command.js +45 -0
- package/dist/utils/clipboard-command.js.map +1 -0
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +53 -81
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +111 -121
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/exif-orientation.d.ts.map +1 -1
- package/dist/utils/exif-orientation.js +2 -3
- package/dist/utils/exif-orientation.js.map +1 -1
- package/dist/utils/mime.d.ts.map +1 -1
- package/dist/utils/mime.js +1 -1
- package/dist/utils/mime.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +21 -21
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/dist/utils/tool-result-images.d.ts +3 -1
- package/dist/utils/tool-result-images.d.ts.map +1 -1
- package/dist/utils/tool-result-images.js +4 -1
- package/dist/utils/tool-result-images.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +11 -2
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/wsl.d.ts +3 -0
- package/dist/utils/wsl.d.ts.map +1 -0
- package/dist/utils/wsl.js +15 -0
- package/dist/utils/wsl.js.map +1 -0
- package/dist/utils/zip.d.ts +7 -0
- package/dist/utils/zip.d.ts.map +1 -0
- package/dist/utils/zip.js +60 -0
- package/dist/utils/zip.js.map +1 -0
- package/docs/cli-integration.md +107 -0
- package/docs/cli.md +269 -0
- package/docs/compaction.md +73 -24
- package/docs/configuration.md +45 -0
- package/docs/containerization.md +22 -21
- package/docs/custom-provider.md +21 -12
- package/docs/development.md +19 -0
- package/docs/docs.json +139 -95
- package/docs/extensions.md +243 -371
- package/docs/how-pi-works.md +49 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/index.md +3 -3
- package/docs/json.md +7 -3
- package/docs/keybindings.md +57 -54
- package/docs/llama-cpp.md +2 -2
- package/docs/message-types.md +261 -0
- package/docs/models.md +103 -49
- package/docs/packages.md +49 -47
- package/docs/prompt-templates.md +29 -56
- package/docs/providers.md +91 -139
- package/docs/quickstart.md +42 -14
- package/docs/rpc-commands.md +854 -0
- package/docs/rpc-extension-ui.md +200 -0
- package/docs/rpc.md +121 -223
- package/docs/sdk.md +99 -124
- package/docs/security.md +7 -7
- package/docs/session-format.md +85 -82
- package/docs/sessions.md +41 -57
- package/docs/settings.md +94 -87
- package/docs/shell-aliases.md +68 -3
- package/docs/skills.md +62 -49
- package/docs/slash-commands.md +60 -0
- package/docs/terminal-setup.md +86 -55
- package/docs/termux.md +65 -75
- package/docs/themes.md +64 -86
- package/docs/tmux.md +29 -7
- package/docs/tui.md +67 -122
- package/docs/usage.md +31 -42
- package/docs/windows.md +41 -13
- package/examples/README.md +16 -2
- package/examples/extensions/README.md +0 -1
- package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/dynamic-resources/dynamic.json +2 -0
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/prompt-customizer.ts +19 -67
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/plugins/pi-example-plugin/README.md +38 -0
- package/examples/plugins/pi-example-plugin/package.json +10 -0
- package/examples/plugins/pi-example-plugin/src/contract.ts +13 -0
- package/examples/plugins/pi-example-plugin/src/session.ts +24 -0
- package/examples/plugins/pi-example-plugin/src/tui.ts +31 -0
- package/examples/rpc-client.ts +35 -0
- package/examples/rpc-extension-ui.ts +25 -5
- package/examples/sdk/README.md +1 -1
- package/npm-shrinkwrap.json +861 -513
- package/package.json +27 -18
- package/dist/bun/register-bedrock.d.ts +0 -2
- package/dist/bun/register-bedrock.d.ts.map +0 -1
- package/dist/bun/register-bedrock.js +0 -4
- package/dist/bun/register-bedrock.js.map +0 -1
- package/dist/cli/experimental/auth.d.ts +0 -16
- package/dist/cli/experimental/auth.d.ts.map +0 -1
- package/dist/cli/experimental/auth.js +0 -13
- package/dist/cli/experimental/auth.js.map +0 -1
- package/dist/cli/experimental/cli.d.ts +0 -6
- package/dist/cli/experimental/cli.d.ts.map +0 -1
- package/dist/cli/experimental/cli.js +0 -5
- package/dist/cli/experimental/cli.js.map +0 -1
- package/dist/cli/experimental/command-options.d.ts +0 -17
- package/dist/cli/experimental/command-options.d.ts.map +0 -1
- package/dist/cli/experimental/command-options.js +0 -35
- package/dist/cli/experimental/command-options.js.map +0 -1
- package/dist/cli/experimental/command.d.ts +0 -63
- package/dist/cli/experimental/command.d.ts.map +0 -1
- package/dist/cli/experimental/command.js +0 -130
- package/dist/cli/experimental/command.js.map +0 -1
- package/dist/cli/experimental/commands/client.d.ts +0 -13
- package/dist/cli/experimental/commands/client.d.ts.map +0 -1
- package/dist/cli/experimental/commands/client.js +0 -25
- package/dist/cli/experimental/commands/client.js.map +0 -1
- package/dist/cli/experimental/commands/pi.d.ts +0 -15
- package/dist/cli/experimental/commands/pi.d.ts.map +0 -1
- package/dist/cli/experimental/commands/pi.js +0 -28
- package/dist/cli/experimental/commands/pi.js.map +0 -1
- package/dist/cli/experimental/commands/server.d.ts +0 -13
- package/dist/cli/experimental/commands/server.d.ts.map +0 -1
- package/dist/cli/experimental/commands/server.js +0 -25
- package/dist/cli/experimental/commands/server.js.map +0 -1
- package/dist/cli/experimental/transport-address.d.ts +0 -10
- package/dist/cli/experimental/transport-address.d.ts.map +0 -1
- package/dist/cli/experimental/transport-address.js +0 -38
- package/dist/cli/experimental/transport-address.js.map +0 -1
- package/dist/client/index.d.ts +0 -3
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js +0 -3
- package/dist/client/index.js.map +0 -1
- package/dist/client/remote-session.d.ts +0 -53
- package/dist/client/remote-session.d.ts.map +0 -1
- package/dist/client/remote-session.js +0 -340
- package/dist/client/remote-session.js.map +0 -1
- package/dist/client/transcript.d.ts +0 -12
- package/dist/client/transcript.d.ts.map +0 -1
- package/dist/client/transcript.js +0 -98
- package/dist/client/transcript.js.map +0 -1
- package/dist/utils/clipboard-native.d.ts +0 -11
- package/dist/utils/clipboard-native.d.ts.map +0 -1
- package/dist/utils/clipboard-native.js +0 -20
- package/dist/utils/clipboard-native.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction-summary-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"compaction-summary-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAuB,KAAK,aAAa,EAA6B,MAAM,wBAAwB,CAAC;AACjH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAI1E;;;GAGG;AACH,qBAAa,iCAAkC,SAAQ,GAAG;IACzD,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,aAAa,CAAgB;IAErC,YAAY,OAAO,EAAE,wBAAwB,EAAE,aAAa,GAAE,aAAkC,EAK/F;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAEQ,UAAU,IAAI,IAAI,CAG1B;IAED,OAAO,CAAC,aAAa;CAoCrB","sourcesContent":["import { Box, Container, Markdown, type MarkdownTheme, MouseRegion, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { CompactionSummaryMessage } from \"../../../core/messages.ts\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.ts\";\nimport { keyText } from \"./keybinding-hints.ts\";\n\n/**\n * Component that renders a compaction message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class CompactionSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: CompactionSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\t\tconst content = new Container();\n\n\t\tconst tokenStr = this.message.tokensBefore.toLocaleString();\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[compaction]\\x1b[22m`);\n\t\tcontent.addChild(new Text(label, 0, 0));\n\t\tcontent.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = `**Compacted from ${tokenStr} tokens**\\n\\n`;\n\t\t\tcontent.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tcontent.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", `Compacted from ${tokenStr} tokens (`) +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tthis.addChild(\n\t\t\tnew MouseRegion(content, (event) => {\n\t\t\t\tif (event.type !== \"click\" || event.button !== \"left\") return undefined;\n\t\t\t\tthis.setExpanded(!this.expanded);\n\t\t\t\treturn { handled: true };\n\t\t\t}),\n\t\t);\n\t}\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
1
|
+
import { Box, Container, Markdown, MouseRegion, Spacer, Text } from "@earendil-works/pi-tui";
|
|
2
2
|
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
3
3
|
import { keyText } from "./keybinding-hints.js";
|
|
4
4
|
/**
|
|
@@ -25,21 +25,28 @@ export class CompactionSummaryMessageComponent extends Box {
|
|
|
25
25
|
}
|
|
26
26
|
updateDisplay() {
|
|
27
27
|
this.clear();
|
|
28
|
+
const content = new Container();
|
|
28
29
|
const tokenStr = this.message.tokensBefore.toLocaleString();
|
|
29
30
|
const label = theme.fg("customMessageLabel", `\x1b[1m[compaction]\x1b[22m`);
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
content.addChild(new Text(label, 0, 0));
|
|
32
|
+
content.addChild(new Spacer(1));
|
|
32
33
|
if (this.expanded) {
|
|
33
34
|
const header = `**Compacted from ${tokenStr} tokens**\n\n`;
|
|
34
|
-
|
|
35
|
+
content.addChild(new Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {
|
|
35
36
|
color: (text) => theme.fg("customMessageText", text),
|
|
36
37
|
}));
|
|
37
38
|
}
|
|
38
39
|
else {
|
|
39
|
-
|
|
40
|
+
content.addChild(new Text(theme.fg("customMessageText", `Compacted from ${tokenStr} tokens (`) +
|
|
40
41
|
theme.fg("dim", keyText("app.tools.expand")) +
|
|
41
42
|
theme.fg("customMessageText", " to expand)"), 0, 0));
|
|
42
43
|
}
|
|
44
|
+
this.addChild(new MouseRegion(content, (event) => {
|
|
45
|
+
if (event.type !== "click" || event.button !== "left")
|
|
46
|
+
return undefined;
|
|
47
|
+
this.setExpanded(!this.expanded);
|
|
48
|
+
return { handled: true };
|
|
49
|
+
}));
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
52
|
//# sourceMappingURL=compaction-summary-message.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAsB,MAAM,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"compaction-summary-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/compaction-summary-message.ts"],"names":[],"mappings":"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","sourcesContent":["import { Box, Container, Markdown, type MarkdownTheme, MouseRegion, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { CompactionSummaryMessage } from \"../../../core/messages.ts\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.ts\";\nimport { keyText } from \"./keybinding-hints.ts\";\n\n/**\n * Component that renders a compaction message with collapsed/expanded state.\n * Uses same background color as custom messages for visual consistency.\n */\nexport class CompactionSummaryMessageComponent extends Box {\n\tprivate expanded = false;\n\tprivate message: CompactionSummaryMessage;\n\tprivate markdownTheme: MarkdownTheme;\n\n\tconstructor(message: CompactionSummaryMessage, markdownTheme: MarkdownTheme = getMarkdownTheme()) {\n\t\tsuper(1, 1, (t) => theme.bg(\"customMessageBg\", t));\n\t\tthis.message = message;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tthis.clear();\n\t\tconst content = new Container();\n\n\t\tconst tokenStr = this.message.tokensBefore.toLocaleString();\n\t\tconst label = theme.fg(\"customMessageLabel\", `\\x1b[1m[compaction]\\x1b[22m`);\n\t\tcontent.addChild(new Text(label, 0, 0));\n\t\tcontent.addChild(new Spacer(1));\n\n\t\tif (this.expanded) {\n\t\t\tconst header = `**Compacted from ${tokenStr} tokens**\\n\\n`;\n\t\t\tcontent.addChild(\n\t\t\t\tnew Markdown(header + this.message.summary, 0, 0, this.markdownTheme, {\n\t\t\t\t\tcolor: (text: string) => theme.fg(\"customMessageText\", text),\n\t\t\t\t}),\n\t\t\t);\n\t\t} else {\n\t\t\tcontent.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\ttheme.fg(\"customMessageText\", `Compacted from ${tokenStr} tokens (`) +\n\t\t\t\t\t\ttheme.fg(\"dim\", keyText(\"app.tools.expand\")) +\n\t\t\t\t\t\ttheme.fg(\"customMessageText\", \" to expand)\"),\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tthis.addChild(\n\t\t\tnew MouseRegion(content, (event) => {\n\t\t\t\tif (event.type !== \"click\" || event.button !== \"left\") return undefined;\n\t\t\t\tthis.setExpanded(!this.expanded);\n\t\t\t\treturn { handled: true };\n\t\t\t}),\n\t\t);\n\t}\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Editor, type EditorOptions, type EditorTheme, type TUI } from "@earendil-works/pi-tui";
|
|
2
2
|
import type { AppKeybinding, KeybindingsManager } from "../../../core/keybindings.ts";
|
|
3
|
+
import type { StatusIndicator } from "./status-indicator.ts";
|
|
3
4
|
/** Which marker the dock draws. The set is closed so a mode cannot arrive without a marker. */
|
|
4
5
|
export type PromptMode = "agent" | "bash";
|
|
5
6
|
export interface CustomEditorOptions extends EditorOptions {
|
|
@@ -23,6 +24,13 @@ export interface CustomEditorOptions extends EditorOptions {
|
|
|
23
24
|
autocompleteRule?: (width: number) => string;
|
|
24
25
|
/** Keybinding footer drawn under the autocomplete rows. Omit for no footer. */
|
|
25
26
|
autocompleteFooter?: () => string;
|
|
27
|
+
/**
|
|
28
|
+
* Render the streaming working status in the editor's top border. Apex's
|
|
29
|
+
* default composer leaves this off: the footer tray carries the working
|
|
30
|
+
* status, and the filled surface drops the top border anyway. Kept so a
|
|
31
|
+
* custom editor built on this class can opt in, as upstream allows.
|
|
32
|
+
*/
|
|
33
|
+
embedWorkingStatus?: boolean;
|
|
26
34
|
}
|
|
27
35
|
/**
|
|
28
36
|
* Custom editor that handles app-level keybindings for coding-agent.
|
|
@@ -41,6 +49,8 @@ export declare class CustomEditor extends Editor {
|
|
|
41
49
|
private readonly placeholder;
|
|
42
50
|
private readonly autocompleteRule;
|
|
43
51
|
private readonly autocompleteFooter;
|
|
52
|
+
private workingStatusIndicator;
|
|
53
|
+
readonly embedWorkingStatus: boolean;
|
|
44
54
|
actionHandlers: Map<AppKeybinding, () => void>;
|
|
45
55
|
onEscape?: () => void;
|
|
46
56
|
onCtrlD?: () => void;
|
|
@@ -88,6 +98,8 @@ export declare class CustomEditor extends Editor {
|
|
|
88
98
|
* the top of this file for what that breaks.
|
|
89
99
|
*/
|
|
90
100
|
private sliceLeadingCursorCell;
|
|
101
|
+
setWorkingStatusIndicator(indicator: StatusIndicator | undefined): void;
|
|
102
|
+
protected renderTopBorder(width: number, hiddenLineCount: number): string;
|
|
91
103
|
/**
|
|
92
104
|
* Register a handler for an app action.
|
|
93
105
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tCURSOR_MARKER,\n\tEditor,\n\ttype EditorOptions,\n\ttype EditorTheme,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.ts\";\nimport { stripAnsi } from \"../../../utils/ansi.ts\";\n\n/** Which marker the dock draws. The set is closed so a mode cannot arrive without a marker. */\nexport type PromptMode = \"agent\" | \"bash\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\t/** Marker rendered at the start of the first input line, e.g. `\"> \"`. */\n\tpromptPrefix?: string;\n\t/** Styles the prompt prefix. Defaults to leaving it unstyled. */\n\tpromptColor?: (text: string) => string;\n\t/** Marker for bash mode. Defaults to `promptPrefix`, so mode is carried by colour alone. */\n\tbashPromptPrefix?: string;\n\t/** Styles the bash marker. Defaults to `promptColor`. */\n\tbashPromptColor?: (text: string) => string;\n\t/** Shown in place of the empty input line. */\n\tplaceholder?: string;\n\t/** Styles the placeholder. Defaults to leaving it unstyled. */\n\tplaceholderColor?: (text: string) => string;\n\t/** Styles a leading `/command` token. Defaults to leaving it unstyled. */\n\tcommandColor?: (text: string) => string;\n\t/** Applies the filled prompt surface around the editor block. */\n\tsurfaceColor?: (text: string) => string;\n\t/** Rule drawn above the autocomplete rows. Omit for no rule. */\n\tautocompleteRule?: (width: number) => string;\n\t/** Keybinding footer drawn under the autocomplete rows. Omit for no footer. */\n\tautocompleteFooter?: () => string;\n}\n\n/** Box-drawing horizontal rule: the character the base Editor draws borders from. */\nconst BORDER_CHAR = \"─\";\n\n/**\n * The two shapes the base Editor draws a border in: a plain full-width rule, and\n * the scroll indicator (`─── ↑ 3 more ────`).\n *\n * Both are anchored deliberately. A prefix test would count any content line that\n * merely *starts* with the rule character — a pasted divider, `─notes` — as a\n * border, which is worse than it sounds: {@link CustomEditor.render} tells the\n * content region from the autocomplete region by counting borders, so one\n * miscount shifts every later line into the wrong region.\n */\nconst BORDER_RULE = /^─+$/;\nconst BORDER_SCROLL = /^─+ [↑↓] \\d+ more/;\n\n/**\n * A leading `/command` token: optional indent, a slash, then non-space.\n * Matched against the line's *visible* text, not the rendered string.\n */\nconst COMMAND_TOKEN = /^(\\s*)(\\/\\S+)/;\n\n/**\n * One ANSI control sequence: a CSI escape (`ESC [ … letter`) or the APC\n * hardware-cursor marker (`ESC _ … BEL`). Sticky, for position-by-position\n * scanning in {@link colorVisibleRange}.\n */\nconst CONTROL_SEQUENCE = /\\x1b\\[[0-9;]*[A-Za-z]|\\x1b_[^\\x07]*\\x07/y;\n\n/** The reverse-video cursor cell emitted by the inherited editor. */\nconst INVERTED_CURSOR_CELL = /\\x1b\\[7m[\\s\\S]\\x1b\\[(?:0|27)m/y;\n\nconst SURFACE_PADDING = 2;\n\n/**\n * ============================ ANSI measuring hazards ============================\n *\n * Two traps live in the rendered strings this file post-processes. Both produced\n * real bugs; both are cheap to re-introduce. Read this before touching the width\n * maths or any leading-sequence match.\n *\n * 1. DO NOT COMPOSE `stripAnsi` WITH `visibleWidth`.\n *\n * `stripAnsi` removes SGR colour codes but leaves the APC hardware-cursor\n * marker (`ESC _pi:c BEL`, see CURSOR_MARKER in packages/tui) fully intact —\n * all seven bytes of it. `visibleWidth` already handles that marker correctly\n * and scores it zero.\n *\n * So `visibleWidth(stripAnsi(line))` counts seven phantom columns on every\n * line carrying a cursor, which silently widened each placeholder line past\n * the terminal width. Measure with `visibleWidth` alone. Use `stripAnsi` only\n * to read text, never to measure it.\n *\n * 2. DO NOT MATCH THE LEADING SEQUENCE RUN GENERICALLY.\n *\n * The base Editor renders an empty line as:\n * CURSOR_MARKER ESC[7m <space> ESC[0m <padding>\n * The `ESC[7m` turns on reverse video for exactly the one-cell cursor block,\n * and `ESC[0m` turns it back off.\n *\n * A permissive prefix match such as `(?:\\x1b\\[[0-9;]*m)*` consumes that\n * `ESC[7m` as though it were ordinary leading styling. Whatever is appended\n * next then inherits reverse video, and because the matching `ESC[0m` was\n * left behind in the discarded remainder, the inversion runs on through the\n * placeholder and the rest of the frame. Match the reverse-video run\n * explicitly instead — see {@link CustomEditor.sliceLeadingCursorCell}.\n *\n * ==============================================================================\n */\n\n/**\n * Wrap the visible characters in `[start, end)` with `colorFn`, leaving every\n * control sequence in place.\n *\n * The range is in visible columns, so it is unaffected by however many escape\n * sequences are interleaved. That matters because the cursor cell can land in\n * the middle of the token being coloured: with the caret at column 3 of\n * `/model`, the line reads `/mo` + CURSOR_MARKER + `ESC[7m` + `d` + `ESC[0m` +\n * `el`. Colour is therefore re-opened after every control sequence rather than\n * once around the whole token, since the cursor cell's `ESC[0m` would otherwise\n * cancel it partway through.\n */\nfunction colorVisibleRange(line: string, start: number, end: number, colorFn: (text: string) => string): string {\n\tlet out = \"\";\n\tlet buffer = \"\";\n\tlet column = 0;\n\tlet index = 0;\n\n\tconst flush = () => {\n\t\tif (buffer) {\n\t\t\tout += colorFn(buffer);\n\t\t\tbuffer = \"\";\n\t\t}\n\t};\n\n\twhile (index < line.length) {\n\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\tif (control) {\n\t\t\tflush();\n\t\t\tout += control[0];\n\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\tcontinue;\n\t\t}\n\t\tconst char = line[index];\n\t\tif (column >= start && column < end) {\n\t\t\tbuffer += char;\n\t\t} else {\n\t\t\tflush();\n\t\t\tout += char;\n\t\t}\n\t\tcolumn += visibleWidth(char);\n\t\tindex += 1;\n\t}\n\tflush();\n\treturn out;\n}\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n *\n * The prompt prefix and placeholder are layered on in `render` rather than\n * inside the base Editor, because `packages/tui` is frozen under ADR 0001 and\n * its `EditorOptions` has no hook for either.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate readonly prompts: Record<PromptMode, { prefix: string; color: (text: string) => string }>;\n\tprivate promptMode: PromptMode = \"agent\";\n\tprivate readonly placeholderColor: (text: string) => string;\n\tprivate readonly commandColor: ((text: string) => string) | undefined;\n\tprivate readonly surfaceColor: ((text: string) => string) | undefined;\n\tprivate readonly placeholder: string | undefined;\n\tprivate readonly autocompleteRule: ((width: number) => string) | undefined;\n\tprivate readonly autocompleteFooter: (() => string) | undefined;\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tconst agentPrefix = options?.promptPrefix ?? \"\";\n\t\tconst agentColor = options?.promptColor ?? ((text) => text);\n\t\tconst bashPrefix = options?.bashPromptPrefix ?? agentPrefix;\n\t\t// Every marker occupies the same cells, so switching mode cannot move the\n\t\t// caret or reflow what is already typed. Padding rather than throwing keeps\n\t\t// a mismatched caller rendering instead of crashing its dock.\n\t\tconst cells = Math.max(visibleWidth(agentPrefix), visibleWidth(bashPrefix));\n\t\tconst pad = (text: string) => text + \" \".repeat(cells - visibleWidth(text));\n\t\tthis.prompts = {\n\t\t\tagent: { prefix: pad(agentPrefix), color: agentColor },\n\t\t\tbash: { prefix: pad(bashPrefix), color: options?.bashPromptColor ?? agentColor },\n\t\t};\n\t\tthis.placeholder = options?.placeholder;\n\t\tthis.placeholderColor = options?.placeholderColor ?? ((text) => text);\n\t\tthis.commandColor = options?.commandColor;\n\t\tthis.surfaceColor = options?.surfaceColor;\n\t\tthis.autocompleteRule = options?.autocompleteRule;\n\t\tthis.autocompleteFooter = options?.autocompleteFooter;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst canRenderSurface = this.surfaceColor !== undefined && width > 0;\n\t\tconst surfacePadding = canRenderSurface ? Math.min(SURFACE_PADDING, Math.floor((width - 1) / 2)) : 0;\n\t\tconst editorWidth = canRenderSurface ? width - surfacePadding * 2 : width;\n\t\tconst lines = this.withAutocompleteChrome(this.renderEditor(editorWidth), editorWidth);\n\t\treturn canRenderSurface ? this.withSurface(lines, width, surfacePadding) : lines;\n\t}\n\n\tprivate renderEditor(width: number): string[] {\n\t\tconst { prefix: promptPrefix, color: promptColor } = this.prompts[this.promptMode];\n\t\tconst prefixWidth = visibleWidth(promptPrefix);\n\t\t// Reserve the prefix out of the width handed to the base Editor, so its\n\t\t// wrapping, scrolling and cursor column all account for the space the\n\t\t// prefix occupies. Bail out entirely when the terminal cannot spare it.\n\t\tif (prefixWidth === 0 || width <= prefixWidth + 4) {\n\t\t\treturn this.withCommandColor(this.withPlaceholder(super.render(width)));\n\t\t}\n\n\t\tconst inner = this.withCommandColor(this.withPlaceholder(super.render(width - prefixWidth)));\n\t\tconst borderPad = this.borderColor(BORDER_CHAR.repeat(prefixWidth));\n\t\tconst blank = \" \".repeat(prefixWidth);\n\t\tconst styledPrefix = promptColor(promptPrefix);\n\n\t\t// Structure is: top border, content lines, bottom border, then optional\n\t\t// autocomplete rows. Counting borders tells the three regions apart\n\t\t// without depending on how many lines each contains.\n\t\tlet borders = 0;\n\t\tlet firstContentLine = true;\n\t\treturn inner.map((line) => {\n\t\t\tif (this.isBorderLine(line)) {\n\t\t\t\tborders++;\n\t\t\t\treturn borderPad + line;\n\t\t\t}\n\t\t\tif (borders !== 1) {\n\t\t\t\t// Autocomplete rows sit below the box; indent them to match.\n\t\t\t\treturn blank + line;\n\t\t\t}\n\t\t\tif (firstContentLine) {\n\t\t\t\tfirstContentLine = false;\n\t\t\t\treturn styledPrefix + line;\n\t\t\t}\n\t\t\treturn blank + line;\n\t\t});\n\t}\n\n\t/**\n\t * Brackets the autocomplete rows with a rule and a keybinding footer.\n\t *\n\t * The rows arrive after the editor's second border, so the same border count\n\t * that separates the box from its dropdown elsewhere in this file finds them\n\t * here. Nothing is added when the dropdown is closed.\n\t */\n\tprivate withAutocompleteChrome(lines: string[], width: number): string[] {\n\t\tif (!this.autocompleteRule && !this.autocompleteFooter) return lines;\n\t\tconst blockLineCount = this.editorBlockLineCount(lines);\n\t\tconst rows = lines.slice(blockLineCount);\n\t\tif (rows.length === 0) return lines;\n\n\t\tconst chrome = [...lines.slice(0, blockLineCount)];\n\t\tif (this.autocompleteRule) chrome.push(this.autocompleteRule(width));\n\t\tchrome.push(...rows);\n\t\tif (this.autocompleteFooter) chrome.push(this.autocompleteFooter());\n\t\treturn chrome;\n\t}\n\n\tprivate withSurface(lines: string[], width: number, padding: number): string[] {\n\t\tconst surfaceColor = this.surfaceColor;\n\t\tif (!surfaceColor) return lines;\n\n\t\tconst result = [this.surfaceLine(\" \".repeat(width), surfaceColor)];\n\t\tconst editorBlockLineCount = this.editorBlockLineCount(lines);\n\t\tconst editorContent = lines.slice(1, Math.max(1, editorBlockLineCount - 1));\n\t\tfor (const line of editorContent) {\n\t\t\tresult.push(this.surfaceLine(`${\" \".repeat(padding)}${line}${\" \".repeat(padding)}`, surfaceColor));\n\t\t}\n\t\tresult.push(this.surfaceLine(\" \".repeat(width), surfaceColor));\n\t\tfor (const line of lines.slice(editorBlockLineCount)) {\n\t\t\tresult.push(`${\" \".repeat(padding)}${line}`);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate editorBlockLineCount(lines: string[]): number {\n\t\tlet borders = 0;\n\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\tif (this.isBorderLine(lines[index])) borders++;\n\t\t\tif (borders === 2) return index + 1;\n\t\t}\n\t\treturn lines.length;\n\t}\n\n\tprivate surfaceLine(line: string, surfaceColor: (text: string) => string): string {\n\t\tlet output = \"\";\n\t\tlet visibleText = \"\";\n\t\tlet index = 0;\n\n\t\tconst flush = () => {\n\t\t\tif (visibleText) {\n\t\t\t\toutput += surfaceColor(visibleText);\n\t\t\t\tvisibleText = \"\";\n\t\t\t}\n\t\t};\n\n\t\twhile (index < line.length) {\n\t\t\tINVERTED_CURSOR_CELL.lastIndex = index;\n\t\t\tconst cursorCell = INVERTED_CURSOR_CELL.exec(line);\n\t\t\tif (cursorCell) {\n\t\t\t\tflush();\n\t\t\t\toutput += cursorCell[0];\n\t\t\t\tindex = INVERTED_CURSOR_CELL.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\t\tif (control) {\n\t\t\t\tflush();\n\t\t\t\toutput += control[0];\n\t\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvisibleText += line[index];\n\t\t\tindex += 1;\n\t\t}\n\n\t\tflush();\n\t\treturn output;\n\t}\n\n\tsetPromptMode(mode: PromptMode): void {\n\t\tif (this.promptMode === mode) return;\n\t\tthis.promptMode = mode;\n\t\tthis.invalidate();\n\t}\n\n\tprivate isBorderLine(line: string): boolean {\n\t\tconst visible = stripAnsi(line.split(CURSOR_MARKER).join(\"\"));\n\t\treturn BORDER_RULE.test(visible) || BORDER_SCROLL.test(visible);\n\t}\n\n\t/**\n\t * Tint a leading `/command` on the first input line.\n\t *\n\t * The base Editor emits input text unstyled, so the only styling already\n\t * present on the line is the cursor cell — which `colorVisibleRange` steps\n\t * over. Only the command token is tinted; arguments after it stay plain, so\n\t * the highlight marks what is being invoked rather than the whole line.\n\t */\n\tprivate withCommandColor(lines: string[]): string[] {\n\t\tconst commandColor = this.commandColor;\n\t\tif (!commandColor || lines.length < 2) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Read the token from the model rather than the rendered line: the first\n\t\t// visual row is the start of the text only when the caret has not scrolled\n\t\t// the view down, and getText is unambiguous either way.\n\t\tconst firstLine = this.getText().split(\"\\n\", 1)[0] ?? \"\";\n\t\tconst match = COMMAND_TOKEN.exec(firstLine);\n\t\tif (!match) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst [, indent, token] = match;\n\t\t// The rendered row must still begin with that token, or the view has\n\t\t// scrolled and the highlight would land on unrelated text.\n\t\tif (!stripAnsi(lines[1].split(CURSOR_MARKER).join(\"\")).startsWith(`${indent}${token}`)) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst start = visibleWidth(indent);\n\t\tconst updated = [...lines];\n\t\tupdated[1] = colorVisibleRange(lines[1], start, start + visibleWidth(token), commandColor);\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Replace the sole content line with the placeholder while the input is\n\t * empty. Index 1 is always the first content line, and an empty editor has\n\t * exactly one, so no line classification is needed here.\n\t */\n\tprivate withPlaceholder(lines: string[]): string[] {\n\t\tconst placeholder = this.placeholder;\n\t\tif (!placeholder || lines.length < 2 || this.getText().length > 0 || this.isShowingAutocomplete()) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst contentLine = lines[1];\n\t\t// Preserve the cursor cell when focused; unfocused there is none, and the\n\t\t// placeholder simply starts at column zero.\n\t\tconst cursorCell = this.sliceLeadingCursorCell(contentLine);\n\t\t// Measure with visibleWidth alone — never visibleWidth(stripAnsi(...)),\n\t\t// which scores the cursor marker as seven columns. See hazard 1 at the top\n\t\t// of this file.\n\t\tconst available = visibleWidth(contentLine) - visibleWidth(cursorCell);\n\t\tif (available <= 0) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Cut by display width, not by String.slice: `available` counts terminal\n\t\t// columns, while slice counts UTF-16 code units. They only agree for\n\t\t// plain ASCII, and disagreeing splits a surrogate pair or overruns the\n\t\t// line on the first wide character.\n\t\tconst text = truncateToWidth(placeholder, available, \"\");\n\t\tconst padding = \" \".repeat(Math.max(0, available - visibleWidth(text)));\n\t\tconst updated = [...lines];\n\t\tupdated[1] = cursorCell + this.placeholderColor(text) + padding;\n\t\treturn updated;\n\t}\n\n\t/**\n\t * The base Editor renders an empty line as the hardware-cursor marker\n\t * followed by a reverse-video cell: `CURSOR_MARKER ESC[7m <space> ESC[0m`.\n\t * Both parts are kept verbatim.\n\t *\n\t * The `ESC[7m … ESC[0m` run is matched explicitly. Do not \"simplify\" this to\n\t * a general leading-SGR match such as `(?:\\x1b\\[[0-9;]*m)*` — see hazard 2 at\n\t * the top of this file for what that breaks.\n\t */\n\tprivate sliceLeadingCursorCell(line: string): string {\n\t\tconst match = /^(?:\\x1b_pi:c\\x07)?(?:\\x1b\\[7m.\\x1b\\[(?:0|27)m)?/.exec(line);\n\t\treturn match?.[0] ?? \"\";\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for clipboard paste keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Explicit history bindings take precedence over app actions while the editor is focused.\n\t\t// This lets users bind Ctrl+P even though it cycles models by default.\n\t\tif (\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyPrevious\") ||\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyNext\")\n\t\t) {\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tCURSOR_MARKER,\n\tEditor,\n\ttype EditorOptions,\n\ttype EditorTheme,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.ts\";\nimport { stripAnsi } from \"../../../utils/ansi.ts\";\nimport type { StatusIndicator } from \"./status-indicator.ts\";\n\n/** Which marker the dock draws. The set is closed so a mode cannot arrive without a marker. */\nexport type PromptMode = \"agent\" | \"bash\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\t/** Marker rendered at the start of the first input line, e.g. `\"> \"`. */\n\tpromptPrefix?: string;\n\t/** Styles the prompt prefix. Defaults to leaving it unstyled. */\n\tpromptColor?: (text: string) => string;\n\t/** Marker for bash mode. Defaults to `promptPrefix`, so mode is carried by colour alone. */\n\tbashPromptPrefix?: string;\n\t/** Styles the bash marker. Defaults to `promptColor`. */\n\tbashPromptColor?: (text: string) => string;\n\t/** Shown in place of the empty input line. */\n\tplaceholder?: string;\n\t/** Styles the placeholder. Defaults to leaving it unstyled. */\n\tplaceholderColor?: (text: string) => string;\n\t/** Styles a leading `/command` token. Defaults to leaving it unstyled. */\n\tcommandColor?: (text: string) => string;\n\t/** Applies the filled prompt surface around the editor block. */\n\tsurfaceColor?: (text: string) => string;\n\t/** Rule drawn above the autocomplete rows. Omit for no rule. */\n\tautocompleteRule?: (width: number) => string;\n\t/** Keybinding footer drawn under the autocomplete rows. Omit for no footer. */\n\tautocompleteFooter?: () => string;\n\t/**\n\t * Render the streaming working status in the editor's top border. Apex's\n\t * default composer leaves this off: the footer tray carries the working\n\t * status, and the filled surface drops the top border anyway. Kept so a\n\t * custom editor built on this class can opt in, as upstream allows.\n\t */\n\tembedWorkingStatus?: boolean;\n}\n\n/** Box-drawing horizontal rule: the character the base Editor draws borders from. */\nconst BORDER_CHAR = \"─\";\n\n/**\n * The two shapes the base Editor draws a border in: a plain full-width rule, and\n * the scroll indicator (`─── ↑ 3 more ────`).\n *\n * Both are anchored deliberately. A prefix test would count any content line that\n * merely *starts* with the rule character — a pasted divider, `─notes` — as a\n * border, which is worse than it sounds: {@link CustomEditor.render} tells the\n * content region from the autocomplete region by counting borders, so one\n * miscount shifts every later line into the wrong region.\n */\nconst BORDER_RULE = /^─+$/;\nconst BORDER_SCROLL = /^─+ [↑↓] \\d+ more/;\n\n/**\n * A leading `/command` token: optional indent, a slash, then non-space.\n * Matched against the line's *visible* text, not the rendered string.\n */\nconst COMMAND_TOKEN = /^(\\s*)(\\/\\S+)/;\n\n/**\n * One ANSI control sequence: a CSI escape (`ESC [ … letter`) or the APC\n * hardware-cursor marker (`ESC _ … BEL`). Sticky, for position-by-position\n * scanning in {@link colorVisibleRange}.\n */\nconst CONTROL_SEQUENCE = /\\x1b\\[[0-9;]*[A-Za-z]|\\x1b_[^\\x07]*\\x07/y;\n\n/** The reverse-video cursor cell emitted by the inherited editor. */\nconst INVERTED_CURSOR_CELL = /\\x1b\\[7m[\\s\\S]\\x1b\\[(?:0|27)m/y;\n\nconst SURFACE_PADDING = 2;\n\n/**\n * ============================ ANSI measuring hazards ============================\n *\n * Two traps live in the rendered strings this file post-processes. Both produced\n * real bugs; both are cheap to re-introduce. Read this before touching the width\n * maths or any leading-sequence match.\n *\n * 1. DO NOT COMPOSE `stripAnsi` WITH `visibleWidth`.\n *\n * `stripAnsi` removes SGR colour codes but leaves the APC hardware-cursor\n * marker (`ESC _pi:c BEL`, see CURSOR_MARKER in packages/tui) fully intact —\n * all seven bytes of it. `visibleWidth` already handles that marker correctly\n * and scores it zero.\n *\n * So `visibleWidth(stripAnsi(line))` counts seven phantom columns on every\n * line carrying a cursor, which silently widened each placeholder line past\n * the terminal width. Measure with `visibleWidth` alone. Use `stripAnsi` only\n * to read text, never to measure it.\n *\n * 2. DO NOT MATCH THE LEADING SEQUENCE RUN GENERICALLY.\n *\n * The base Editor renders an empty line as:\n * CURSOR_MARKER ESC[7m <space> ESC[0m <padding>\n * The `ESC[7m` turns on reverse video for exactly the one-cell cursor block,\n * and `ESC[0m` turns it back off.\n *\n * A permissive prefix match such as `(?:\\x1b\\[[0-9;]*m)*` consumes that\n * `ESC[7m` as though it were ordinary leading styling. Whatever is appended\n * next then inherits reverse video, and because the matching `ESC[0m` was\n * left behind in the discarded remainder, the inversion runs on through the\n * placeholder and the rest of the frame. Match the reverse-video run\n * explicitly instead — see {@link CustomEditor.sliceLeadingCursorCell}.\n *\n * ==============================================================================\n */\n\n/**\n * Wrap the visible characters in `[start, end)` with `colorFn`, leaving every\n * control sequence in place.\n *\n * The range is in visible columns, so it is unaffected by however many escape\n * sequences are interleaved. That matters because the cursor cell can land in\n * the middle of the token being coloured: with the caret at column 3 of\n * `/model`, the line reads `/mo` + CURSOR_MARKER + `ESC[7m` + `d` + `ESC[0m` +\n * `el`. Colour is therefore re-opened after every control sequence rather than\n * once around the whole token, since the cursor cell's `ESC[0m` would otherwise\n * cancel it partway through.\n */\nfunction colorVisibleRange(line: string, start: number, end: number, colorFn: (text: string) => string): string {\n\tlet out = \"\";\n\tlet buffer = \"\";\n\tlet column = 0;\n\tlet index = 0;\n\n\tconst flush = () => {\n\t\tif (buffer) {\n\t\t\tout += colorFn(buffer);\n\t\t\tbuffer = \"\";\n\t\t}\n\t};\n\n\twhile (index < line.length) {\n\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\tif (control) {\n\t\t\tflush();\n\t\t\tout += control[0];\n\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\tcontinue;\n\t\t}\n\t\tconst char = line[index];\n\t\tif (column >= start && column < end) {\n\t\t\tbuffer += char;\n\t\t} else {\n\t\t\tflush();\n\t\t\tout += char;\n\t\t}\n\t\tcolumn += visibleWidth(char);\n\t\tindex += 1;\n\t}\n\tflush();\n\treturn out;\n}\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n *\n * The prompt prefix and placeholder are layered on in `render` rather than\n * inside the base Editor, because `packages/tui` is frozen under ADR 0001 and\n * its `EditorOptions` has no hook for either.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate readonly prompts: Record<PromptMode, { prefix: string; color: (text: string) => string }>;\n\tprivate promptMode: PromptMode = \"agent\";\n\tprivate readonly placeholderColor: (text: string) => string;\n\tprivate readonly commandColor: ((text: string) => string) | undefined;\n\tprivate readonly surfaceColor: ((text: string) => string) | undefined;\n\tprivate readonly placeholder: string | undefined;\n\tprivate readonly autocompleteRule: ((width: number) => string) | undefined;\n\tprivate readonly autocompleteFooter: (() => string) | undefined;\n\tprivate workingStatusIndicator: StatusIndicator | undefined;\n\tpublic readonly embedWorkingStatus: boolean;\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tconst agentPrefix = options?.promptPrefix ?? \"\";\n\t\tconst agentColor = options?.promptColor ?? ((text) => text);\n\t\tconst bashPrefix = options?.bashPromptPrefix ?? agentPrefix;\n\t\t// Every marker occupies the same cells, so switching mode cannot move the\n\t\t// caret or reflow what is already typed. Padding rather than throwing keeps\n\t\t// a mismatched caller rendering instead of crashing its dock.\n\t\tconst cells = Math.max(visibleWidth(agentPrefix), visibleWidth(bashPrefix));\n\t\tconst pad = (text: string) => text + \" \".repeat(cells - visibleWidth(text));\n\t\tthis.prompts = {\n\t\t\tagent: { prefix: pad(agentPrefix), color: agentColor },\n\t\t\tbash: { prefix: pad(bashPrefix), color: options?.bashPromptColor ?? agentColor },\n\t\t};\n\t\tthis.placeholder = options?.placeholder;\n\t\tthis.placeholderColor = options?.placeholderColor ?? ((text) => text);\n\t\tthis.commandColor = options?.commandColor;\n\t\tthis.surfaceColor = options?.surfaceColor;\n\t\tthis.autocompleteRule = options?.autocompleteRule;\n\t\tthis.autocompleteFooter = options?.autocompleteFooter;\n\t\tthis.embedWorkingStatus = options?.embedWorkingStatus ?? false;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst canRenderSurface = this.surfaceColor !== undefined && width > 0;\n\t\tconst surfacePadding = canRenderSurface ? Math.min(SURFACE_PADDING, Math.floor((width - 1) / 2)) : 0;\n\t\tconst editorWidth = canRenderSurface ? width - surfacePadding * 2 : width;\n\t\tconst lines = this.withAutocompleteChrome(this.renderEditor(editorWidth), editorWidth);\n\t\treturn canRenderSurface ? this.withSurface(lines, width, surfacePadding) : lines;\n\t}\n\n\tprivate renderEditor(width: number): string[] {\n\t\tconst { prefix: promptPrefix, color: promptColor } = this.prompts[this.promptMode];\n\t\tconst prefixWidth = visibleWidth(promptPrefix);\n\t\t// Reserve the prefix out of the width handed to the base Editor, so its\n\t\t// wrapping, scrolling and cursor column all account for the space the\n\t\t// prefix occupies. Bail out entirely when the terminal cannot spare it.\n\t\tif (prefixWidth === 0 || width <= prefixWidth + 4) {\n\t\t\treturn this.withCommandColor(this.withPlaceholder(super.render(width)));\n\t\t}\n\n\t\tconst inner = this.withCommandColor(this.withPlaceholder(super.render(width - prefixWidth)));\n\t\tconst borderPad = this.borderColor(BORDER_CHAR.repeat(prefixWidth));\n\t\tconst blank = \" \".repeat(prefixWidth);\n\t\tconst styledPrefix = promptColor(promptPrefix);\n\n\t\t// Structure is: top border, content lines, bottom border, then optional\n\t\t// autocomplete rows. Counting borders tells the three regions apart\n\t\t// without depending on how many lines each contains.\n\t\tlet borders = 0;\n\t\tlet firstContentLine = true;\n\t\treturn inner.map((line) => {\n\t\t\tif (this.isBorderLine(line)) {\n\t\t\t\tborders++;\n\t\t\t\treturn borderPad + line;\n\t\t\t}\n\t\t\tif (borders !== 1) {\n\t\t\t\t// Autocomplete rows sit below the box; indent them to match.\n\t\t\t\treturn blank + line;\n\t\t\t}\n\t\t\tif (firstContentLine) {\n\t\t\t\tfirstContentLine = false;\n\t\t\t\treturn styledPrefix + line;\n\t\t\t}\n\t\t\treturn blank + line;\n\t\t});\n\t}\n\n\t/**\n\t * Brackets the autocomplete rows with a rule and a keybinding footer.\n\t *\n\t * The rows arrive after the editor's second border, so the same border count\n\t * that separates the box from its dropdown elsewhere in this file finds them\n\t * here. Nothing is added when the dropdown is closed.\n\t */\n\tprivate withAutocompleteChrome(lines: string[], width: number): string[] {\n\t\tif (!this.autocompleteRule && !this.autocompleteFooter) return lines;\n\t\tconst blockLineCount = this.editorBlockLineCount(lines);\n\t\tconst rows = lines.slice(blockLineCount);\n\t\tif (rows.length === 0) return lines;\n\n\t\tconst chrome = [...lines.slice(0, blockLineCount)];\n\t\tif (this.autocompleteRule) chrome.push(this.autocompleteRule(width));\n\t\tchrome.push(...rows);\n\t\tif (this.autocompleteFooter) chrome.push(this.autocompleteFooter());\n\t\treturn chrome;\n\t}\n\n\tprivate withSurface(lines: string[], width: number, padding: number): string[] {\n\t\tconst surfaceColor = this.surfaceColor;\n\t\tif (!surfaceColor) return lines;\n\n\t\tconst result = [this.surfaceLine(\" \".repeat(width), surfaceColor)];\n\t\tconst editorBlockLineCount = this.editorBlockLineCount(lines);\n\t\tconst editorContent = lines.slice(1, Math.max(1, editorBlockLineCount - 1));\n\t\tfor (const line of editorContent) {\n\t\t\tresult.push(this.surfaceLine(`${\" \".repeat(padding)}${line}${\" \".repeat(padding)}`, surfaceColor));\n\t\t}\n\t\tresult.push(this.surfaceLine(\" \".repeat(width), surfaceColor));\n\t\tfor (const line of lines.slice(editorBlockLineCount)) {\n\t\t\tresult.push(`${\" \".repeat(padding)}${line}`);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate editorBlockLineCount(lines: string[]): number {\n\t\tlet borders = 0;\n\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\tif (this.isBorderLine(lines[index])) borders++;\n\t\t\tif (borders === 2) return index + 1;\n\t\t}\n\t\treturn lines.length;\n\t}\n\n\tprivate surfaceLine(line: string, surfaceColor: (text: string) => string): string {\n\t\tlet output = \"\";\n\t\tlet visibleText = \"\";\n\t\tlet index = 0;\n\n\t\tconst flush = () => {\n\t\t\tif (visibleText) {\n\t\t\t\toutput += surfaceColor(visibleText);\n\t\t\t\tvisibleText = \"\";\n\t\t\t}\n\t\t};\n\n\t\twhile (index < line.length) {\n\t\t\tINVERTED_CURSOR_CELL.lastIndex = index;\n\t\t\tconst cursorCell = INVERTED_CURSOR_CELL.exec(line);\n\t\t\tif (cursorCell) {\n\t\t\t\tflush();\n\t\t\t\toutput += cursorCell[0];\n\t\t\t\tindex = INVERTED_CURSOR_CELL.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\t\tif (control) {\n\t\t\t\tflush();\n\t\t\t\toutput += control[0];\n\t\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvisibleText += line[index];\n\t\t\tindex += 1;\n\t\t}\n\n\t\tflush();\n\t\treturn output;\n\t}\n\n\tsetPromptMode(mode: PromptMode): void {\n\t\tif (this.promptMode === mode) return;\n\t\tthis.promptMode = mode;\n\t\tthis.invalidate();\n\t}\n\n\tprivate isBorderLine(line: string): boolean {\n\t\tconst visible = stripAnsi(line.split(CURSOR_MARKER).join(\"\"));\n\t\treturn BORDER_RULE.test(visible) || BORDER_SCROLL.test(visible);\n\t}\n\n\t/**\n\t * Tint a leading `/command` on the first input line.\n\t *\n\t * The base Editor emits input text unstyled, so the only styling already\n\t * present on the line is the cursor cell — which `colorVisibleRange` steps\n\t * over. Only the command token is tinted; arguments after it stay plain, so\n\t * the highlight marks what is being invoked rather than the whole line.\n\t */\n\tprivate withCommandColor(lines: string[]): string[] {\n\t\tconst commandColor = this.commandColor;\n\t\tif (!commandColor || lines.length < 2) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Read the token from the model rather than the rendered line: the first\n\t\t// visual row is the start of the text only when the caret has not scrolled\n\t\t// the view down, and getText is unambiguous either way.\n\t\tconst firstLine = this.getText().split(\"\\n\", 1)[0] ?? \"\";\n\t\tconst match = COMMAND_TOKEN.exec(firstLine);\n\t\tif (!match) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst [, indent, token] = match;\n\t\t// The rendered row must still begin with that token, or the view has\n\t\t// scrolled and the highlight would land on unrelated text.\n\t\tif (!stripAnsi(lines[1].split(CURSOR_MARKER).join(\"\")).startsWith(`${indent}${token}`)) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst start = visibleWidth(indent);\n\t\tconst updated = [...lines];\n\t\tupdated[1] = colorVisibleRange(lines[1], start, start + visibleWidth(token), commandColor);\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Replace the sole content line with the placeholder while the input is\n\t * empty. Index 1 is always the first content line, and an empty editor has\n\t * exactly one, so no line classification is needed here.\n\t */\n\tprivate withPlaceholder(lines: string[]): string[] {\n\t\tconst placeholder = this.placeholder;\n\t\tif (!placeholder || lines.length < 2 || this.getText().length > 0 || this.isShowingAutocomplete()) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst contentLine = lines[1];\n\t\t// Preserve the cursor cell when focused; unfocused there is none, and the\n\t\t// placeholder simply starts at column zero.\n\t\tconst cursorCell = this.sliceLeadingCursorCell(contentLine);\n\t\t// Measure with visibleWidth alone — never visibleWidth(stripAnsi(...)),\n\t\t// which scores the cursor marker as seven columns. See hazard 1 at the top\n\t\t// of this file.\n\t\tconst available = visibleWidth(contentLine) - visibleWidth(cursorCell);\n\t\tif (available <= 0) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Cut by display width, not by String.slice: `available` counts terminal\n\t\t// columns, while slice counts UTF-16 code units. They only agree for\n\t\t// plain ASCII, and disagreeing splits a surrogate pair or overruns the\n\t\t// line on the first wide character.\n\t\tconst text = truncateToWidth(placeholder, available, \"\");\n\t\tconst padding = \" \".repeat(Math.max(0, available - visibleWidth(text)));\n\t\tconst updated = [...lines];\n\t\tupdated[1] = cursorCell + this.placeholderColor(text) + padding;\n\t\treturn updated;\n\t}\n\n\t/**\n\t * The base Editor renders an empty line as the hardware-cursor marker\n\t * followed by a reverse-video cell: `CURSOR_MARKER ESC[7m <space> ESC[0m`.\n\t * Both parts are kept verbatim.\n\t *\n\t * The `ESC[7m … ESC[0m` run is matched explicitly. Do not \"simplify\" this to\n\t * a general leading-SGR match such as `(?:\\x1b\\[[0-9;]*m)*` — see hazard 2 at\n\t * the top of this file for what that breaks.\n\t */\n\tprivate sliceLeadingCursorCell(line: string): string {\n\t\tconst match = /^(?:\\x1b_pi:c\\x07)?(?:\\x1b\\[7m.\\x1b\\[(?:0|27)m)?/.exec(line);\n\t\treturn match?.[0] ?? \"\";\n\t}\n\n\tsetWorkingStatusIndicator(indicator: StatusIndicator | undefined): void {\n\t\tthis.workingStatusIndicator = indicator;\n\t}\n\n\tprotected override renderTopBorder(width: number, hiddenLineCount: number): string {\n\t\tif (!this.embedWorkingStatus || !this.workingStatusIndicator || width <= 0) {\n\t\t\treturn super.renderTopBorder(width, hiddenLineCount);\n\t\t}\n\n\t\tlet status = this.workingStatusIndicator.renderInBorder(Math.max(1, width - 5));\n\t\tlet statusWidth = visibleWidth(status);\n\t\tif (statusWidth === 0) return super.renderTopBorder(width, hiddenLineCount);\n\n\t\tconst overflowLabel = hiddenLineCount > 0 ? ` ↑ ${hiddenLineCount} more ` : undefined;\n\t\tconst overflowLabelWidth = overflowLabel ? visibleWidth(overflowLabel) : 0;\n\t\tconst overflowStart = Math.floor((width - overflowLabelWidth) / 2);\n\t\tconst canFitOverflow = () =>\n\t\t\toverflowLabel !== undefined && overflowLabelWidth + 2 <= width && overflowStart - (3 + statusWidth + 1) >= 1;\n\n\t\tif (overflowLabel && !canFitOverflow()) {\n\t\t\tstatus = this.workingStatusIndicator.renderSpinnerInBorder(width);\n\t\t\tstatusWidth = visibleWidth(status);\n\t\t}\n\n\t\tif (canFitOverflow()) {\n\t\t\tconst leftBlockWidth = 3 + statusWidth + 1;\n\t\t\treturn (\n\t\t\t\tthis.borderColor(\"── \") +\n\t\t\t\tstatus +\n\t\t\t\tthis.borderColor(\n\t\t\t\t\t` ${\"─\".repeat(overflowStart - leftBlockWidth)}${overflowLabel}${\"─\".repeat(width - overflowStart - overflowLabelWidth)}`,\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\tif (width >= statusWidth + 5) {\n\t\t\treturn this.borderColor(\"── \") + status + this.borderColor(` ${\"─\".repeat(width - statusWidth - 4)}`);\n\t\t}\n\n\t\tstatus = this.workingStatusIndicator.renderSpinnerInBorder(width);\n\t\tstatusWidth = visibleWidth(status);\n\t\tconst prefixWidth = Math.min(3, Math.max(0, width - statusWidth));\n\t\treturn (\n\t\t\tthis.borderColor(\"─\".repeat(prefixWidth)) +\n\t\t\tstatus +\n\t\t\tthis.borderColor(\"─\".repeat(Math.max(0, width - prefixWidth - statusWidth)))\n\t\t);\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for clipboard paste keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Explicit history bindings take precedence over app actions while the editor is focused.\n\t\t// This lets users bind Ctrl+P even though it cycles models by default.\n\t\tif (\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyPrevious\") ||\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyNext\")\n\t\t) {\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
|
|
@@ -126,6 +126,8 @@ export class CustomEditor extends Editor {
|
|
|
126
126
|
placeholder;
|
|
127
127
|
autocompleteRule;
|
|
128
128
|
autocompleteFooter;
|
|
129
|
+
workingStatusIndicator;
|
|
130
|
+
embedWorkingStatus;
|
|
129
131
|
actionHandlers = new Map();
|
|
130
132
|
// Special handlers that can be dynamically replaced
|
|
131
133
|
onEscape;
|
|
@@ -154,6 +156,7 @@ export class CustomEditor extends Editor {
|
|
|
154
156
|
this.surfaceColor = options?.surfaceColor;
|
|
155
157
|
this.autocompleteRule = options?.autocompleteRule;
|
|
156
158
|
this.autocompleteFooter = options?.autocompleteFooter;
|
|
159
|
+
this.embedWorkingStatus = options?.embedWorkingStatus ?? false;
|
|
157
160
|
}
|
|
158
161
|
render(width) {
|
|
159
162
|
const canRenderSurface = this.surfaceColor !== undefined && width > 0;
|
|
@@ -363,6 +366,41 @@ export class CustomEditor extends Editor {
|
|
|
363
366
|
const match = /^(?:\x1b_pi:c\x07)?(?:\x1b\[7m.\x1b\[(?:0|27)m)?/.exec(line);
|
|
364
367
|
return match?.[0] ?? "";
|
|
365
368
|
}
|
|
369
|
+
setWorkingStatusIndicator(indicator) {
|
|
370
|
+
this.workingStatusIndicator = indicator;
|
|
371
|
+
}
|
|
372
|
+
renderTopBorder(width, hiddenLineCount) {
|
|
373
|
+
if (!this.embedWorkingStatus || !this.workingStatusIndicator || width <= 0) {
|
|
374
|
+
return super.renderTopBorder(width, hiddenLineCount);
|
|
375
|
+
}
|
|
376
|
+
let status = this.workingStatusIndicator.renderInBorder(Math.max(1, width - 5));
|
|
377
|
+
let statusWidth = visibleWidth(status);
|
|
378
|
+
if (statusWidth === 0)
|
|
379
|
+
return super.renderTopBorder(width, hiddenLineCount);
|
|
380
|
+
const overflowLabel = hiddenLineCount > 0 ? ` ↑ ${hiddenLineCount} more ` : undefined;
|
|
381
|
+
const overflowLabelWidth = overflowLabel ? visibleWidth(overflowLabel) : 0;
|
|
382
|
+
const overflowStart = Math.floor((width - overflowLabelWidth) / 2);
|
|
383
|
+
const canFitOverflow = () => overflowLabel !== undefined && overflowLabelWidth + 2 <= width && overflowStart - (3 + statusWidth + 1) >= 1;
|
|
384
|
+
if (overflowLabel && !canFitOverflow()) {
|
|
385
|
+
status = this.workingStatusIndicator.renderSpinnerInBorder(width);
|
|
386
|
+
statusWidth = visibleWidth(status);
|
|
387
|
+
}
|
|
388
|
+
if (canFitOverflow()) {
|
|
389
|
+
const leftBlockWidth = 3 + statusWidth + 1;
|
|
390
|
+
return (this.borderColor("── ") +
|
|
391
|
+
status +
|
|
392
|
+
this.borderColor(` ${"─".repeat(overflowStart - leftBlockWidth)}${overflowLabel}${"─".repeat(width - overflowStart - overflowLabelWidth)}`));
|
|
393
|
+
}
|
|
394
|
+
if (width >= statusWidth + 5) {
|
|
395
|
+
return this.borderColor("── ") + status + this.borderColor(` ${"─".repeat(width - statusWidth - 4)}`);
|
|
396
|
+
}
|
|
397
|
+
status = this.workingStatusIndicator.renderSpinnerInBorder(width);
|
|
398
|
+
statusWidth = visibleWidth(status);
|
|
399
|
+
const prefixWidth = Math.min(3, Math.max(0, width - statusWidth));
|
|
400
|
+
return (this.borderColor("─".repeat(prefixWidth)) +
|
|
401
|
+
status +
|
|
402
|
+
this.borderColor("─".repeat(Math.max(0, width - prefixWidth - statusWidth))));
|
|
403
|
+
}
|
|
366
404
|
/**
|
|
367
405
|
* Register a handler for an app action.
|
|
368
406
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tCURSOR_MARKER,\n\tEditor,\n\ttype EditorOptions,\n\ttype EditorTheme,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.ts\";\nimport { stripAnsi } from \"../../../utils/ansi.ts\";\n\n/** Which marker the dock draws. The set is closed so a mode cannot arrive without a marker. */\nexport type PromptMode = \"agent\" | \"bash\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\t/** Marker rendered at the start of the first input line, e.g. `\"> \"`. */\n\tpromptPrefix?: string;\n\t/** Styles the prompt prefix. Defaults to leaving it unstyled. */\n\tpromptColor?: (text: string) => string;\n\t/** Marker for bash mode. Defaults to `promptPrefix`, so mode is carried by colour alone. */\n\tbashPromptPrefix?: string;\n\t/** Styles the bash marker. Defaults to `promptColor`. */\n\tbashPromptColor?: (text: string) => string;\n\t/** Shown in place of the empty input line. */\n\tplaceholder?: string;\n\t/** Styles the placeholder. Defaults to leaving it unstyled. */\n\tplaceholderColor?: (text: string) => string;\n\t/** Styles a leading `/command` token. Defaults to leaving it unstyled. */\n\tcommandColor?: (text: string) => string;\n\t/** Applies the filled prompt surface around the editor block. */\n\tsurfaceColor?: (text: string) => string;\n\t/** Rule drawn above the autocomplete rows. Omit for no rule. */\n\tautocompleteRule?: (width: number) => string;\n\t/** Keybinding footer drawn under the autocomplete rows. Omit for no footer. */\n\tautocompleteFooter?: () => string;\n}\n\n/** Box-drawing horizontal rule: the character the base Editor draws borders from. */\nconst BORDER_CHAR = \"─\";\n\n/**\n * The two shapes the base Editor draws a border in: a plain full-width rule, and\n * the scroll indicator (`─── ↑ 3 more ────`).\n *\n * Both are anchored deliberately. A prefix test would count any content line that\n * merely *starts* with the rule character — a pasted divider, `─notes` — as a\n * border, which is worse than it sounds: {@link CustomEditor.render} tells the\n * content region from the autocomplete region by counting borders, so one\n * miscount shifts every later line into the wrong region.\n */\nconst BORDER_RULE = /^─+$/;\nconst BORDER_SCROLL = /^─+ [↑↓] \\d+ more/;\n\n/**\n * A leading `/command` token: optional indent, a slash, then non-space.\n * Matched against the line's *visible* text, not the rendered string.\n */\nconst COMMAND_TOKEN = /^(\\s*)(\\/\\S+)/;\n\n/**\n * One ANSI control sequence: a CSI escape (`ESC [ … letter`) or the APC\n * hardware-cursor marker (`ESC _ … BEL`). Sticky, for position-by-position\n * scanning in {@link colorVisibleRange}.\n */\nconst CONTROL_SEQUENCE = /\\x1b\\[[0-9;]*[A-Za-z]|\\x1b_[^\\x07]*\\x07/y;\n\n/** The reverse-video cursor cell emitted by the inherited editor. */\nconst INVERTED_CURSOR_CELL = /\\x1b\\[7m[\\s\\S]\\x1b\\[(?:0|27)m/y;\n\nconst SURFACE_PADDING = 2;\n\n/**\n * ============================ ANSI measuring hazards ============================\n *\n * Two traps live in the rendered strings this file post-processes. Both produced\n * real bugs; both are cheap to re-introduce. Read this before touching the width\n * maths or any leading-sequence match.\n *\n * 1. DO NOT COMPOSE `stripAnsi` WITH `visibleWidth`.\n *\n * `stripAnsi` removes SGR colour codes but leaves the APC hardware-cursor\n * marker (`ESC _pi:c BEL`, see CURSOR_MARKER in packages/tui) fully intact —\n * all seven bytes of it. `visibleWidth` already handles that marker correctly\n * and scores it zero.\n *\n * So `visibleWidth(stripAnsi(line))` counts seven phantom columns on every\n * line carrying a cursor, which silently widened each placeholder line past\n * the terminal width. Measure with `visibleWidth` alone. Use `stripAnsi` only\n * to read text, never to measure it.\n *\n * 2. DO NOT MATCH THE LEADING SEQUENCE RUN GENERICALLY.\n *\n * The base Editor renders an empty line as:\n * CURSOR_MARKER ESC[7m <space> ESC[0m <padding>\n * The `ESC[7m` turns on reverse video for exactly the one-cell cursor block,\n * and `ESC[0m` turns it back off.\n *\n * A permissive prefix match such as `(?:\\x1b\\[[0-9;]*m)*` consumes that\n * `ESC[7m` as though it were ordinary leading styling. Whatever is appended\n * next then inherits reverse video, and because the matching `ESC[0m` was\n * left behind in the discarded remainder, the inversion runs on through the\n * placeholder and the rest of the frame. Match the reverse-video run\n * explicitly instead — see {@link CustomEditor.sliceLeadingCursorCell}.\n *\n * ==============================================================================\n */\n\n/**\n * Wrap the visible characters in `[start, end)` with `colorFn`, leaving every\n * control sequence in place.\n *\n * The range is in visible columns, so it is unaffected by however many escape\n * sequences are interleaved. That matters because the cursor cell can land in\n * the middle of the token being coloured: with the caret at column 3 of\n * `/model`, the line reads `/mo` + CURSOR_MARKER + `ESC[7m` + `d` + `ESC[0m` +\n * `el`. Colour is therefore re-opened after every control sequence rather than\n * once around the whole token, since the cursor cell's `ESC[0m` would otherwise\n * cancel it partway through.\n */\nfunction colorVisibleRange(line: string, start: number, end: number, colorFn: (text: string) => string): string {\n\tlet out = \"\";\n\tlet buffer = \"\";\n\tlet column = 0;\n\tlet index = 0;\n\n\tconst flush = () => {\n\t\tif (buffer) {\n\t\t\tout += colorFn(buffer);\n\t\t\tbuffer = \"\";\n\t\t}\n\t};\n\n\twhile (index < line.length) {\n\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\tif (control) {\n\t\t\tflush();\n\t\t\tout += control[0];\n\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\tcontinue;\n\t\t}\n\t\tconst char = line[index];\n\t\tif (column >= start && column < end) {\n\t\t\tbuffer += char;\n\t\t} else {\n\t\t\tflush();\n\t\t\tout += char;\n\t\t}\n\t\tcolumn += visibleWidth(char);\n\t\tindex += 1;\n\t}\n\tflush();\n\treturn out;\n}\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n *\n * The prompt prefix and placeholder are layered on in `render` rather than\n * inside the base Editor, because `packages/tui` is frozen under ADR 0001 and\n * its `EditorOptions` has no hook for either.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate readonly prompts: Record<PromptMode, { prefix: string; color: (text: string) => string }>;\n\tprivate promptMode: PromptMode = \"agent\";\n\tprivate readonly placeholderColor: (text: string) => string;\n\tprivate readonly commandColor: ((text: string) => string) | undefined;\n\tprivate readonly surfaceColor: ((text: string) => string) | undefined;\n\tprivate readonly placeholder: string | undefined;\n\tprivate readonly autocompleteRule: ((width: number) => string) | undefined;\n\tprivate readonly autocompleteFooter: (() => string) | undefined;\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tconst agentPrefix = options?.promptPrefix ?? \"\";\n\t\tconst agentColor = options?.promptColor ?? ((text) => text);\n\t\tconst bashPrefix = options?.bashPromptPrefix ?? agentPrefix;\n\t\t// Every marker occupies the same cells, so switching mode cannot move the\n\t\t// caret or reflow what is already typed. Padding rather than throwing keeps\n\t\t// a mismatched caller rendering instead of crashing its dock.\n\t\tconst cells = Math.max(visibleWidth(agentPrefix), visibleWidth(bashPrefix));\n\t\tconst pad = (text: string) => text + \" \".repeat(cells - visibleWidth(text));\n\t\tthis.prompts = {\n\t\t\tagent: { prefix: pad(agentPrefix), color: agentColor },\n\t\t\tbash: { prefix: pad(bashPrefix), color: options?.bashPromptColor ?? agentColor },\n\t\t};\n\t\tthis.placeholder = options?.placeholder;\n\t\tthis.placeholderColor = options?.placeholderColor ?? ((text) => text);\n\t\tthis.commandColor = options?.commandColor;\n\t\tthis.surfaceColor = options?.surfaceColor;\n\t\tthis.autocompleteRule = options?.autocompleteRule;\n\t\tthis.autocompleteFooter = options?.autocompleteFooter;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst canRenderSurface = this.surfaceColor !== undefined && width > 0;\n\t\tconst surfacePadding = canRenderSurface ? Math.min(SURFACE_PADDING, Math.floor((width - 1) / 2)) : 0;\n\t\tconst editorWidth = canRenderSurface ? width - surfacePadding * 2 : width;\n\t\tconst lines = this.withAutocompleteChrome(this.renderEditor(editorWidth), editorWidth);\n\t\treturn canRenderSurface ? this.withSurface(lines, width, surfacePadding) : lines;\n\t}\n\n\tprivate renderEditor(width: number): string[] {\n\t\tconst { prefix: promptPrefix, color: promptColor } = this.prompts[this.promptMode];\n\t\tconst prefixWidth = visibleWidth(promptPrefix);\n\t\t// Reserve the prefix out of the width handed to the base Editor, so its\n\t\t// wrapping, scrolling and cursor column all account for the space the\n\t\t// prefix occupies. Bail out entirely when the terminal cannot spare it.\n\t\tif (prefixWidth === 0 || width <= prefixWidth + 4) {\n\t\t\treturn this.withCommandColor(this.withPlaceholder(super.render(width)));\n\t\t}\n\n\t\tconst inner = this.withCommandColor(this.withPlaceholder(super.render(width - prefixWidth)));\n\t\tconst borderPad = this.borderColor(BORDER_CHAR.repeat(prefixWidth));\n\t\tconst blank = \" \".repeat(prefixWidth);\n\t\tconst styledPrefix = promptColor(promptPrefix);\n\n\t\t// Structure is: top border, content lines, bottom border, then optional\n\t\t// autocomplete rows. Counting borders tells the three regions apart\n\t\t// without depending on how many lines each contains.\n\t\tlet borders = 0;\n\t\tlet firstContentLine = true;\n\t\treturn inner.map((line) => {\n\t\t\tif (this.isBorderLine(line)) {\n\t\t\t\tborders++;\n\t\t\t\treturn borderPad + line;\n\t\t\t}\n\t\t\tif (borders !== 1) {\n\t\t\t\t// Autocomplete rows sit below the box; indent them to match.\n\t\t\t\treturn blank + line;\n\t\t\t}\n\t\t\tif (firstContentLine) {\n\t\t\t\tfirstContentLine = false;\n\t\t\t\treturn styledPrefix + line;\n\t\t\t}\n\t\t\treturn blank + line;\n\t\t});\n\t}\n\n\t/**\n\t * Brackets the autocomplete rows with a rule and a keybinding footer.\n\t *\n\t * The rows arrive after the editor's second border, so the same border count\n\t * that separates the box from its dropdown elsewhere in this file finds them\n\t * here. Nothing is added when the dropdown is closed.\n\t */\n\tprivate withAutocompleteChrome(lines: string[], width: number): string[] {\n\t\tif (!this.autocompleteRule && !this.autocompleteFooter) return lines;\n\t\tconst blockLineCount = this.editorBlockLineCount(lines);\n\t\tconst rows = lines.slice(blockLineCount);\n\t\tif (rows.length === 0) return lines;\n\n\t\tconst chrome = [...lines.slice(0, blockLineCount)];\n\t\tif (this.autocompleteRule) chrome.push(this.autocompleteRule(width));\n\t\tchrome.push(...rows);\n\t\tif (this.autocompleteFooter) chrome.push(this.autocompleteFooter());\n\t\treturn chrome;\n\t}\n\n\tprivate withSurface(lines: string[], width: number, padding: number): string[] {\n\t\tconst surfaceColor = this.surfaceColor;\n\t\tif (!surfaceColor) return lines;\n\n\t\tconst result = [this.surfaceLine(\" \".repeat(width), surfaceColor)];\n\t\tconst editorBlockLineCount = this.editorBlockLineCount(lines);\n\t\tconst editorContent = lines.slice(1, Math.max(1, editorBlockLineCount - 1));\n\t\tfor (const line of editorContent) {\n\t\t\tresult.push(this.surfaceLine(`${\" \".repeat(padding)}${line}${\" \".repeat(padding)}`, surfaceColor));\n\t\t}\n\t\tresult.push(this.surfaceLine(\" \".repeat(width), surfaceColor));\n\t\tfor (const line of lines.slice(editorBlockLineCount)) {\n\t\t\tresult.push(`${\" \".repeat(padding)}${line}`);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate editorBlockLineCount(lines: string[]): number {\n\t\tlet borders = 0;\n\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\tif (this.isBorderLine(lines[index])) borders++;\n\t\t\tif (borders === 2) return index + 1;\n\t\t}\n\t\treturn lines.length;\n\t}\n\n\tprivate surfaceLine(line: string, surfaceColor: (text: string) => string): string {\n\t\tlet output = \"\";\n\t\tlet visibleText = \"\";\n\t\tlet index = 0;\n\n\t\tconst flush = () => {\n\t\t\tif (visibleText) {\n\t\t\t\toutput += surfaceColor(visibleText);\n\t\t\t\tvisibleText = \"\";\n\t\t\t}\n\t\t};\n\n\t\twhile (index < line.length) {\n\t\t\tINVERTED_CURSOR_CELL.lastIndex = index;\n\t\t\tconst cursorCell = INVERTED_CURSOR_CELL.exec(line);\n\t\t\tif (cursorCell) {\n\t\t\t\tflush();\n\t\t\t\toutput += cursorCell[0];\n\t\t\t\tindex = INVERTED_CURSOR_CELL.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\t\tif (control) {\n\t\t\t\tflush();\n\t\t\t\toutput += control[0];\n\t\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvisibleText += line[index];\n\t\t\tindex += 1;\n\t\t}\n\n\t\tflush();\n\t\treturn output;\n\t}\n\n\tsetPromptMode(mode: PromptMode): void {\n\t\tif (this.promptMode === mode) return;\n\t\tthis.promptMode = mode;\n\t\tthis.invalidate();\n\t}\n\n\tprivate isBorderLine(line: string): boolean {\n\t\tconst visible = stripAnsi(line.split(CURSOR_MARKER).join(\"\"));\n\t\treturn BORDER_RULE.test(visible) || BORDER_SCROLL.test(visible);\n\t}\n\n\t/**\n\t * Tint a leading `/command` on the first input line.\n\t *\n\t * The base Editor emits input text unstyled, so the only styling already\n\t * present on the line is the cursor cell — which `colorVisibleRange` steps\n\t * over. Only the command token is tinted; arguments after it stay plain, so\n\t * the highlight marks what is being invoked rather than the whole line.\n\t */\n\tprivate withCommandColor(lines: string[]): string[] {\n\t\tconst commandColor = this.commandColor;\n\t\tif (!commandColor || lines.length < 2) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Read the token from the model rather than the rendered line: the first\n\t\t// visual row is the start of the text only when the caret has not scrolled\n\t\t// the view down, and getText is unambiguous either way.\n\t\tconst firstLine = this.getText().split(\"\\n\", 1)[0] ?? \"\";\n\t\tconst match = COMMAND_TOKEN.exec(firstLine);\n\t\tif (!match) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst [, indent, token] = match;\n\t\t// The rendered row must still begin with that token, or the view has\n\t\t// scrolled and the highlight would land on unrelated text.\n\t\tif (!stripAnsi(lines[1].split(CURSOR_MARKER).join(\"\")).startsWith(`${indent}${token}`)) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst start = visibleWidth(indent);\n\t\tconst updated = [...lines];\n\t\tupdated[1] = colorVisibleRange(lines[1], start, start + visibleWidth(token), commandColor);\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Replace the sole content line with the placeholder while the input is\n\t * empty. Index 1 is always the first content line, and an empty editor has\n\t * exactly one, so no line classification is needed here.\n\t */\n\tprivate withPlaceholder(lines: string[]): string[] {\n\t\tconst placeholder = this.placeholder;\n\t\tif (!placeholder || lines.length < 2 || this.getText().length > 0 || this.isShowingAutocomplete()) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst contentLine = lines[1];\n\t\t// Preserve the cursor cell when focused; unfocused there is none, and the\n\t\t// placeholder simply starts at column zero.\n\t\tconst cursorCell = this.sliceLeadingCursorCell(contentLine);\n\t\t// Measure with visibleWidth alone — never visibleWidth(stripAnsi(...)),\n\t\t// which scores the cursor marker as seven columns. See hazard 1 at the top\n\t\t// of this file.\n\t\tconst available = visibleWidth(contentLine) - visibleWidth(cursorCell);\n\t\tif (available <= 0) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Cut by display width, not by String.slice: `available` counts terminal\n\t\t// columns, while slice counts UTF-16 code units. They only agree for\n\t\t// plain ASCII, and disagreeing splits a surrogate pair or overruns the\n\t\t// line on the first wide character.\n\t\tconst text = truncateToWidth(placeholder, available, \"\");\n\t\tconst padding = \" \".repeat(Math.max(0, available - visibleWidth(text)));\n\t\tconst updated = [...lines];\n\t\tupdated[1] = cursorCell + this.placeholderColor(text) + padding;\n\t\treturn updated;\n\t}\n\n\t/**\n\t * The base Editor renders an empty line as the hardware-cursor marker\n\t * followed by a reverse-video cell: `CURSOR_MARKER ESC[7m <space> ESC[0m`.\n\t * Both parts are kept verbatim.\n\t *\n\t * The `ESC[7m … ESC[0m` run is matched explicitly. Do not \"simplify\" this to\n\t * a general leading-SGR match such as `(?:\\x1b\\[[0-9;]*m)*` — see hazard 2 at\n\t * the top of this file for what that breaks.\n\t */\n\tprivate sliceLeadingCursorCell(line: string): string {\n\t\tconst match = /^(?:\\x1b_pi:c\\x07)?(?:\\x1b\\[7m.\\x1b\\[(?:0|27)m)?/.exec(line);\n\t\treturn match?.[0] ?? \"\";\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for clipboard paste keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Explicit history bindings take precedence over app actions while the editor is focused.\n\t\t// This lets users bind Ctrl+P even though it cycles models by default.\n\t\tif (\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyPrevious\") ||\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyNext\")\n\t\t) {\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"custom-editor.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tCURSOR_MARKER,\n\tEditor,\n\ttype EditorOptions,\n\ttype EditorTheme,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.ts\";\nimport { stripAnsi } from \"../../../utils/ansi.ts\";\nimport type { StatusIndicator } from \"./status-indicator.ts\";\n\n/** Which marker the dock draws. The set is closed so a mode cannot arrive without a marker. */\nexport type PromptMode = \"agent\" | \"bash\";\n\nexport interface CustomEditorOptions extends EditorOptions {\n\t/** Marker rendered at the start of the first input line, e.g. `\"> \"`. */\n\tpromptPrefix?: string;\n\t/** Styles the prompt prefix. Defaults to leaving it unstyled. */\n\tpromptColor?: (text: string) => string;\n\t/** Marker for bash mode. Defaults to `promptPrefix`, so mode is carried by colour alone. */\n\tbashPromptPrefix?: string;\n\t/** Styles the bash marker. Defaults to `promptColor`. */\n\tbashPromptColor?: (text: string) => string;\n\t/** Shown in place of the empty input line. */\n\tplaceholder?: string;\n\t/** Styles the placeholder. Defaults to leaving it unstyled. */\n\tplaceholderColor?: (text: string) => string;\n\t/** Styles a leading `/command` token. Defaults to leaving it unstyled. */\n\tcommandColor?: (text: string) => string;\n\t/** Applies the filled prompt surface around the editor block. */\n\tsurfaceColor?: (text: string) => string;\n\t/** Rule drawn above the autocomplete rows. Omit for no rule. */\n\tautocompleteRule?: (width: number) => string;\n\t/** Keybinding footer drawn under the autocomplete rows. Omit for no footer. */\n\tautocompleteFooter?: () => string;\n\t/**\n\t * Render the streaming working status in the editor's top border. Apex's\n\t * default composer leaves this off: the footer tray carries the working\n\t * status, and the filled surface drops the top border anyway. Kept so a\n\t * custom editor built on this class can opt in, as upstream allows.\n\t */\n\tembedWorkingStatus?: boolean;\n}\n\n/** Box-drawing horizontal rule: the character the base Editor draws borders from. */\nconst BORDER_CHAR = \"─\";\n\n/**\n * The two shapes the base Editor draws a border in: a plain full-width rule, and\n * the scroll indicator (`─── ↑ 3 more ────`).\n *\n * Both are anchored deliberately. A prefix test would count any content line that\n * merely *starts* with the rule character — a pasted divider, `─notes` — as a\n * border, which is worse than it sounds: {@link CustomEditor.render} tells the\n * content region from the autocomplete region by counting borders, so one\n * miscount shifts every later line into the wrong region.\n */\nconst BORDER_RULE = /^─+$/;\nconst BORDER_SCROLL = /^─+ [↑↓] \\d+ more/;\n\n/**\n * A leading `/command` token: optional indent, a slash, then non-space.\n * Matched against the line's *visible* text, not the rendered string.\n */\nconst COMMAND_TOKEN = /^(\\s*)(\\/\\S+)/;\n\n/**\n * One ANSI control sequence: a CSI escape (`ESC [ … letter`) or the APC\n * hardware-cursor marker (`ESC _ … BEL`). Sticky, for position-by-position\n * scanning in {@link colorVisibleRange}.\n */\nconst CONTROL_SEQUENCE = /\\x1b\\[[0-9;]*[A-Za-z]|\\x1b_[^\\x07]*\\x07/y;\n\n/** The reverse-video cursor cell emitted by the inherited editor. */\nconst INVERTED_CURSOR_CELL = /\\x1b\\[7m[\\s\\S]\\x1b\\[(?:0|27)m/y;\n\nconst SURFACE_PADDING = 2;\n\n/**\n * ============================ ANSI measuring hazards ============================\n *\n * Two traps live in the rendered strings this file post-processes. Both produced\n * real bugs; both are cheap to re-introduce. Read this before touching the width\n * maths or any leading-sequence match.\n *\n * 1. DO NOT COMPOSE `stripAnsi` WITH `visibleWidth`.\n *\n * `stripAnsi` removes SGR colour codes but leaves the APC hardware-cursor\n * marker (`ESC _pi:c BEL`, see CURSOR_MARKER in packages/tui) fully intact —\n * all seven bytes of it. `visibleWidth` already handles that marker correctly\n * and scores it zero.\n *\n * So `visibleWidth(stripAnsi(line))` counts seven phantom columns on every\n * line carrying a cursor, which silently widened each placeholder line past\n * the terminal width. Measure with `visibleWidth` alone. Use `stripAnsi` only\n * to read text, never to measure it.\n *\n * 2. DO NOT MATCH THE LEADING SEQUENCE RUN GENERICALLY.\n *\n * The base Editor renders an empty line as:\n * CURSOR_MARKER ESC[7m <space> ESC[0m <padding>\n * The `ESC[7m` turns on reverse video for exactly the one-cell cursor block,\n * and `ESC[0m` turns it back off.\n *\n * A permissive prefix match such as `(?:\\x1b\\[[0-9;]*m)*` consumes that\n * `ESC[7m` as though it were ordinary leading styling. Whatever is appended\n * next then inherits reverse video, and because the matching `ESC[0m` was\n * left behind in the discarded remainder, the inversion runs on through the\n * placeholder and the rest of the frame. Match the reverse-video run\n * explicitly instead — see {@link CustomEditor.sliceLeadingCursorCell}.\n *\n * ==============================================================================\n */\n\n/**\n * Wrap the visible characters in `[start, end)` with `colorFn`, leaving every\n * control sequence in place.\n *\n * The range is in visible columns, so it is unaffected by however many escape\n * sequences are interleaved. That matters because the cursor cell can land in\n * the middle of the token being coloured: with the caret at column 3 of\n * `/model`, the line reads `/mo` + CURSOR_MARKER + `ESC[7m` + `d` + `ESC[0m` +\n * `el`. Colour is therefore re-opened after every control sequence rather than\n * once around the whole token, since the cursor cell's `ESC[0m` would otherwise\n * cancel it partway through.\n */\nfunction colorVisibleRange(line: string, start: number, end: number, colorFn: (text: string) => string): string {\n\tlet out = \"\";\n\tlet buffer = \"\";\n\tlet column = 0;\n\tlet index = 0;\n\n\tconst flush = () => {\n\t\tif (buffer) {\n\t\t\tout += colorFn(buffer);\n\t\t\tbuffer = \"\";\n\t\t}\n\t};\n\n\twhile (index < line.length) {\n\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\tif (control) {\n\t\t\tflush();\n\t\t\tout += control[0];\n\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\tcontinue;\n\t\t}\n\t\tconst char = line[index];\n\t\tif (column >= start && column < end) {\n\t\t\tbuffer += char;\n\t\t} else {\n\t\t\tflush();\n\t\t\tout += char;\n\t\t}\n\t\tcolumn += visibleWidth(char);\n\t\tindex += 1;\n\t}\n\tflush();\n\treturn out;\n}\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n *\n * The prompt prefix and placeholder are layered on in `render` rather than\n * inside the base Editor, because `packages/tui` is frozen under ADR 0001 and\n * its `EditorOptions` has no hook for either.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\n\tprivate readonly prompts: Record<PromptMode, { prefix: string; color: (text: string) => string }>;\n\tprivate promptMode: PromptMode = \"agent\";\n\tprivate readonly placeholderColor: (text: string) => string;\n\tprivate readonly commandColor: ((text: string) => string) | undefined;\n\tprivate readonly surfaceColor: ((text: string) => string) | undefined;\n\tprivate readonly placeholder: string | undefined;\n\tprivate readonly autocompleteRule: ((width: number) => string) | undefined;\n\tprivate readonly autocompleteFooter: (() => string) | undefined;\n\tprivate workingStatusIndicator: StatusIndicator | undefined;\n\tpublic readonly embedWorkingStatus: boolean;\n\tpublic actionHandlers: Map<AppKeybinding, () => void> = new Map();\n\n\t// Special handlers that can be dynamically replaced\n\tpublic onEscape?: () => void;\n\tpublic onCtrlD?: () => void;\n\tpublic onPasteImage?: () => void;\n\t/** Handler for extension-registered shortcuts. Returns true if handled. */\n\tpublic onExtensionShortcut?: (data: string) => boolean;\n\n\tconstructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\n\t\tconst agentPrefix = options?.promptPrefix ?? \"\";\n\t\tconst agentColor = options?.promptColor ?? ((text) => text);\n\t\tconst bashPrefix = options?.bashPromptPrefix ?? agentPrefix;\n\t\t// Every marker occupies the same cells, so switching mode cannot move the\n\t\t// caret or reflow what is already typed. Padding rather than throwing keeps\n\t\t// a mismatched caller rendering instead of crashing its dock.\n\t\tconst cells = Math.max(visibleWidth(agentPrefix), visibleWidth(bashPrefix));\n\t\tconst pad = (text: string) => text + \" \".repeat(cells - visibleWidth(text));\n\t\tthis.prompts = {\n\t\t\tagent: { prefix: pad(agentPrefix), color: agentColor },\n\t\t\tbash: { prefix: pad(bashPrefix), color: options?.bashPromptColor ?? agentColor },\n\t\t};\n\t\tthis.placeholder = options?.placeholder;\n\t\tthis.placeholderColor = options?.placeholderColor ?? ((text) => text);\n\t\tthis.commandColor = options?.commandColor;\n\t\tthis.surfaceColor = options?.surfaceColor;\n\t\tthis.autocompleteRule = options?.autocompleteRule;\n\t\tthis.autocompleteFooter = options?.autocompleteFooter;\n\t\tthis.embedWorkingStatus = options?.embedWorkingStatus ?? false;\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst canRenderSurface = this.surfaceColor !== undefined && width > 0;\n\t\tconst surfacePadding = canRenderSurface ? Math.min(SURFACE_PADDING, Math.floor((width - 1) / 2)) : 0;\n\t\tconst editorWidth = canRenderSurface ? width - surfacePadding * 2 : width;\n\t\tconst lines = this.withAutocompleteChrome(this.renderEditor(editorWidth), editorWidth);\n\t\treturn canRenderSurface ? this.withSurface(lines, width, surfacePadding) : lines;\n\t}\n\n\tprivate renderEditor(width: number): string[] {\n\t\tconst { prefix: promptPrefix, color: promptColor } = this.prompts[this.promptMode];\n\t\tconst prefixWidth = visibleWidth(promptPrefix);\n\t\t// Reserve the prefix out of the width handed to the base Editor, so its\n\t\t// wrapping, scrolling and cursor column all account for the space the\n\t\t// prefix occupies. Bail out entirely when the terminal cannot spare it.\n\t\tif (prefixWidth === 0 || width <= prefixWidth + 4) {\n\t\t\treturn this.withCommandColor(this.withPlaceholder(super.render(width)));\n\t\t}\n\n\t\tconst inner = this.withCommandColor(this.withPlaceholder(super.render(width - prefixWidth)));\n\t\tconst borderPad = this.borderColor(BORDER_CHAR.repeat(prefixWidth));\n\t\tconst blank = \" \".repeat(prefixWidth);\n\t\tconst styledPrefix = promptColor(promptPrefix);\n\n\t\t// Structure is: top border, content lines, bottom border, then optional\n\t\t// autocomplete rows. Counting borders tells the three regions apart\n\t\t// without depending on how many lines each contains.\n\t\tlet borders = 0;\n\t\tlet firstContentLine = true;\n\t\treturn inner.map((line) => {\n\t\t\tif (this.isBorderLine(line)) {\n\t\t\t\tborders++;\n\t\t\t\treturn borderPad + line;\n\t\t\t}\n\t\t\tif (borders !== 1) {\n\t\t\t\t// Autocomplete rows sit below the box; indent them to match.\n\t\t\t\treturn blank + line;\n\t\t\t}\n\t\t\tif (firstContentLine) {\n\t\t\t\tfirstContentLine = false;\n\t\t\t\treturn styledPrefix + line;\n\t\t\t}\n\t\t\treturn blank + line;\n\t\t});\n\t}\n\n\t/**\n\t * Brackets the autocomplete rows with a rule and a keybinding footer.\n\t *\n\t * The rows arrive after the editor's second border, so the same border count\n\t * that separates the box from its dropdown elsewhere in this file finds them\n\t * here. Nothing is added when the dropdown is closed.\n\t */\n\tprivate withAutocompleteChrome(lines: string[], width: number): string[] {\n\t\tif (!this.autocompleteRule && !this.autocompleteFooter) return lines;\n\t\tconst blockLineCount = this.editorBlockLineCount(lines);\n\t\tconst rows = lines.slice(blockLineCount);\n\t\tif (rows.length === 0) return lines;\n\n\t\tconst chrome = [...lines.slice(0, blockLineCount)];\n\t\tif (this.autocompleteRule) chrome.push(this.autocompleteRule(width));\n\t\tchrome.push(...rows);\n\t\tif (this.autocompleteFooter) chrome.push(this.autocompleteFooter());\n\t\treturn chrome;\n\t}\n\n\tprivate withSurface(lines: string[], width: number, padding: number): string[] {\n\t\tconst surfaceColor = this.surfaceColor;\n\t\tif (!surfaceColor) return lines;\n\n\t\tconst result = [this.surfaceLine(\" \".repeat(width), surfaceColor)];\n\t\tconst editorBlockLineCount = this.editorBlockLineCount(lines);\n\t\tconst editorContent = lines.slice(1, Math.max(1, editorBlockLineCount - 1));\n\t\tfor (const line of editorContent) {\n\t\t\tresult.push(this.surfaceLine(`${\" \".repeat(padding)}${line}${\" \".repeat(padding)}`, surfaceColor));\n\t\t}\n\t\tresult.push(this.surfaceLine(\" \".repeat(width), surfaceColor));\n\t\tfor (const line of lines.slice(editorBlockLineCount)) {\n\t\t\tresult.push(`${\" \".repeat(padding)}${line}`);\n\t\t}\n\t\treturn result;\n\t}\n\n\tprivate editorBlockLineCount(lines: string[]): number {\n\t\tlet borders = 0;\n\t\tfor (let index = 0; index < lines.length; index++) {\n\t\t\tif (this.isBorderLine(lines[index])) borders++;\n\t\t\tif (borders === 2) return index + 1;\n\t\t}\n\t\treturn lines.length;\n\t}\n\n\tprivate surfaceLine(line: string, surfaceColor: (text: string) => string): string {\n\t\tlet output = \"\";\n\t\tlet visibleText = \"\";\n\t\tlet index = 0;\n\n\t\tconst flush = () => {\n\t\t\tif (visibleText) {\n\t\t\t\toutput += surfaceColor(visibleText);\n\t\t\t\tvisibleText = \"\";\n\t\t\t}\n\t\t};\n\n\t\twhile (index < line.length) {\n\t\t\tINVERTED_CURSOR_CELL.lastIndex = index;\n\t\t\tconst cursorCell = INVERTED_CURSOR_CELL.exec(line);\n\t\t\tif (cursorCell) {\n\t\t\t\tflush();\n\t\t\t\toutput += cursorCell[0];\n\t\t\t\tindex = INVERTED_CURSOR_CELL.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tCONTROL_SEQUENCE.lastIndex = index;\n\t\t\tconst control = CONTROL_SEQUENCE.exec(line);\n\t\t\tif (control) {\n\t\t\t\tflush();\n\t\t\t\toutput += control[0];\n\t\t\t\tindex = CONTROL_SEQUENCE.lastIndex;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvisibleText += line[index];\n\t\t\tindex += 1;\n\t\t}\n\n\t\tflush();\n\t\treturn output;\n\t}\n\n\tsetPromptMode(mode: PromptMode): void {\n\t\tif (this.promptMode === mode) return;\n\t\tthis.promptMode = mode;\n\t\tthis.invalidate();\n\t}\n\n\tprivate isBorderLine(line: string): boolean {\n\t\tconst visible = stripAnsi(line.split(CURSOR_MARKER).join(\"\"));\n\t\treturn BORDER_RULE.test(visible) || BORDER_SCROLL.test(visible);\n\t}\n\n\t/**\n\t * Tint a leading `/command` on the first input line.\n\t *\n\t * The base Editor emits input text unstyled, so the only styling already\n\t * present on the line is the cursor cell — which `colorVisibleRange` steps\n\t * over. Only the command token is tinted; arguments after it stay plain, so\n\t * the highlight marks what is being invoked rather than the whole line.\n\t */\n\tprivate withCommandColor(lines: string[]): string[] {\n\t\tconst commandColor = this.commandColor;\n\t\tif (!commandColor || lines.length < 2) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Read the token from the model rather than the rendered line: the first\n\t\t// visual row is the start of the text only when the caret has not scrolled\n\t\t// the view down, and getText is unambiguous either way.\n\t\tconst firstLine = this.getText().split(\"\\n\", 1)[0] ?? \"\";\n\t\tconst match = COMMAND_TOKEN.exec(firstLine);\n\t\tif (!match) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst [, indent, token] = match;\n\t\t// The rendered row must still begin with that token, or the view has\n\t\t// scrolled and the highlight would land on unrelated text.\n\t\tif (!stripAnsi(lines[1].split(CURSOR_MARKER).join(\"\")).startsWith(`${indent}${token}`)) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst start = visibleWidth(indent);\n\t\tconst updated = [...lines];\n\t\tupdated[1] = colorVisibleRange(lines[1], start, start + visibleWidth(token), commandColor);\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Replace the sole content line with the placeholder while the input is\n\t * empty. Index 1 is always the first content line, and an empty editor has\n\t * exactly one, so no line classification is needed here.\n\t */\n\tprivate withPlaceholder(lines: string[]): string[] {\n\t\tconst placeholder = this.placeholder;\n\t\tif (!placeholder || lines.length < 2 || this.getText().length > 0 || this.isShowingAutocomplete()) {\n\t\t\treturn lines;\n\t\t}\n\t\tconst contentLine = lines[1];\n\t\t// Preserve the cursor cell when focused; unfocused there is none, and the\n\t\t// placeholder simply starts at column zero.\n\t\tconst cursorCell = this.sliceLeadingCursorCell(contentLine);\n\t\t// Measure with visibleWidth alone — never visibleWidth(stripAnsi(...)),\n\t\t// which scores the cursor marker as seven columns. See hazard 1 at the top\n\t\t// of this file.\n\t\tconst available = visibleWidth(contentLine) - visibleWidth(cursorCell);\n\t\tif (available <= 0) {\n\t\t\treturn lines;\n\t\t}\n\t\t// Cut by display width, not by String.slice: `available` counts terminal\n\t\t// columns, while slice counts UTF-16 code units. They only agree for\n\t\t// plain ASCII, and disagreeing splits a surrogate pair or overruns the\n\t\t// line on the first wide character.\n\t\tconst text = truncateToWidth(placeholder, available, \"\");\n\t\tconst padding = \" \".repeat(Math.max(0, available - visibleWidth(text)));\n\t\tconst updated = [...lines];\n\t\tupdated[1] = cursorCell + this.placeholderColor(text) + padding;\n\t\treturn updated;\n\t}\n\n\t/**\n\t * The base Editor renders an empty line as the hardware-cursor marker\n\t * followed by a reverse-video cell: `CURSOR_MARKER ESC[7m <space> ESC[0m`.\n\t * Both parts are kept verbatim.\n\t *\n\t * The `ESC[7m … ESC[0m` run is matched explicitly. Do not \"simplify\" this to\n\t * a general leading-SGR match such as `(?:\\x1b\\[[0-9;]*m)*` — see hazard 2 at\n\t * the top of this file for what that breaks.\n\t */\n\tprivate sliceLeadingCursorCell(line: string): string {\n\t\tconst match = /^(?:\\x1b_pi:c\\x07)?(?:\\x1b\\[7m.\\x1b\\[(?:0|27)m)?/.exec(line);\n\t\treturn match?.[0] ?? \"\";\n\t}\n\n\tsetWorkingStatusIndicator(indicator: StatusIndicator | undefined): void {\n\t\tthis.workingStatusIndicator = indicator;\n\t}\n\n\tprotected override renderTopBorder(width: number, hiddenLineCount: number): string {\n\t\tif (!this.embedWorkingStatus || !this.workingStatusIndicator || width <= 0) {\n\t\t\treturn super.renderTopBorder(width, hiddenLineCount);\n\t\t}\n\n\t\tlet status = this.workingStatusIndicator.renderInBorder(Math.max(1, width - 5));\n\t\tlet statusWidth = visibleWidth(status);\n\t\tif (statusWidth === 0) return super.renderTopBorder(width, hiddenLineCount);\n\n\t\tconst overflowLabel = hiddenLineCount > 0 ? ` ↑ ${hiddenLineCount} more ` : undefined;\n\t\tconst overflowLabelWidth = overflowLabel ? visibleWidth(overflowLabel) : 0;\n\t\tconst overflowStart = Math.floor((width - overflowLabelWidth) / 2);\n\t\tconst canFitOverflow = () =>\n\t\t\toverflowLabel !== undefined && overflowLabelWidth + 2 <= width && overflowStart - (3 + statusWidth + 1) >= 1;\n\n\t\tif (overflowLabel && !canFitOverflow()) {\n\t\t\tstatus = this.workingStatusIndicator.renderSpinnerInBorder(width);\n\t\t\tstatusWidth = visibleWidth(status);\n\t\t}\n\n\t\tif (canFitOverflow()) {\n\t\t\tconst leftBlockWidth = 3 + statusWidth + 1;\n\t\t\treturn (\n\t\t\t\tthis.borderColor(\"── \") +\n\t\t\t\tstatus +\n\t\t\t\tthis.borderColor(\n\t\t\t\t\t` ${\"─\".repeat(overflowStart - leftBlockWidth)}${overflowLabel}${\"─\".repeat(width - overflowStart - overflowLabelWidth)}`,\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\tif (width >= statusWidth + 5) {\n\t\t\treturn this.borderColor(\"── \") + status + this.borderColor(` ${\"─\".repeat(width - statusWidth - 4)}`);\n\t\t}\n\n\t\tstatus = this.workingStatusIndicator.renderSpinnerInBorder(width);\n\t\tstatusWidth = visibleWidth(status);\n\t\tconst prefixWidth = Math.min(3, Math.max(0, width - statusWidth));\n\t\treturn (\n\t\t\tthis.borderColor(\"─\".repeat(prefixWidth)) +\n\t\t\tstatus +\n\t\t\tthis.borderColor(\"─\".repeat(Math.max(0, width - prefixWidth - statusWidth)))\n\t\t);\n\t}\n\n\t/**\n\t * Register a handler for an app action.\n\t */\n\tonAction(action: AppKeybinding, handler: () => void): void {\n\t\tthis.actionHandlers.set(action, handler);\n\t}\n\n\thandleInput(data: string): void {\n\t\t// Check extension-registered shortcuts first\n\t\tif (this.onExtensionShortcut?.(data)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Check for clipboard paste keybinding\n\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\tthis.onPasteImage?.();\n\t\t\treturn;\n\t\t}\n\n\t\t// Check app keybindings first\n\n\t\t// Escape/interrupt - only if autocomplete is NOT active\n\t\tif (this.keybindings.matches(data, \"app.interrupt\")) {\n\t\t\tif (!this.isShowingAutocomplete()) {\n\t\t\t\t// Use dynamic onEscape if set, otherwise registered handler\n\t\t\t\tconst handler = this.onEscape ?? this.actionHandlers.get(\"app.interrupt\");\n\t\t\t\tif (handler) {\n\t\t\t\t\thandler();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Let parent handle escape for autocomplete cancellation\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Exit (Ctrl+D) - only when editor is empty\n\t\tif (this.keybindings.matches(data, \"app.exit\")) {\n\t\t\tif (this.getText().length === 0) {\n\t\t\t\tconst handler = this.onCtrlD ?? this.actionHandlers.get(\"app.exit\");\n\t\t\t\tif (handler) handler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fall through to editor handling for delete-char-forward when not empty\n\t\t}\n\n\t\t// Explicit history bindings take precedence over app actions while the editor is focused.\n\t\t// This lets users bind Ctrl+P even though it cycles models by default.\n\t\tif (\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyPrevious\") ||\n\t\t\tthis.keybindings.matches(data, \"tui.editor.historyNext\")\n\t\t) {\n\t\t\tsuper.handleInput(data);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check all other app actions\n\t\tfor (const [action, handler] of this.actionHandlers) {\n\t\t\tif (action !== \"app.interrupt\" && action !== \"app.exit\" && this.keybindings.matches(data, action)) {\n\t\t\t\thandler();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Pass to parent for editor handling\n\t\tsuper.handleInput(data);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one line that stands for a multi-line error while it is collapsed, or
|
|
3
|
+
* undefined when the error is already one line and there is nothing to fold.
|
|
4
|
+
*
|
|
5
|
+
* A Python traceback leads with frames and ends with the raised error, so the
|
|
6
|
+
* last unindented line names it; anything else leads with its headline.
|
|
7
|
+
*/
|
|
8
|
+
export declare function summarizeError(text: string): string | undefined;
|
|
9
|
+
/** A folded error line: the summary followed by the expand hint. */
|
|
10
|
+
export declare function collapsedErrorLine(styledSummary: string): string;
|
|
11
|
+
//# sourceMappingURL=error-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-summary.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/error-summary.ts"],"names":[],"mappings":"AASA;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAS/D;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEhE","sourcesContent":["import { keyHint } from \"./keybinding-hints.ts\";\n\nfunction nonEmptyLines(text: string): string[] {\n\treturn text\n\t\t.replace(/\\r\\n?/g, \"\\n\")\n\t\t.split(\"\\n\")\n\t\t.filter((line) => line.trim() !== \"\");\n}\n\n/**\n * The one line that stands for a multi-line error while it is collapsed, or\n * undefined when the error is already one line and there is nothing to fold.\n *\n * A Python traceback leads with frames and ends with the raised error, so the\n * last unindented line names it; anything else leads with its headline.\n */\nexport function summarizeError(text: string): string | undefined {\n\tconst lines = nonEmptyLines(text);\n\tif (lines.length <= 1) return undefined;\n\tconst prefix = lines[0].startsWith(\"Error: Traceback \") ? \"Error: \" : \"\";\n\tif (lines[0].slice(prefix.length).startsWith(\"Traceback \")) {\n\t\tconst raised = lines.filter((line) => !/^\\s/.test(line)).at(-1);\n\t\tif (raised && raised !== lines[0]) return `${prefix}${raised.trim()}`;\n\t}\n\treturn lines[0].trim();\n}\n\n/** A folded error line: the summary followed by the expand hint. */\nexport function collapsedErrorLine(styledSummary: string): string {\n\treturn `${styledSummary} ${keyHint(\"app.tools.expand\", \"to expand\")}`;\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { keyHint } from "./keybinding-hints.js";
|
|
2
|
+
function nonEmptyLines(text) {
|
|
3
|
+
return text
|
|
4
|
+
.replace(/\r\n?/g, "\n")
|
|
5
|
+
.split("\n")
|
|
6
|
+
.filter((line) => line.trim() !== "");
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The one line that stands for a multi-line error while it is collapsed, or
|
|
10
|
+
* undefined when the error is already one line and there is nothing to fold.
|
|
11
|
+
*
|
|
12
|
+
* A Python traceback leads with frames and ends with the raised error, so the
|
|
13
|
+
* last unindented line names it; anything else leads with its headline.
|
|
14
|
+
*/
|
|
15
|
+
export function summarizeError(text) {
|
|
16
|
+
const lines = nonEmptyLines(text);
|
|
17
|
+
if (lines.length <= 1)
|
|
18
|
+
return undefined;
|
|
19
|
+
const prefix = lines[0].startsWith("Error: Traceback ") ? "Error: " : "";
|
|
20
|
+
if (lines[0].slice(prefix.length).startsWith("Traceback ")) {
|
|
21
|
+
const raised = lines.filter((line) => !/^\s/.test(line)).at(-1);
|
|
22
|
+
if (raised && raised !== lines[0])
|
|
23
|
+
return `${prefix}${raised.trim()}`;
|
|
24
|
+
}
|
|
25
|
+
return lines[0].trim();
|
|
26
|
+
}
|
|
27
|
+
/** A folded error line: the summary followed by the expand hint. */
|
|
28
|
+
export function collapsedErrorLine(styledSummary) {
|
|
29
|
+
return `${styledSummary} ${keyHint("app.tools.expand", "to expand")}`;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=error-summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-summary.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/error-summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD,SAAS,aAAa,CAAC,IAAY,EAAY;IAC9C,OAAO,IAAI;SACT,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC;SACvB,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAAA,CACvC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAsB;IAChE,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC5D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAChE,IAAI,MAAM,IAAI,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC;YAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;IACvE,CAAC;IACD,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAAA,CACvB;AAED,oEAAoE;AACpE,MAAM,UAAU,kBAAkB,CAAC,aAAqB,EAAU;IACjE,OAAO,GAAG,aAAa,IAAI,OAAO,CAAC,kBAAkB,EAAE,WAAW,CAAC,EAAE,CAAC;AAAA,CACtE","sourcesContent":["import { keyHint } from \"./keybinding-hints.ts\";\n\nfunction nonEmptyLines(text: string): string[] {\n\treturn text\n\t\t.replace(/\\r\\n?/g, \"\\n\")\n\t\t.split(\"\\n\")\n\t\t.filter((line) => line.trim() !== \"\");\n}\n\n/**\n * The one line that stands for a multi-line error while it is collapsed, or\n * undefined when the error is already one line and there is nothing to fold.\n *\n * A Python traceback leads with frames and ends with the raised error, so the\n * last unindented line names it; anything else leads with its headline.\n */\nexport function summarizeError(text: string): string | undefined {\n\tconst lines = nonEmptyLines(text);\n\tif (lines.length <= 1) return undefined;\n\tconst prefix = lines[0].startsWith(\"Error: Traceback \") ? \"Error: \" : \"\";\n\tif (lines[0].slice(prefix.length).startsWith(\"Traceback \")) {\n\t\tconst raised = lines.filter((line) => !/^\\s/.test(line)).at(-1);\n\t\tif (raised && raised !== lines[0]) return `${prefix}${raised.trim()}`;\n\t}\n\treturn lines[0].trim();\n}\n\n/** A folded error line: the summary followed by the expand hint. */\nexport function collapsedErrorLine(styledSummary: string): string {\n\treturn `${styledSummary} ${keyHint(\"app.tools.expand\", \"to expand\")}`;\n}\n"]}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Container, type EditorOptions, type Focusable, type TUI } from "@earendil-works/pi-tui";
|
|
6
6
|
import type { KeybindingsManager } from "../../../core/keybindings.ts";
|
|
7
|
+
export interface ExtensionEditorOptions extends EditorOptions {
|
|
8
|
+
description?: string;
|
|
9
|
+
}
|
|
7
10
|
export declare class ExtensionEditorComponent extends Container implements Focusable {
|
|
8
11
|
private editor;
|
|
9
12
|
private onSubmitCallback;
|
|
@@ -14,7 +17,7 @@ export declare class ExtensionEditorComponent extends Container implements Focus
|
|
|
14
17
|
private _focused;
|
|
15
18
|
get focused(): boolean;
|
|
16
19
|
set focused(value: boolean);
|
|
17
|
-
constructor(tui: TUI, keybindings: KeybindingsManager, title: string, prefill: string | undefined, onSubmit: (value: string) => void, onCancel: () => void, options?:
|
|
20
|
+
constructor(tui: TUI, keybindings: KeybindingsManager, title: string, prefill: string | undefined, onSubmit: (value: string) => void, onCancel: () => void, options?: ExtensionEditorOptions, externalEditorCommand?: string);
|
|
18
21
|
handleInput(keyData: string): void;
|
|
19
22
|
private handleOpenExternalEditor;
|
|
20
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/extension-editor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,SAAS,EAET,KAAK,aAAa,EAClB,KAAK,SAAS,EAId,KAAK,GAAG,EACR,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAMvE,qBAAa,wBAAyB,SAAQ,SAAU,YAAW,SAAS;IAC3E,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,qBAAqB,CAAS;IAEtC,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YACC,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,kBAAkB,EAC/B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,QAAQ,EAAE,MAAM,IAAI,EACpB,OAAO,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"extension-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/extension-editor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,SAAS,EAET,KAAK,aAAa,EAClB,KAAK,SAAS,EAId,KAAK,GAAG,EACR,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAMvE,MAAM,WAAW,sBAAuB,SAAQ,aAAa;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,wBAAyB,SAAQ,SAAU,YAAW,SAAS;IAC3E,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,qBAAqB,CAAS;IAEtC,OAAO,CAAC,QAAQ,CAAS;IACzB,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,YACC,GAAG,EAAE,GAAG,EACR,WAAW,EAAE,kBAAkB,EAC/B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EACjC,QAAQ,EAAE,MAAM,IAAI,EACpB,OAAO,CAAC,EAAE,sBAAsB,EAChC,qBAAqB,CAAC,EAAE,MAAM,EAqD9B;IAED,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAgBjC;YAEa,wBAAwB;CAgBtC","sourcesContent":["/**\n * Multi-line editor component for extensions.\n * Supports Ctrl+G for external editor.\n */\n\nimport {\n\tContainer,\n\tEditor,\n\ttype EditorOptions,\n\ttype Focusable,\n\tgetKeybindings,\n\tSpacer,\n\tText,\n\ttype TUI,\n} from \"@earendil-works/pi-tui\";\nimport type { KeybindingsManager } from \"../../../core/keybindings.ts\";\nimport { editInExternalEditor } from \"../external-editor.ts\";\nimport { getEditorTheme, theme } from \"../theme/theme.ts\";\nimport { DynamicBorder } from \"./dynamic-border.ts\";\nimport { hintRow } from \"./keybinding-hints.ts\";\n\nexport interface ExtensionEditorOptions extends EditorOptions {\n\tdescription?: string;\n}\n\nexport class ExtensionEditorComponent extends Container implements Focusable {\n\tprivate editor: Editor;\n\tprivate onSubmitCallback: (value: string) => void;\n\tprivate onCancelCallback: () => void;\n\tprivate tui: TUI;\n\tprivate keybindings: KeybindingsManager;\n\tprivate externalEditorCommand: string;\n\n\tprivate _focused = false;\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.editor.focused = value;\n\t}\n\n\tconstructor(\n\t\ttui: TUI,\n\t\tkeybindings: KeybindingsManager,\n\t\ttitle: string,\n\t\tprefill: string | undefined,\n\t\tonSubmit: (value: string) => void,\n\t\tonCancel: () => void,\n\t\toptions?: ExtensionEditorOptions,\n\t\texternalEditorCommand?: string,\n\t) {\n\t\tsuper();\n\n\t\tthis.tui = tui;\n\t\tthis.keybindings = keybindings;\n\t\tthis.externalEditorCommand =\n\t\t\texternalEditorCommand ||\n\t\t\tprocess.env.VISUAL ||\n\t\t\tprocess.env.EDITOR ||\n\t\t\t(process.platform === \"win32\" ? \"notepad\" : \"nano\");\n\t\tthis.onSubmitCallback = onSubmit;\n\t\tthis.onCancelCallback = onCancel;\n\t\tconst { description, ...editorOptions } = options ?? {};\n\n\t\t// Add top border\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add title\n\t\tthis.addChild(new Text(theme.fg(\"accent\", title), 0, 0));\n\t\tif (description) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(theme.fg(\"text\", description), 1, 0));\n\t\t}\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create editor\n\t\tthis.editor = new Editor(tui, getEditorTheme(), editorOptions);\n\t\tif (prefill) {\n\t\t\tthis.editor.setText(prefill);\n\t\t}\n\t\t// Wire up Enter to submit (Shift+Enter for newlines, like the main editor)\n\t\tthis.editor.onSubmit = (text: string) => {\n\t\t\tthis.onSubmitCallback(text);\n\t\t};\n\t\tthis.addChild(this.editor);\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add hint\n\t\tconst hint = hintRow([\n\t\t\t[\"tui.select.confirm\", \"submit\"],\n\t\t\t[\"tui.input.newLine\", \"newline\"],\n\t\t\t[\"tui.select.cancel\", \"cancel\"],\n\t\t\t[\"app.editor.external\", \"external editor\"],\n\t\t]);\n\t\tthis.addChild(new Text(hint, 0, 0));\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Add bottom border\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\t\t// Escape or Ctrl+C to cancel\n\t\tif (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tthis.onCancelCallback();\n\t\t\treturn;\n\t\t}\n\n\t\t// External editor (app keybinding)\n\t\tif (this.keybindings.matches(keyData, \"app.editor.external\")) {\n\t\t\tvoid this.handleOpenExternalEditor();\n\t\t\treturn;\n\t\t}\n\n\t\t// Forward to editor\n\t\tthis.editor.handleInput(keyData);\n\t}\n\n\tprivate async handleOpenExternalEditor(): Promise<void> {\n\t\tconst content = this.editor.getText();\n\t\tthis.tui.stop();\n\t\ttry {\n\t\t\tconst result = await editInExternalEditor({\n\t\t\t\tcommand: this.externalEditorCommand,\n\t\t\t\tcontent,\n\t\t\t});\n\t\t\tif (result.status === \"complete\") {\n\t\t\t\tthis.editor.setText(result.content);\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.tui.start();\n\t\t\tthis.tui.requestRender(true);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -33,14 +33,19 @@ export class ExtensionEditorComponent extends Container {
|
|
|
33
33
|
(process.platform === "win32" ? "notepad" : "nano");
|
|
34
34
|
this.onSubmitCallback = onSubmit;
|
|
35
35
|
this.onCancelCallback = onCancel;
|
|
36
|
+
const { description, ...editorOptions } = options ?? {};
|
|
36
37
|
// Add top border
|
|
37
38
|
this.addChild(new DynamicBorder());
|
|
38
39
|
this.addChild(new Spacer(1));
|
|
39
40
|
// Add title
|
|
40
41
|
this.addChild(new Text(theme.fg("accent", title), 0, 0));
|
|
42
|
+
if (description) {
|
|
43
|
+
this.addChild(new Spacer(1));
|
|
44
|
+
this.addChild(new Text(theme.fg("text", description), 1, 0));
|
|
45
|
+
}
|
|
41
46
|
this.addChild(new Spacer(1));
|
|
42
47
|
// Create editor
|
|
43
|
-
this.editor = new Editor(tui, getEditorTheme(),
|
|
48
|
+
this.editor = new Editor(tui, getEditorTheme(), editorOptions);
|
|
44
49
|
if (prefill) {
|
|
45
50
|
this.editor.setText(prefill);
|
|
46
51
|
}
|