apex-code 0.0.1-alpha.0 → 0.0.1-alpha.10
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 +471 -0
- package/README.md +74 -680
- package/dist/cli/args.d.ts +21 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +119 -15
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/auth-command.d.ts.map +1 -1
- package/dist/cli/auth-command.js +3 -3
- package/dist/cli/auth-command.js.map +1 -1
- package/dist/cli/concurrent-session.d.ts +3 -0
- package/dist/cli/concurrent-session.d.ts.map +1 -0
- package/dist/cli/concurrent-session.js +13 -0
- package/dist/cli/concurrent-session.js.map +1 -0
- package/dist/cli/cost-command.d.ts +19 -0
- package/dist/cli/cost-command.d.ts.map +1 -0
- package/dist/cli/cost-command.js +107 -0
- package/dist/cli/cost-command.js.map +1 -0
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +2 -1
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli/startup-ui.d.ts +1 -1
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +1 -2
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +99 -16
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +5 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +35 -20
- 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 +2 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +28 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +140 -4
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +121 -9
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +364 -123
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +4 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +17 -14
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +11 -10
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +3 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +4 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +41 -32
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/context/deferred-schemas.d.ts +92 -0
- package/dist/core/context/deferred-schemas.d.ts.map +1 -0
- package/dist/core/context/deferred-schemas.js +74 -0
- package/dist/core/context/deferred-schemas.js.map +1 -0
- package/dist/core/context/eviction.d.ts +47 -0
- package/dist/core/context/eviction.d.ts.map +1 -0
- package/dist/core/context/eviction.js +83 -0
- package/dist/core/context/eviction.js.map +1 -0
- package/dist/core/context/pipeline.d.ts +90 -0
- package/dist/core/context/pipeline.d.ts.map +1 -0
- package/dist/core/context/pipeline.js +109 -0
- package/dist/core/context/pipeline.js.map +1 -0
- package/dist/core/credential-failover.d.ts +30 -0
- package/dist/core/credential-failover.d.ts.map +1 -0
- package/dist/core/credential-failover.js +46 -0
- package/dist/core/credential-failover.js.map +1 -0
- package/dist/core/credential-pool.d.ts +60 -0
- package/dist/core/credential-pool.d.ts.map +1 -0
- package/dist/core/credential-pool.js +84 -0
- package/dist/core/credential-pool.js.map +1 -0
- package/dist/core/defaults.d.ts +1 -0
- package/dist/core/defaults.d.ts.map +1 -1
- package/dist/core/defaults.js +9 -0
- package/dist/core/defaults.js.map +1 -1
- package/dist/core/delegation/agents.d.ts +15 -0
- package/dist/core/delegation/agents.d.ts.map +1 -0
- package/dist/core/delegation/agents.js +55 -0
- package/dist/core/delegation/agents.js.map +1 -0
- package/dist/core/delegation/ceiling.d.ts +29 -0
- package/dist/core/delegation/ceiling.d.ts.map +1 -0
- package/dist/core/delegation/ceiling.js +34 -0
- package/dist/core/delegation/ceiling.js.map +1 -0
- package/dist/core/delegation/runtime.d.ts +94 -0
- package/dist/core/delegation/runtime.d.ts.map +1 -0
- package/dist/core/delegation/runtime.js +117 -0
- package/dist/core/delegation/runtime.js.map +1 -0
- package/dist/core/durable-state/daemon.d.ts +37 -0
- package/dist/core/durable-state/daemon.d.ts.map +1 -0
- package/dist/core/durable-state/daemon.js +62 -0
- package/dist/core/durable-state/daemon.js.map +1 -0
- package/dist/core/durable-state/provenance.d.ts +7 -0
- package/dist/core/durable-state/provenance.d.ts.map +1 -0
- package/dist/core/durable-state/provenance.js +24 -0
- package/dist/core/durable-state/provenance.js.map +1 -0
- package/dist/core/durable-state/sqlite.d.ts +84 -0
- package/dist/core/durable-state/sqlite.d.ts.map +1 -0
- package/dist/core/durable-state/sqlite.js +355 -0
- package/dist/core/durable-state/sqlite.js.map +1 -0
- package/dist/core/environment.d.ts +12 -0
- package/dist/core/environment.d.ts.map +1 -0
- package/dist/core/environment.js +69 -0
- package/dist/core/environment.js.map +1 -0
- package/dist/core/evidence-policy.d.ts +17 -0
- package/dist/core/evidence-policy.d.ts.map +1 -0
- package/dist/core/evidence-policy.js +25 -0
- package/dist/core/evidence-policy.js.map +1 -0
- package/dist/core/evidence.d.ts +16 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +51 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/experimental.d.ts +4 -0
- package/dist/core/experimental.d.ts.map +1 -1
- package/dist/core/experimental.js +6 -1
- package/dist/core/experimental.js.map +1 -1
- package/dist/core/extensions/index.d.ts +2 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +115 -48
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +44 -9
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +3 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts +1 -2
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +2 -17
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/http-idle-timeout.d.ts +12 -0
- package/dist/core/http-idle-timeout.d.ts.map +1 -0
- package/dist/core/http-idle-timeout.js +27 -0
- package/dist/core/http-idle-timeout.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts +38 -13
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +26 -5
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/lsp/client.d.ts +78 -0
- package/dist/core/lsp/client.d.ts.map +1 -0
- package/dist/core/lsp/client.js +494 -0
- package/dist/core/lsp/client.js.map +1 -0
- package/dist/core/lsp/diagnostics.d.ts +47 -0
- package/dist/core/lsp/diagnostics.d.ts.map +1 -0
- package/dist/core/lsp/diagnostics.js +224 -0
- package/dist/core/lsp/diagnostics.js.map +1 -0
- package/dist/core/lsp/pool.d.ts +48 -0
- package/dist/core/lsp/pool.d.ts.map +1 -0
- package/dist/core/lsp/pool.js +150 -0
- package/dist/core/lsp/pool.js.map +1 -0
- package/dist/core/lsp/registry.d.ts +49 -0
- package/dist/core/lsp/registry.d.ts.map +1 -0
- package/dist/core/lsp/registry.js +215 -0
- package/dist/core/lsp/registry.js.map +1 -0
- package/dist/core/lsp/status.d.ts +23 -0
- package/dist/core/lsp/status.d.ts.map +1 -0
- package/dist/core/lsp/status.js +35 -0
- package/dist/core/lsp/status.js.map +1 -0
- package/dist/core/lsp/sync.d.ts +13 -0
- package/dist/core/lsp/sync.d.ts.map +1 -0
- package/dist/core/lsp/sync.js +71 -0
- package/dist/core/lsp/sync.js.map +1 -0
- package/dist/core/model-config.d.ts +18 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +25 -6
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver.d.ts +21 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +73 -8
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +73 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +225 -8
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/models-store.d.ts.map +1 -1
- package/dist/core/models-store.js +2 -1
- package/dist/core/models-store.js.map +1 -1
- package/dist/core/observability/aggregate.d.ts +21 -0
- package/dist/core/observability/aggregate.d.ts.map +1 -0
- package/dist/core/observability/aggregate.js +36 -0
- package/dist/core/observability/aggregate.js.map +1 -0
- package/dist/core/observability/otlp.d.ts +66 -0
- package/dist/core/observability/otlp.d.ts.map +1 -0
- package/dist/core/observability/otlp.js +95 -0
- package/dist/core/observability/otlp.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +40 -16
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/permissions/gate.d.ts +42 -0
- package/dist/core/permissions/gate.d.ts.map +1 -0
- package/dist/core/permissions/gate.js +72 -0
- package/dist/core/permissions/gate.js.map +1 -0
- package/dist/core/permissions/modes.d.ts +18 -0
- package/dist/core/permissions/modes.d.ts.map +1 -0
- package/dist/core/permissions/modes.js +57 -0
- package/dist/core/permissions/modes.js.map +1 -0
- package/dist/core/permissions/responder.d.ts +30 -0
- package/dist/core/permissions/responder.d.ts.map +1 -0
- package/dist/core/permissions/responder.js +32 -0
- package/dist/core/permissions/responder.js.map +1 -0
- package/dist/core/permissions/rules.d.ts +34 -0
- package/dist/core/permissions/rules.d.ts.map +1 -0
- package/dist/core/permissions/rules.js +74 -0
- package/dist/core/permissions/rules.js.map +1 -0
- package/dist/core/permissions/startup.d.ts +45 -0
- package/dist/core/permissions/startup.d.ts.map +1 -0
- package/dist/core/permissions/startup.js +63 -0
- package/dist/core/permissions/startup.js.map +1 -0
- package/dist/core/permissions/store.d.ts +114 -0
- package/dist/core/permissions/store.d.ts.map +1 -0
- package/dist/core/permissions/store.js +229 -0
- package/dist/core/permissions/store.js.map +1 -0
- package/dist/core/pi-manifest.d.ts.map +1 -1
- package/dist/core/pi-manifest.js +2 -1
- package/dist/core/pi-manifest.js.map +1 -1
- package/dist/core/project-trust.d.ts.map +1 -1
- package/dist/core/project-trust.js +1 -1
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/provider-attribution.d.ts.map +1 -1
- package/dist/core/provider-attribution.js +6 -7
- package/dist/core/provider-attribution.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +2 -2
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +22 -17
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +3 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sandbox/bwrap-arguments.d.ts +52 -0
- package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
- package/dist/core/sandbox/bwrap-arguments.js +86 -0
- package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
- package/dist/core/sandbox/child-entry.d.ts +2 -0
- package/dist/core/sandbox/child-entry.d.ts.map +1 -0
- package/dist/core/sandbox/child-entry.js +22 -0
- package/dist/core/sandbox/child-entry.js.map +1 -0
- package/dist/core/sandbox/cli-launch.d.ts +88 -0
- package/dist/core/sandbox/cli-launch.d.ts.map +1 -0
- package/dist/core/sandbox/cli-launch.js +272 -0
- package/dist/core/sandbox/cli-launch.js.map +1 -0
- package/dist/core/sandbox/cli-supervisor.d.ts +30 -0
- package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -0
- package/dist/core/sandbox/cli-supervisor.js +123 -0
- package/dist/core/sandbox/cli-supervisor.js.map +1 -0
- package/dist/core/sandbox/default-hosts.d.ts +28 -0
- package/dist/core/sandbox/default-hosts.d.ts.map +1 -0
- package/dist/core/sandbox/default-hosts.js +69 -0
- package/dist/core/sandbox/default-hosts.js.map +1 -0
- package/dist/core/sandbox/full-access.d.ts +28 -0
- package/dist/core/sandbox/full-access.d.ts.map +1 -0
- package/dist/core/sandbox/full-access.js +63 -0
- package/dist/core/sandbox/full-access.js.map +1 -0
- package/dist/core/sandbox/git-identity.d.ts +57 -0
- package/dist/core/sandbox/git-identity.d.ts.map +1 -0
- package/dist/core/sandbox/git-identity.js +78 -0
- package/dist/core/sandbox/git-identity.js.map +1 -0
- package/dist/core/sandbox/host-approval.d.ts +42 -0
- package/dist/core/sandbox/host-approval.d.ts.map +1 -0
- package/dist/core/sandbox/host-approval.js +99 -0
- package/dist/core/sandbox/host-approval.js.map +1 -0
- package/dist/core/sandbox/linux-backend.d.ts +46 -0
- package/dist/core/sandbox/linux-backend.d.ts.map +1 -0
- package/dist/core/sandbox/linux-backend.js +304 -0
- package/dist/core/sandbox/linux-backend.js.map +1 -0
- package/dist/core/sandbox/macos-backend.d.ts +28 -0
- package/dist/core/sandbox/macos-backend.d.ts.map +1 -0
- package/dist/core/sandbox/macos-backend.js +313 -0
- package/dist/core/sandbox/macos-backend.js.map +1 -0
- package/dist/core/sandbox/network-proxy.d.ts +38 -0
- package/dist/core/sandbox/network-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/network-proxy.js +153 -0
- package/dist/core/sandbox/network-proxy.js.map +1 -0
- package/dist/core/sandbox/network-refusal.d.ts +24 -0
- package/dist/core/sandbox/network-refusal.d.ts.map +1 -0
- package/dist/core/sandbox/network-refusal.js +72 -0
- package/dist/core/sandbox/network-refusal.js.map +1 -0
- package/dist/core/sandbox/policy.d.ts +40 -0
- package/dist/core/sandbox/policy.d.ts.map +1 -0
- package/dist/core/sandbox/policy.js +55 -0
- package/dist/core/sandbox/policy.js.map +1 -0
- package/dist/core/sandbox/profiles.d.ts +25 -0
- package/dist/core/sandbox/profiles.d.ts.map +1 -0
- package/dist/core/sandbox/profiles.js +23 -0
- package/dist/core/sandbox/profiles.js.map +1 -0
- package/dist/core/sandbox/rpc/command-client.d.ts +28 -0
- package/dist/core/sandbox/rpc/command-client.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/command-client.js +86 -0
- package/dist/core/sandbox/rpc/command-client.js.map +1 -0
- package/dist/core/sandbox/rpc/command-proxy.d.ts +48 -0
- package/dist/core/sandbox/rpc/command-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/command-proxy.js +120 -0
- package/dist/core/sandbox/rpc/command-proxy.js.map +1 -0
- package/dist/core/sandbox/rpc/credential-client.d.ts +20 -0
- package/dist/core/sandbox/rpc/credential-client.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/credential-client.js +170 -0
- package/dist/core/sandbox/rpc/credential-client.js.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts +44 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.js +287 -0
- package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -0
- package/dist/core/sandbox/rpc/framing.d.ts +35 -0
- package/dist/core/sandbox/rpc/framing.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/framing.js +114 -0
- package/dist/core/sandbox/rpc/framing.js.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-helper.d.ts +40 -0
- package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-helper.js +152 -0
- package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +48 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.js +142 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.js.map +1 -0
- package/dist/core/sandbox/supervisor-temp.d.ts +18 -0
- package/dist/core/sandbox/supervisor-temp.d.ts.map +1 -0
- package/dist/core/sandbox/supervisor-temp.js +21 -0
- package/dist/core/sandbox/supervisor-temp.js.map +1 -0
- package/dist/core/sandbox/supervisor.d.ts +51 -0
- package/dist/core/sandbox/supervisor.d.ts.map +1 -0
- package/dist/core/sandbox/supervisor.js +18 -0
- package/dist/core/sandbox/supervisor.js.map +1 -0
- package/dist/core/sandbox/terminal-handoff.d.ts +45 -0
- package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
- package/dist/core/sandbox/terminal-handoff.js +176 -0
- package/dist/core/sandbox/terminal-handoff.js.map +1 -0
- package/dist/core/sandbox/terminal-size.d.ts +40 -0
- package/dist/core/sandbox/terminal-size.d.ts.map +1 -0
- package/dist/core/sandbox/terminal-size.js +105 -0
- package/dist/core/sandbox/terminal-size.js.map +1 -0
- package/dist/core/sandbox/violations.d.ts +23 -0
- package/dist/core/sandbox/violations.d.ts.map +1 -0
- package/dist/core/sandbox/violations.js +30 -0
- package/dist/core/sandbox/violations.js.map +1 -0
- package/dist/core/sdk.d.ts +32 -41
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +161 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-export.d.ts +4 -0
- package/dist/core/session-export.d.ts.map +1 -0
- package/dist/core/session-export.js +32 -0
- package/dist/core/session-export.js.map +1 -0
- package/dist/core/session-lease.d.ts +31 -0
- package/dist/core/session-lease.d.ts.map +1 -0
- package/dist/core/session-lease.js +138 -0
- package/dist/core/session-lease.js.map +1 -0
- package/dist/core/session-manager.d.ts +46 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +62 -4
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-share.d.ts +31 -0
- package/dist/core/session-share.d.ts.map +1 -0
- package/dist/core/session-share.js +45 -0
- package/dist/core/session-share.js.map +1 -0
- package/dist/core/settings-diagnostics.d.ts +9 -0
- package/dist/core/settings-diagnostics.d.ts.map +1 -0
- package/dist/core/settings-diagnostics.js +21 -0
- package/dist/core/settings-diagnostics.js.map +1 -0
- package/dist/core/settings-manager.d.ts +92 -9
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +138 -36
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +19 -7
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +128 -52
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +5 -2
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts +2 -2
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +59 -68
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/timings.d.ts +1 -1
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +3 -2
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/ask-user.d.ts +25 -0
- package/dist/core/tools/ask-user.d.ts.map +1 -0
- package/dist/core/tools/ask-user.js +49 -0
- package/dist/core/tools/ask-user.js.map +1 -0
- package/dist/core/tools/bash-command-segments.d.ts +22 -0
- package/dist/core/tools/bash-command-segments.d.ts.map +1 -0
- package/dist/core/tools/bash-command-segments.js +128 -0
- package/dist/core/tools/bash-command-segments.js.map +1 -0
- package/dist/core/tools/bash.d.ts +40 -4
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +183 -32
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/contract.d.ts +183 -0
- package/dist/core/tools/contract.d.ts.map +1 -0
- package/dist/core/tools/contract.js +48 -0
- package/dist/core/tools/contract.js.map +1 -0
- package/dist/core/tools/delegate.d.ts +38 -0
- package/dist/core/tools/delegate.d.ts.map +1 -0
- package/dist/core/tools/delegate.js +90 -0
- package/dist/core/tools/delegate.js.map +1 -0
- package/dist/core/tools/diagnostics.d.ts +39 -0
- package/dist/core/tools/diagnostics.d.ts.map +1 -0
- package/dist/core/tools/diagnostics.js +62 -0
- package/dist/core/tools/diagnostics.js.map +1 -0
- package/dist/core/tools/edit-diff.d.ts +0 -5
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +2 -5
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +12 -2
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +61 -10
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts +2 -2
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +13 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +4 -2
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +47 -10
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +51 -6
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +145 -2
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +4 -2
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +17 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/lsp.d.ts +62 -0
- package/dist/core/tools/lsp.d.ts.map +1 -0
- package/dist/core/tools/lsp.js +216 -0
- package/dist/core/tools/lsp.js.map +1 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +1 -1
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/path-permission.d.ts +18 -0
- package/dist/core/tools/path-permission.d.ts.map +1 -0
- package/dist/core/tools/path-permission.js +28 -0
- package/dist/core/tools/path-permission.js.map +1 -0
- package/dist/core/tools/plan-present.d.ts +27 -0
- package/dist/core/tools/plan-present.d.ts.map +1 -0
- package/dist/core/tools/plan-present.js +55 -0
- package/dist/core/tools/plan-present.js.map +1 -0
- package/dist/core/tools/powershell.d.ts +16 -0
- package/dist/core/tools/powershell.d.ts.map +1 -0
- package/dist/core/tools/powershell.js +39 -0
- package/dist/core/tools/powershell.js.map +1 -0
- package/dist/core/tools/read.d.ts +2 -2
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +14 -0
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/skill-search.d.ts +32 -0
- package/dist/core/tools/skill-search.d.ts.map +1 -0
- package/dist/core/tools/skill-search.js +56 -0
- package/dist/core/tools/skill-search.js.map +1 -0
- package/dist/core/tools/test.d.ts +34 -0
- package/dist/core/tools/test.d.ts.map +1 -0
- package/dist/core/tools/test.js +75 -0
- package/dist/core/tools/test.js.map +1 -0
- package/dist/core/tools/todo-write.d.ts +31 -0
- package/dist/core/tools/todo-write.d.ts.map +1 -0
- package/dist/core/tools/todo-write.js +51 -0
- package/dist/core/tools/todo-write.js.map +1 -0
- package/dist/core/tools/tool-definition-wrapper.d.ts +13 -2
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +11 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/tools/tool-schema.d.ts +22 -0
- package/dist/core/tools/tool-schema.d.ts.map +1 -0
- package/dist/core/tools/tool-schema.js +45 -0
- package/dist/core/tools/tool-schema.js.map +1 -0
- package/dist/core/tools/web-fetch.d.ts +26 -0
- package/dist/core/tools/web-fetch.d.ts.map +1 -0
- package/dist/core/tools/web-fetch.js +62 -0
- package/dist/core/tools/web-fetch.js.map +1 -0
- package/dist/core/tools/web-search-exa.d.ts +33 -0
- package/dist/core/tools/web-search-exa.d.ts.map +1 -0
- package/dist/core/tools/web-search-exa.js +128 -0
- package/dist/core/tools/web-search-exa.js.map +1 -0
- package/dist/core/tools/web-search.d.ts +26 -0
- package/dist/core/tools/web-search.d.ts.map +1 -0
- package/dist/core/tools/web-search.js +53 -0
- package/dist/core/tools/web-search.js.map +1 -0
- package/dist/core/tools/write.d.ts +16 -2
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +48 -2
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +2 -1
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/core/usage-performance-store.d.ts +56 -0
- package/dist/core/usage-performance-store.d.ts.map +1 -0
- package/dist/core/usage-performance-store.js +85 -0
- package/dist/core/usage-performance-store.js.map +1 -0
- package/dist/core/web-search-provider.d.ts +64 -0
- package/dist/core/web-search-provider.d.ts.map +1 -0
- package/dist/core/web-search-provider.js +149 -0
- package/dist/core/web-search-provider.js.map +1 -0
- package/dist/extensions/llama/index.d.ts.map +1 -1
- package/dist/extensions/llama/index.js +2 -0
- package/dist/extensions/llama/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +6 -2
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +5 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +87 -22
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +6 -5
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +9 -6
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +72 -1
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +338 -1
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts +7 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +11 -2
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +4 -0
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +24 -6
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +1 -4
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +17 -41
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/modes/interactive/components/first-use-hints.d.ts +8 -0
- package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -0
- package/dist/modes/interactive/components/first-use-hints.js +22 -0
- package/dist/modes/interactive/components/first-use-hints.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts +23 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +182 -21
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +2 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +2 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +31 -9
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +236 -96
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +25 -5
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +226 -58
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
- package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
- package/dist/modes/interactive/components/settings-submenu.js +165 -0
- package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
- package/dist/modes/interactive/components/splash-header.d.ts +66 -0
- package/dist/modes/interactive/components/splash-header.d.ts.map +1 -0
- package/dist/modes/interactive/components/splash-header.js +180 -0
- package/dist/modes/interactive/components/splash-header.js.map +1 -0
- package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +74 -17
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +16 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +113 -35
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-panel.d.ts +30 -0
- package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-panel.js +130 -0
- package/dist/modes/interactive/components/tool-panel.js.map +1 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +60 -10
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +61 -5
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +642 -203
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
- package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
- package/dist/modes/interactive/model-catalog-refresh.js +34 -0
- package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
- package/dist/modes/interactive/theme/apex.json +93 -0
- package/dist/modes/interactive/theme/dark.json +2 -0
- package/dist/modes/interactive/theme/light.json +3 -1
- package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +25 -11
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +11 -3
- package/dist/modes/interactive/theme/theme.d.ts +14 -3
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +65 -10
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +18 -12
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +22 -5
- package/dist/modes/json-event.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts +1 -0
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +211 -14
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/rpc-entry.d.ts.map +1 -1
- package/dist/rpc-entry.js +2 -1
- package/dist/rpc-entry.js.map +1 -1
- package/dist/server/create-harness.d.ts +1 -1
- package/dist/server/create-harness.d.ts.map +1 -1
- package/dist/server/create-harness.js +8 -5
- package/dist/server/create-harness.js.map +1 -1
- package/dist/testing/replay/metrics.d.ts +24 -0
- package/dist/testing/replay/metrics.d.ts.map +1 -0
- package/dist/testing/replay/metrics.js +35 -0
- package/dist/testing/replay/metrics.js.map +1 -0
- package/dist/testing/replay/recorded-provider.d.ts +10 -0
- package/dist/testing/replay/recorded-provider.d.ts.map +1 -0
- package/dist/testing/replay/recorded-provider.js +108 -0
- package/dist/testing/replay/recorded-provider.js.map +1 -0
- package/dist/testing/replay/runner.d.ts +56 -0
- package/dist/testing/replay/runner.d.ts.map +1 -0
- package/dist/testing/replay/runner.js +406 -0
- package/dist/testing/replay/runner.js.map +1 -0
- package/dist/themes/apex-logo.d.ts +41 -0
- package/dist/themes/apex-logo.d.ts.map +1 -0
- package/dist/themes/apex-logo.js +35 -0
- package/dist/themes/apex-logo.js.map +1 -0
- package/dist/utils/apex-code-user-agent.d.ts +2 -0
- package/dist/utils/apex-code-user-agent.d.ts.map +1 -0
- package/dist/utils/apex-code-user-agent.js +5 -0
- package/dist/utils/apex-code-user-agent.js.map +1 -0
- package/dist/utils/changelog.d.ts +7 -2
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js +82 -13
- package/dist/utils/changelog.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +2 -1
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/management-http.d.ts +5 -3
- package/dist/utils/management-http.d.ts.map +1 -1
- package/dist/utils/management-http.js +14 -10
- package/dist/utils/management-http.js.map +1 -1
- package/dist/utils/shell.d.ts +3 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +17 -5
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts +1 -0
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +60 -1
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/dist/utils/text.d.ts +8 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +9 -0
- package/dist/utils/text.js.map +1 -0
- package/dist/utils/tools-manager.d.ts +70 -2
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +280 -124
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/version-check.d.ts +7 -5
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +25 -14
- package/dist/utils/version-check.js.map +1 -1
- package/dist/utils/windows-self-update.d.ts.map +1 -1
- package/dist/utils/windows-self-update.js +1 -1
- package/dist/utils/windows-self-update.js.map +1 -1
- package/docs/compaction.md +39 -24
- package/docs/containerization.md +24 -21
- package/docs/custom-provider.md +15 -12
- package/docs/development.md +11 -11
- package/docs/environment-variables.md +37 -77
- package/docs/extensions.md +68 -54
- package/docs/index.md +12 -24
- package/docs/json.md +18 -11
- package/docs/keybindings.md +18 -12
- package/docs/llama-cpp.md +9 -7
- package/docs/models.md +17 -11
- package/docs/packages.md +47 -43
- package/docs/prompt-templates.md +3 -3
- package/docs/providers.md +10 -10
- package/docs/quickstart.md +49 -32
- package/docs/rpc.md +35 -18
- package/docs/sdk.md +20 -6
- package/docs/security.md +18 -18
- package/docs/session-format.md +9 -9
- package/docs/sessions.md +13 -13
- package/docs/settings.md +83 -16
- package/docs/shell-aliases.md +1 -1
- package/docs/skills.md +66 -15
- package/docs/terminal-setup.md +44 -19
- package/docs/termux.md +4 -4
- package/docs/themes.md +32 -11
- package/docs/tmux.md +4 -4
- package/docs/tui.md +5 -5
- package/docs/usage.md +61 -53
- package/docs/windows.md +24 -2
- package/examples/extensions/border-status-editor.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +2 -2
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/git-checkpoint.ts +2 -2
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/notify.ts +3 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +3 -1
- package/examples/extensions/subagent/agents.ts +40 -9
- package/examples/extensions/subagent/index.ts +27 -4
- package/examples/extensions/titlebar-spinner.ts +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/rpc-extension-ui.ts +1 -1
- package/examples/sdk/06-extensions.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/npm-shrinkwrap.json +280 -445
- package/package.json +9 -12
- package/dist/core/telemetry.d.ts +0 -3
- package/dist/core/telemetry.d.ts.map +0 -1
- package/dist/core/telemetry.js +0 -9
- package/dist/core/telemetry.js.map +0 -1
- package/dist/utils/pi-user-agent.d.ts +0 -2
- package/dist/utils/pi-user-agent.d.ts.map +0 -1
- package/dist/utils/pi-user-agent.js +0 -5
- package/dist/utils/pi-user-agent.js.map +0 -1
|
@@ -1,17 +1,88 @@
|
|
|
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
|
+
export interface CustomEditorOptions extends EditorOptions {
|
|
4
|
+
/** Marker rendered at the start of the first input line, e.g. `"> "`. */
|
|
5
|
+
promptPrefix?: string;
|
|
6
|
+
/** Styles the prompt prefix. Defaults to leaving it unstyled. */
|
|
7
|
+
promptColor?: (text: string) => string;
|
|
8
|
+
/** Shown in place of the empty input line. */
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
/** Styles the placeholder. Defaults to leaving it unstyled. */
|
|
11
|
+
placeholderColor?: (text: string) => string;
|
|
12
|
+
/** Styles a leading `/command` token. Defaults to leaving it unstyled. */
|
|
13
|
+
commandColor?: (text: string) => string;
|
|
14
|
+
/** Applies the filled prompt surface around the editor block. */
|
|
15
|
+
surfaceColor?: (text: string) => string;
|
|
16
|
+
/** Rule drawn above the autocomplete rows. Omit for no rule. */
|
|
17
|
+
autocompleteRule?: (width: number) => string;
|
|
18
|
+
/** Keybinding footer drawn under the autocomplete rows. Omit for no footer. */
|
|
19
|
+
autocompleteFooter?: () => string;
|
|
20
|
+
}
|
|
3
21
|
/**
|
|
4
22
|
* Custom editor that handles app-level keybindings for coding-agent.
|
|
23
|
+
*
|
|
24
|
+
* The prompt prefix and placeholder are layered on in `render` rather than
|
|
25
|
+
* inside the base Editor, because `packages/tui` is frozen under ADR 0001 and
|
|
26
|
+
* its `EditorOptions` has no hook for either.
|
|
5
27
|
*/
|
|
6
28
|
export declare class CustomEditor extends Editor {
|
|
7
29
|
private keybindings;
|
|
30
|
+
private readonly promptPrefix;
|
|
31
|
+
private readonly promptColor;
|
|
32
|
+
private readonly placeholderColor;
|
|
33
|
+
private readonly commandColor;
|
|
34
|
+
private readonly surfaceColor;
|
|
35
|
+
private readonly placeholder;
|
|
36
|
+
private readonly autocompleteRule;
|
|
37
|
+
private readonly autocompleteFooter;
|
|
38
|
+
private modeLabel;
|
|
8
39
|
actionHandlers: Map<AppKeybinding, () => void>;
|
|
9
40
|
onEscape?: () => void;
|
|
10
41
|
onCtrlD?: () => void;
|
|
11
42
|
onPasteImage?: () => void;
|
|
12
43
|
/** Handler for extension-registered shortcuts. Returns true if handled. */
|
|
13
44
|
onExtensionShortcut?: (data: string) => boolean;
|
|
14
|
-
constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?:
|
|
45
|
+
constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, options?: CustomEditorOptions);
|
|
46
|
+
render(width: number): string[];
|
|
47
|
+
private renderEditor;
|
|
48
|
+
/**
|
|
49
|
+
* Brackets the autocomplete rows with a rule and a keybinding footer.
|
|
50
|
+
*
|
|
51
|
+
* The rows arrive after the editor's second border, so the same border count
|
|
52
|
+
* that separates the box from its dropdown elsewhere in this file finds them
|
|
53
|
+
* here. Nothing is added when the dropdown is closed.
|
|
54
|
+
*/
|
|
55
|
+
private withAutocompleteChrome;
|
|
56
|
+
private withSurface;
|
|
57
|
+
private editorBlockLineCount;
|
|
58
|
+
private surfaceLine;
|
|
59
|
+
setModeLabel(label: string | undefined): void;
|
|
60
|
+
private isBorderLine;
|
|
61
|
+
/**
|
|
62
|
+
* Tint a leading `/command` on the first input line.
|
|
63
|
+
*
|
|
64
|
+
* The base Editor emits input text unstyled, so the only styling already
|
|
65
|
+
* present on the line is the cursor cell — which `colorVisibleRange` steps
|
|
66
|
+
* over. Only the command token is tinted; arguments after it stay plain, so
|
|
67
|
+
* the highlight marks what is being invoked rather than the whole line.
|
|
68
|
+
*/
|
|
69
|
+
private withCommandColor;
|
|
70
|
+
/**
|
|
71
|
+
* Replace the sole content line with the placeholder while the input is
|
|
72
|
+
* empty. Index 1 is always the first content line, and an empty editor has
|
|
73
|
+
* exactly one, so no line classification is needed here.
|
|
74
|
+
*/
|
|
75
|
+
private withPlaceholder;
|
|
76
|
+
/**
|
|
77
|
+
* The base Editor renders an empty line as the hardware-cursor marker
|
|
78
|
+
* followed by a reverse-video cell: `CURSOR_MARKER ESC[7m <space> ESC[0m`.
|
|
79
|
+
* Both parts are kept verbatim.
|
|
80
|
+
*
|
|
81
|
+
* The `ESC[7m … ESC[0m` run is matched explicitly. Do not "simplify" this to
|
|
82
|
+
* a general leading-SGR match such as `(?:\x1b\[[0-9;]*m)*` — see hazard 2 at
|
|
83
|
+
* the top of this file for what that breaks.
|
|
84
|
+
*/
|
|
85
|
+
private sliceLeadingCursorCell;
|
|
15
86
|
/**
|
|
16
87
|
* Register a handler for an app action.
|
|
17
88
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-editor.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/custom-editor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,GAAG,EAAE,MAAM,wBAAwB,CAAC;AAChG,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEtF;;GAEG;AACH,qBAAa,YAAa,SAAQ,MAAM;IACvC,OAAO,CAAC,WAAW,CAAqB;IACjC,cAAc,EAAE,GAAG,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,CAAa;IAG3D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,2EAA2E;IACpE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAEvD,YAAY,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,aAAa,EAGjG;IAED;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAEzD;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA2D9B;CACD","sourcesContent":["import { Editor, type EditorOptions, type EditorTheme, type TUI } from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.ts\";\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\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?: EditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\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":"AAAA,OAAO,EAEN,MAAM,EACN,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,GAAG,EAGR,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGtF,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACzD,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACvC,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,0EAA0E;IAC1E,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,iEAAiE;IACjE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,+EAA+E;IAC/E,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;CAClC;AAwHD;;;;;;GAMG;AACH,qBAAa,YAAa,SAAQ,MAAM;IACvC,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IACvD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA2B;IAC5D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyC;IACtE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyC;IACtE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA0C;IAC3E,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA6B;IAChE,OAAO,CAAC,SAAS,CAAM;IAChB,cAAc,EAAE,GAAG,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,CAAa;IAG3D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,2EAA2E;IACpE,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAEvD,YAAY,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,mBAAmB,EAWvG;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvC;IAED,OAAO,CAAC,YAAY;IAqCpB;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;IAa9B,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,WAAW;IAuCnB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAG5C;IAED,OAAO,CAAC,YAAY;IAKpB;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB;IAyBxB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IA2BvB;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAK9B;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAEzD;IAED,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CA2D9B;CACD","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\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/** 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 promptPrefix: string;\n\tprivate readonly promptColor: (text: string) => string;\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 modeLabel = \"\";\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\tthis.promptPrefix = options?.promptPrefix ?? \"\";\n\t\tthis.promptColor = options?.promptColor ?? ((text) => text);\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 promptPrefix = this.modeLabel ? `[${this.modeLabel}] ${this.promptPrefix}` : this.promptPrefix;\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 = this.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\tsetModeLabel(label: string | undefined): void {\n\t\tthis.modeLabel = label ?? \"\";\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,9 +1,132 @@
|
|
|
1
|
-
import { Editor } from "@earendil-works/pi-tui";
|
|
1
|
+
import { CURSOR_MARKER, Editor, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
|
|
2
|
+
import { stripAnsi } from "../../../utils/ansi.js";
|
|
3
|
+
/** Box-drawing horizontal rule: the character the base Editor draws borders from. */
|
|
4
|
+
const BORDER_CHAR = "─";
|
|
5
|
+
/**
|
|
6
|
+
* The two shapes the base Editor draws a border in: a plain full-width rule, and
|
|
7
|
+
* the scroll indicator (`─── ↑ 3 more ────`).
|
|
8
|
+
*
|
|
9
|
+
* Both are anchored deliberately. A prefix test would count any content line that
|
|
10
|
+
* merely *starts* with the rule character — a pasted divider, `─notes` — as a
|
|
11
|
+
* border, which is worse than it sounds: {@link CustomEditor.render} tells the
|
|
12
|
+
* content region from the autocomplete region by counting borders, so one
|
|
13
|
+
* miscount shifts every later line into the wrong region.
|
|
14
|
+
*/
|
|
15
|
+
const BORDER_RULE = /^─+$/;
|
|
16
|
+
const BORDER_SCROLL = /^─+ [↑↓] \d+ more/;
|
|
17
|
+
/**
|
|
18
|
+
* A leading `/command` token: optional indent, a slash, then non-space.
|
|
19
|
+
* Matched against the line's *visible* text, not the rendered string.
|
|
20
|
+
*/
|
|
21
|
+
const COMMAND_TOKEN = /^(\s*)(\/\S+)/;
|
|
22
|
+
/**
|
|
23
|
+
* One ANSI control sequence: a CSI escape (`ESC [ … letter`) or the APC
|
|
24
|
+
* hardware-cursor marker (`ESC _ … BEL`). Sticky, for position-by-position
|
|
25
|
+
* scanning in {@link colorVisibleRange}.
|
|
26
|
+
*/
|
|
27
|
+
const CONTROL_SEQUENCE = /\x1b\[[0-9;]*[A-Za-z]|\x1b_[^\x07]*\x07/y;
|
|
28
|
+
/** The reverse-video cursor cell emitted by the inherited editor. */
|
|
29
|
+
const INVERTED_CURSOR_CELL = /\x1b\[7m[\s\S]\x1b\[(?:0|27)m/y;
|
|
30
|
+
const SURFACE_PADDING = 2;
|
|
31
|
+
/**
|
|
32
|
+
* ============================ ANSI measuring hazards ============================
|
|
33
|
+
*
|
|
34
|
+
* Two traps live in the rendered strings this file post-processes. Both produced
|
|
35
|
+
* real bugs; both are cheap to re-introduce. Read this before touching the width
|
|
36
|
+
* maths or any leading-sequence match.
|
|
37
|
+
*
|
|
38
|
+
* 1. DO NOT COMPOSE `stripAnsi` WITH `visibleWidth`.
|
|
39
|
+
*
|
|
40
|
+
* `stripAnsi` removes SGR colour codes but leaves the APC hardware-cursor
|
|
41
|
+
* marker (`ESC _pi:c BEL`, see CURSOR_MARKER in packages/tui) fully intact —
|
|
42
|
+
* all seven bytes of it. `visibleWidth` already handles that marker correctly
|
|
43
|
+
* and scores it zero.
|
|
44
|
+
*
|
|
45
|
+
* So `visibleWidth(stripAnsi(line))` counts seven phantom columns on every
|
|
46
|
+
* line carrying a cursor, which silently widened each placeholder line past
|
|
47
|
+
* the terminal width. Measure with `visibleWidth` alone. Use `stripAnsi` only
|
|
48
|
+
* to read text, never to measure it.
|
|
49
|
+
*
|
|
50
|
+
* 2. DO NOT MATCH THE LEADING SEQUENCE RUN GENERICALLY.
|
|
51
|
+
*
|
|
52
|
+
* The base Editor renders an empty line as:
|
|
53
|
+
* CURSOR_MARKER ESC[7m <space> ESC[0m <padding>
|
|
54
|
+
* The `ESC[7m` turns on reverse video for exactly the one-cell cursor block,
|
|
55
|
+
* and `ESC[0m` turns it back off.
|
|
56
|
+
*
|
|
57
|
+
* A permissive prefix match such as `(?:\x1b\[[0-9;]*m)*` consumes that
|
|
58
|
+
* `ESC[7m` as though it were ordinary leading styling. Whatever is appended
|
|
59
|
+
* next then inherits reverse video, and because the matching `ESC[0m` was
|
|
60
|
+
* left behind in the discarded remainder, the inversion runs on through the
|
|
61
|
+
* placeholder and the rest of the frame. Match the reverse-video run
|
|
62
|
+
* explicitly instead — see {@link CustomEditor.sliceLeadingCursorCell}.
|
|
63
|
+
*
|
|
64
|
+
* ==============================================================================
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* Wrap the visible characters in `[start, end)` with `colorFn`, leaving every
|
|
68
|
+
* control sequence in place.
|
|
69
|
+
*
|
|
70
|
+
* The range is in visible columns, so it is unaffected by however many escape
|
|
71
|
+
* sequences are interleaved. That matters because the cursor cell can land in
|
|
72
|
+
* the middle of the token being coloured: with the caret at column 3 of
|
|
73
|
+
* `/model`, the line reads `/mo` + CURSOR_MARKER + `ESC[7m` + `d` + `ESC[0m` +
|
|
74
|
+
* `el`. Colour is therefore re-opened after every control sequence rather than
|
|
75
|
+
* once around the whole token, since the cursor cell's `ESC[0m` would otherwise
|
|
76
|
+
* cancel it partway through.
|
|
77
|
+
*/
|
|
78
|
+
function colorVisibleRange(line, start, end, colorFn) {
|
|
79
|
+
let out = "";
|
|
80
|
+
let buffer = "";
|
|
81
|
+
let column = 0;
|
|
82
|
+
let index = 0;
|
|
83
|
+
const flush = () => {
|
|
84
|
+
if (buffer) {
|
|
85
|
+
out += colorFn(buffer);
|
|
86
|
+
buffer = "";
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
while (index < line.length) {
|
|
90
|
+
CONTROL_SEQUENCE.lastIndex = index;
|
|
91
|
+
const control = CONTROL_SEQUENCE.exec(line);
|
|
92
|
+
if (control) {
|
|
93
|
+
flush();
|
|
94
|
+
out += control[0];
|
|
95
|
+
index = CONTROL_SEQUENCE.lastIndex;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const char = line[index];
|
|
99
|
+
if (column >= start && column < end) {
|
|
100
|
+
buffer += char;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
flush();
|
|
104
|
+
out += char;
|
|
105
|
+
}
|
|
106
|
+
column += visibleWidth(char);
|
|
107
|
+
index += 1;
|
|
108
|
+
}
|
|
109
|
+
flush();
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
2
112
|
/**
|
|
3
113
|
* Custom editor that handles app-level keybindings for coding-agent.
|
|
114
|
+
*
|
|
115
|
+
* The prompt prefix and placeholder are layered on in `render` rather than
|
|
116
|
+
* inside the base Editor, because `packages/tui` is frozen under ADR 0001 and
|
|
117
|
+
* its `EditorOptions` has no hook for either.
|
|
4
118
|
*/
|
|
5
119
|
export class CustomEditor extends Editor {
|
|
6
120
|
keybindings;
|
|
121
|
+
promptPrefix;
|
|
122
|
+
promptColor;
|
|
123
|
+
placeholderColor;
|
|
124
|
+
commandColor;
|
|
125
|
+
surfaceColor;
|
|
126
|
+
placeholder;
|
|
127
|
+
autocompleteRule;
|
|
128
|
+
autocompleteFooter;
|
|
129
|
+
modeLabel = "";
|
|
7
130
|
actionHandlers = new Map();
|
|
8
131
|
// Special handlers that can be dynamically replaced
|
|
9
132
|
onEscape;
|
|
@@ -14,6 +137,220 @@ export class CustomEditor extends Editor {
|
|
|
14
137
|
constructor(tui, theme, keybindings, options) {
|
|
15
138
|
super(tui, theme, options);
|
|
16
139
|
this.keybindings = keybindings;
|
|
140
|
+
this.promptPrefix = options?.promptPrefix ?? "";
|
|
141
|
+
this.promptColor = options?.promptColor ?? ((text) => text);
|
|
142
|
+
this.placeholder = options?.placeholder;
|
|
143
|
+
this.placeholderColor = options?.placeholderColor ?? ((text) => text);
|
|
144
|
+
this.commandColor = options?.commandColor;
|
|
145
|
+
this.surfaceColor = options?.surfaceColor;
|
|
146
|
+
this.autocompleteRule = options?.autocompleteRule;
|
|
147
|
+
this.autocompleteFooter = options?.autocompleteFooter;
|
|
148
|
+
}
|
|
149
|
+
render(width) {
|
|
150
|
+
const canRenderSurface = this.surfaceColor !== undefined && width > 0;
|
|
151
|
+
const surfacePadding = canRenderSurface ? Math.min(SURFACE_PADDING, Math.floor((width - 1) / 2)) : 0;
|
|
152
|
+
const editorWidth = canRenderSurface ? width - surfacePadding * 2 : width;
|
|
153
|
+
const lines = this.withAutocompleteChrome(this.renderEditor(editorWidth), editorWidth);
|
|
154
|
+
return canRenderSurface ? this.withSurface(lines, width, surfacePadding) : lines;
|
|
155
|
+
}
|
|
156
|
+
renderEditor(width) {
|
|
157
|
+
const promptPrefix = this.modeLabel ? `[${this.modeLabel}] ${this.promptPrefix}` : this.promptPrefix;
|
|
158
|
+
const prefixWidth = visibleWidth(promptPrefix);
|
|
159
|
+
// Reserve the prefix out of the width handed to the base Editor, so its
|
|
160
|
+
// wrapping, scrolling and cursor column all account for the space the
|
|
161
|
+
// prefix occupies. Bail out entirely when the terminal cannot spare it.
|
|
162
|
+
if (prefixWidth === 0 || width <= prefixWidth + 4) {
|
|
163
|
+
return this.withCommandColor(this.withPlaceholder(super.render(width)));
|
|
164
|
+
}
|
|
165
|
+
const inner = this.withCommandColor(this.withPlaceholder(super.render(width - prefixWidth)));
|
|
166
|
+
const borderPad = this.borderColor(BORDER_CHAR.repeat(prefixWidth));
|
|
167
|
+
const blank = " ".repeat(prefixWidth);
|
|
168
|
+
const styledPrefix = this.promptColor(promptPrefix);
|
|
169
|
+
// Structure is: top border, content lines, bottom border, then optional
|
|
170
|
+
// autocomplete rows. Counting borders tells the three regions apart
|
|
171
|
+
// without depending on how many lines each contains.
|
|
172
|
+
let borders = 0;
|
|
173
|
+
let firstContentLine = true;
|
|
174
|
+
return inner.map((line) => {
|
|
175
|
+
if (this.isBorderLine(line)) {
|
|
176
|
+
borders++;
|
|
177
|
+
return borderPad + line;
|
|
178
|
+
}
|
|
179
|
+
if (borders !== 1) {
|
|
180
|
+
// Autocomplete rows sit below the box; indent them to match.
|
|
181
|
+
return blank + line;
|
|
182
|
+
}
|
|
183
|
+
if (firstContentLine) {
|
|
184
|
+
firstContentLine = false;
|
|
185
|
+
return styledPrefix + line;
|
|
186
|
+
}
|
|
187
|
+
return blank + line;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Brackets the autocomplete rows with a rule and a keybinding footer.
|
|
192
|
+
*
|
|
193
|
+
* The rows arrive after the editor's second border, so the same border count
|
|
194
|
+
* that separates the box from its dropdown elsewhere in this file finds them
|
|
195
|
+
* here. Nothing is added when the dropdown is closed.
|
|
196
|
+
*/
|
|
197
|
+
withAutocompleteChrome(lines, width) {
|
|
198
|
+
if (!this.autocompleteRule && !this.autocompleteFooter)
|
|
199
|
+
return lines;
|
|
200
|
+
const blockLineCount = this.editorBlockLineCount(lines);
|
|
201
|
+
const rows = lines.slice(blockLineCount);
|
|
202
|
+
if (rows.length === 0)
|
|
203
|
+
return lines;
|
|
204
|
+
const chrome = [...lines.slice(0, blockLineCount)];
|
|
205
|
+
if (this.autocompleteRule)
|
|
206
|
+
chrome.push(this.autocompleteRule(width));
|
|
207
|
+
chrome.push(...rows);
|
|
208
|
+
if (this.autocompleteFooter)
|
|
209
|
+
chrome.push(this.autocompleteFooter());
|
|
210
|
+
return chrome;
|
|
211
|
+
}
|
|
212
|
+
withSurface(lines, width, padding) {
|
|
213
|
+
const surfaceColor = this.surfaceColor;
|
|
214
|
+
if (!surfaceColor)
|
|
215
|
+
return lines;
|
|
216
|
+
const result = [this.surfaceLine(" ".repeat(width), surfaceColor)];
|
|
217
|
+
const editorBlockLineCount = this.editorBlockLineCount(lines);
|
|
218
|
+
const editorContent = lines.slice(1, Math.max(1, editorBlockLineCount - 1));
|
|
219
|
+
for (const line of editorContent) {
|
|
220
|
+
result.push(this.surfaceLine(`${" ".repeat(padding)}${line}${" ".repeat(padding)}`, surfaceColor));
|
|
221
|
+
}
|
|
222
|
+
result.push(this.surfaceLine(" ".repeat(width), surfaceColor));
|
|
223
|
+
for (const line of lines.slice(editorBlockLineCount)) {
|
|
224
|
+
result.push(`${" ".repeat(padding)}${line}`);
|
|
225
|
+
}
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
editorBlockLineCount(lines) {
|
|
229
|
+
let borders = 0;
|
|
230
|
+
for (let index = 0; index < lines.length; index++) {
|
|
231
|
+
if (this.isBorderLine(lines[index]))
|
|
232
|
+
borders++;
|
|
233
|
+
if (borders === 2)
|
|
234
|
+
return index + 1;
|
|
235
|
+
}
|
|
236
|
+
return lines.length;
|
|
237
|
+
}
|
|
238
|
+
surfaceLine(line, surfaceColor) {
|
|
239
|
+
let output = "";
|
|
240
|
+
let visibleText = "";
|
|
241
|
+
let index = 0;
|
|
242
|
+
const flush = () => {
|
|
243
|
+
if (visibleText) {
|
|
244
|
+
output += surfaceColor(visibleText);
|
|
245
|
+
visibleText = "";
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
while (index < line.length) {
|
|
249
|
+
INVERTED_CURSOR_CELL.lastIndex = index;
|
|
250
|
+
const cursorCell = INVERTED_CURSOR_CELL.exec(line);
|
|
251
|
+
if (cursorCell) {
|
|
252
|
+
flush();
|
|
253
|
+
output += cursorCell[0];
|
|
254
|
+
index = INVERTED_CURSOR_CELL.lastIndex;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
CONTROL_SEQUENCE.lastIndex = index;
|
|
258
|
+
const control = CONTROL_SEQUENCE.exec(line);
|
|
259
|
+
if (control) {
|
|
260
|
+
flush();
|
|
261
|
+
output += control[0];
|
|
262
|
+
index = CONTROL_SEQUENCE.lastIndex;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
visibleText += line[index];
|
|
266
|
+
index += 1;
|
|
267
|
+
}
|
|
268
|
+
flush();
|
|
269
|
+
return output;
|
|
270
|
+
}
|
|
271
|
+
setModeLabel(label) {
|
|
272
|
+
this.modeLabel = label ?? "";
|
|
273
|
+
this.invalidate();
|
|
274
|
+
}
|
|
275
|
+
isBorderLine(line) {
|
|
276
|
+
const visible = stripAnsi(line.split(CURSOR_MARKER).join(""));
|
|
277
|
+
return BORDER_RULE.test(visible) || BORDER_SCROLL.test(visible);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Tint a leading `/command` on the first input line.
|
|
281
|
+
*
|
|
282
|
+
* The base Editor emits input text unstyled, so the only styling already
|
|
283
|
+
* present on the line is the cursor cell — which `colorVisibleRange` steps
|
|
284
|
+
* over. Only the command token is tinted; arguments after it stay plain, so
|
|
285
|
+
* the highlight marks what is being invoked rather than the whole line.
|
|
286
|
+
*/
|
|
287
|
+
withCommandColor(lines) {
|
|
288
|
+
const commandColor = this.commandColor;
|
|
289
|
+
if (!commandColor || lines.length < 2) {
|
|
290
|
+
return lines;
|
|
291
|
+
}
|
|
292
|
+
// Read the token from the model rather than the rendered line: the first
|
|
293
|
+
// visual row is the start of the text only when the caret has not scrolled
|
|
294
|
+
// the view down, and getText is unambiguous either way.
|
|
295
|
+
const firstLine = this.getText().split("\n", 1)[0] ?? "";
|
|
296
|
+
const match = COMMAND_TOKEN.exec(firstLine);
|
|
297
|
+
if (!match) {
|
|
298
|
+
return lines;
|
|
299
|
+
}
|
|
300
|
+
const [, indent, token] = match;
|
|
301
|
+
// The rendered row must still begin with that token, or the view has
|
|
302
|
+
// scrolled and the highlight would land on unrelated text.
|
|
303
|
+
if (!stripAnsi(lines[1].split(CURSOR_MARKER).join("")).startsWith(`${indent}${token}`)) {
|
|
304
|
+
return lines;
|
|
305
|
+
}
|
|
306
|
+
const start = visibleWidth(indent);
|
|
307
|
+
const updated = [...lines];
|
|
308
|
+
updated[1] = colorVisibleRange(lines[1], start, start + visibleWidth(token), commandColor);
|
|
309
|
+
return updated;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Replace the sole content line with the placeholder while the input is
|
|
313
|
+
* empty. Index 1 is always the first content line, and an empty editor has
|
|
314
|
+
* exactly one, so no line classification is needed here.
|
|
315
|
+
*/
|
|
316
|
+
withPlaceholder(lines) {
|
|
317
|
+
const placeholder = this.placeholder;
|
|
318
|
+
if (!placeholder || lines.length < 2 || this.getText().length > 0 || this.isShowingAutocomplete()) {
|
|
319
|
+
return lines;
|
|
320
|
+
}
|
|
321
|
+
const contentLine = lines[1];
|
|
322
|
+
// Preserve the cursor cell when focused; unfocused there is none, and the
|
|
323
|
+
// placeholder simply starts at column zero.
|
|
324
|
+
const cursorCell = this.sliceLeadingCursorCell(contentLine);
|
|
325
|
+
// Measure with visibleWidth alone — never visibleWidth(stripAnsi(...)),
|
|
326
|
+
// which scores the cursor marker as seven columns. See hazard 1 at the top
|
|
327
|
+
// of this file.
|
|
328
|
+
const available = visibleWidth(contentLine) - visibleWidth(cursorCell);
|
|
329
|
+
if (available <= 0) {
|
|
330
|
+
return lines;
|
|
331
|
+
}
|
|
332
|
+
// Cut by display width, not by String.slice: `available` counts terminal
|
|
333
|
+
// columns, while slice counts UTF-16 code units. They only agree for
|
|
334
|
+
// plain ASCII, and disagreeing splits a surrogate pair or overruns the
|
|
335
|
+
// line on the first wide character.
|
|
336
|
+
const text = truncateToWidth(placeholder, available, "");
|
|
337
|
+
const padding = " ".repeat(Math.max(0, available - visibleWidth(text)));
|
|
338
|
+
const updated = [...lines];
|
|
339
|
+
updated[1] = cursorCell + this.placeholderColor(text) + padding;
|
|
340
|
+
return updated;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* The base Editor renders an empty line as the hardware-cursor marker
|
|
344
|
+
* followed by a reverse-video cell: `CURSOR_MARKER ESC[7m <space> ESC[0m`.
|
|
345
|
+
* Both parts are kept verbatim.
|
|
346
|
+
*
|
|
347
|
+
* The `ESC[7m … ESC[0m` run is matched explicitly. Do not "simplify" this to
|
|
348
|
+
* a general leading-SGR match such as `(?:\x1b\[[0-9;]*m)*` — see hazard 2 at
|
|
349
|
+
* the top of this file for what that breaks.
|
|
350
|
+
*/
|
|
351
|
+
sliceLeadingCursorCell(line) {
|
|
352
|
+
const match = /^(?:\x1b_pi:c\x07)?(?:\x1b\[7m.\x1b\[(?:0|27)m)?/.exec(line);
|
|
353
|
+
return match?.[0] ?? "";
|
|
17
354
|
}
|
|
18
355
|
/**
|
|
19
356
|
* Register a handler for an app action.
|
|
@@ -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 { Editor, type EditorOptions, type EditorTheme, type TUI } from \"@earendil-works/pi-tui\";\nimport type { AppKeybinding, KeybindingsManager } from \"../../../core/keybindings.ts\";\n\n/**\n * Custom editor that handles app-level keybindings for coding-agent.\n */\nexport class CustomEditor extends Editor {\n\tprivate keybindings: KeybindingsManager;\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?: EditorOptions) {\n\t\tsuper(tui, theme, options);\n\t\tthis.keybindings = keybindings;\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\";\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/** 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 promptPrefix: string;\n\tprivate readonly promptColor: (text: string) => string;\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 modeLabel = \"\";\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\tthis.promptPrefix = options?.promptPrefix ?? \"\";\n\t\tthis.promptColor = options?.promptColor ?? ((text) => text);\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 promptPrefix = this.modeLabel ? `[${this.modeLabel}] ${this.promptPrefix}` : this.promptPrefix;\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 = this.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\tsetModeLabel(label: string | undefined): void {\n\t\tthis.modeLabel = label ?? \"\";\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"]}
|
|
@@ -2,13 +2,19 @@ import type { Component } from "@earendil-works/pi-tui";
|
|
|
2
2
|
/**
|
|
3
3
|
* Dynamic border component that adjusts to viewport width.
|
|
4
4
|
*
|
|
5
|
+
* It is the boundary every overlay draws, so it is deliberately quiet: a dotted
|
|
6
|
+
* rule in `borderMuted` rather than a solid one in the brand accent. A
|
|
7
|
+
* full-width accent rule above and below every selector made the accent the
|
|
8
|
+
* loudest thing on screen in exactly the moment the user is reading a list.
|
|
9
|
+
*
|
|
5
10
|
* Note: When used from extensions loaded via jiti, the global `theme` may be undefined
|
|
6
11
|
* because jiti creates a separate module cache. Always pass an explicit color
|
|
7
12
|
* function when using DynamicBorder in components exported for extension use.
|
|
8
13
|
*/
|
|
9
14
|
export declare class DynamicBorder implements Component {
|
|
10
15
|
private color;
|
|
11
|
-
|
|
16
|
+
private glyph;
|
|
17
|
+
constructor(color?: (str: string) => string, glyph?: string);
|
|
12
18
|
invalidate(): void;
|
|
13
19
|
render(width: number): string[];
|
|
14
20
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-border.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/dynamic-border.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"dynamic-border.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/dynamic-border.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAMxD;;;;;;;;;;;GAWG;AACH,qBAAa,aAAc,YAAW,SAAS;IAC9C,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,KAAK,CAAS;IAEtB,YAAY,KAAK,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAA8C,EAAE,KAAK,SAAe,EAGvG;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE9B;CACD","sourcesContent":["import type { Component } from \"@earendil-works/pi-tui\";\nimport { theme } from \"../theme/theme.ts\";\n\n/** Dotted, so an overlay boundary never reads as the solid structural rule. */\nconst OVERLAY_RULE = \"┄\";\n\n/**\n * Dynamic border component that adjusts to viewport width.\n *\n * It is the boundary every overlay draws, so it is deliberately quiet: a dotted\n * rule in `borderMuted` rather than a solid one in the brand accent. A\n * full-width accent rule above and below every selector made the accent the\n * loudest thing on screen in exactly the moment the user is reading a list.\n *\n * Note: When used from extensions loaded via jiti, the global `theme` may be undefined\n * because jiti creates a separate module cache. Always pass an explicit color\n * function when using DynamicBorder in components exported for extension use.\n */\nexport class DynamicBorder implements Component {\n\tprivate color: (str: string) => string;\n\tprivate glyph: string;\n\n\tconstructor(color: (str: string) => string = (str) => theme.fg(\"borderMuted\", str), glyph = OVERLAY_RULE) {\n\t\tthis.color = color;\n\t\tthis.glyph = glyph;\n\t}\n\n\tinvalidate(): void {\n\t\t// No cached state to invalidate currently\n\t}\n\n\trender(width: number): string[] {\n\t\treturn [this.color(this.glyph.repeat(Math.max(1, width)))];\n\t}\n}\n"]}
|