@oh-my-pi/pi-tui 18.2.4 → 18.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +43 -0
- package/dist/types/app-keybindings.d.ts +395 -0
- package/dist/types/apps/autoresearch-dashboard.d.ts +43 -0
- package/dist/types/apps/autoresearch-data.d.ts +15 -0
- package/dist/types/apps/cleanse-board.d.ts +66 -0
- package/dist/types/apps/cleanse-picker.d.ts +23 -0
- package/dist/types/apps/debug/log-formatting.d.ts +6 -0
- package/dist/types/apps/debug/log-viewer.d.ts +81 -0
- package/dist/types/apps/debug/protocol-probe.d.ts +42 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +58 -0
- package/dist/types/apps/debug/raw-sse.d.ts +26 -0
- package/dist/types/apps/debug/terminal-info.d.ts +34 -0
- package/dist/types/apps/debug/viewer-frame.d.ts +52 -0
- package/dist/types/apps/git/avatar.d.ts +9 -0
- package/dist/types/apps/git/colors.d.ts +49 -0
- package/dist/types/apps/git/diff-pane.d.ts +188 -0
- package/dist/types/apps/git/git-tui.d.ts +66 -0
- package/dist/types/apps/git/sidebar.d.ts +149 -0
- package/dist/types/apps/git/state.d.ts +117 -0
- package/dist/types/apps/if-bench-board.d.ts +75 -0
- package/dist/types/apps/live-visualizer.d.ts +33 -0
- package/dist/types/apps/ps-data.d.ts +51 -0
- package/dist/types/apps/ps-top.d.ts +33 -0
- package/dist/types/apps/session-picker.d.ts +26 -0
- package/dist/types/apps/setup-model-picker.d.ts +13 -0
- package/dist/types/apps/standalone-picker.d.ts +42 -0
- package/dist/types/autocomplete.d.ts +4 -0
- package/dist/types/chat/advisor-message.d.ts +11 -0
- package/dist/types/chat/assistant-message.d.ts +106 -0
- package/dist/types/chat/background-tan-message.d.ts +9 -0
- package/dist/types/chat/bash-execution.d.ts +47 -0
- package/dist/types/chat/cache-invalidation-marker.d.ts +50 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +50 -0
- package/dist/types/chat/collab-prompt-message.d.ts +10 -0
- package/dist/types/chat/compaction-summary-message.d.ts +54 -0
- package/dist/types/chat/custom-message.d.ts +10 -0
- package/dist/types/chat/display-preferences.d.ts +13 -0
- package/dist/types/chat/eval-execution.d.ts +29 -0
- package/dist/types/chat/execution-shared.d.ts +51 -0
- package/dist/types/chat/extension-types.d.ts +28 -0
- package/dist/types/chat/hook-message.d.ts +10 -0
- package/dist/types/chat/image-loading.d.ts +46 -0
- package/dist/types/chat/late-diagnostics-message.d.ts +21 -0
- package/dist/types/chat/messages.d.ts +189 -0
- package/dist/types/chat/reaction.d.ts +30 -0
- package/dist/types/chat/read-target.d.ts +4 -0
- package/dist/types/chat/read-tool-group.d.ts +67 -0
- package/dist/types/chat/served-model-marker.d.ts +47 -0
- package/dist/types/chat/skill-message.d.ts +33 -0
- package/dist/types/chat/skill-title-input.d.ts +14 -0
- package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +12 -0
- package/dist/types/chat/thinking-display.d.ts +12 -0
- package/dist/types/chat/todo-reminder.d.ts +13 -0
- package/dist/types/chat/tool-execution.d.ts +138 -0
- package/dist/types/chat/transcript-browser.d.ts +93 -0
- package/dist/types/chat/transcript-entry.d.ts +45 -0
- package/dist/types/chat/transcript-outline.d.ts +86 -0
- package/dist/types/chat/transcript-render-helpers.d.ts +92 -0
- package/dist/types/chat/ttsr-notification.d.ts +23 -0
- package/dist/types/chat/user-message.d.ts +52 -0
- package/dist/types/chrome/chat-block.d.ts +64 -0
- package/dist/types/chrome/collab-qrcode.d.ts +28 -0
- package/dist/types/chrome/context-thresholds.d.ts +10 -0
- package/dist/types/chrome/countdown-timer.d.ts +14 -0
- package/dist/types/chrome/diff.d.ts +14 -0
- package/dist/types/chrome/dynamic-border.d.ts +16 -0
- package/dist/types/chrome/error-block.d.ts +17 -0
- package/dist/types/chrome/form-theme.d.ts +3 -0
- package/dist/types/chrome/format.d.ts +12 -0
- package/dist/types/chrome/index.d.ts +20 -0
- package/dist/types/chrome/keybinding-hints.d.ts +40 -0
- package/dist/types/chrome/live-board.d.ts +23 -0
- package/dist/types/chrome/local-date.d.ts +4 -0
- package/dist/types/chrome/message-divider.d.ts +22 -0
- package/dist/types/chrome/message-frame.d.ts +52 -0
- package/dist/types/chrome/message-notice.d.ts +35 -0
- package/dist/types/chrome/overlay-box.d.ts +74 -0
- package/dist/types/chrome/qrcode.d.ts +48 -0
- package/dist/types/chrome/segment-track.d.ts +27 -0
- package/dist/types/chrome/select-list-mouse-routing.d.ts +4 -0
- package/dist/types/chrome/selector-helpers.d.ts +52 -0
- package/dist/types/chrome/shared.d.ts +5 -0
- package/dist/types/chrome/streaming-panel.d.ts +19 -0
- package/dist/types/chrome/tool-activity.d.ts +17 -0
- package/dist/types/chrome/transcript-container.d.ts +129 -0
- package/dist/types/chrome/transcript-status.d.ts +12 -0
- package/dist/types/chrome/visual-truncate.d.ts +19 -0
- package/dist/types/components/disclosure.d.ts +42 -0
- package/dist/types/components/form.d.ts +130 -0
- package/dist/types/components/key-value-list.d.ts +24 -0
- package/dist/types/components/layout/geometry.d.ts +73 -0
- package/dist/types/components/layout/row.d.ts +55 -0
- package/dist/types/components/layout/split-pane.d.ts +80 -0
- package/dist/types/components/layout/stack.d.ts +50 -0
- package/dist/types/components/menu-selection.d.ts +71 -0
- package/dist/types/components/metric.d.ts +44 -0
- package/dist/types/components/progress-bar.d.ts +36 -0
- package/dist/types/components/scroll-view.d.ts +53 -19
- package/dist/types/components/scroll-viewport.d.ts +46 -0
- package/dist/types/components/section.d.ts +19 -0
- package/dist/types/components/select-list.d.ts +46 -2
- package/dist/types/components/settings-list.d.ts +20 -0
- package/dist/types/components/table.d.ts +32 -0
- package/dist/types/components/tree-view.d.ts +130 -0
- package/dist/types/components/wizard-step.d.ts +60 -0
- package/dist/types/hotkeys-markdown.d.ts +7 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/keybinding-matchers.d.ts +35 -0
- package/dist/types/keys.d.ts +2 -2
- package/dist/types/lang-from-path.d.ts +9 -0
- package/dist/types/overlays/advisor-config.d.ts +114 -0
- package/dist/types/overlays/agent-activity.d.ts +41 -0
- package/dist/types/overlays/agent-hub-projection.d.ts +33 -0
- package/dist/types/overlays/agent-hub-renderer.d.ts +53 -0
- package/dist/types/overlays/agent-hub-types.d.ts +99 -0
- package/dist/types/overlays/agent-hub.d.ts +126 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +71 -0
- package/dist/types/overlays/agents-hub.d.ts +72 -0
- package/dist/types/overlays/ask-dialog.d.ts +88 -0
- package/dist/types/overlays/bordered-loader.d.ts +11 -0
- package/dist/types/overlays/btw-history-panel.d.ts +29 -0
- package/dist/types/overlays/btw-history.d.ts +17 -0
- package/dist/types/overlays/btw-panel.d.ts +26 -0
- package/dist/types/overlays/cleanse-panel.d.ts +36 -0
- package/dist/types/overlays/codex-reset-fireworks.d.ts +52 -0
- package/dist/types/overlays/composer-shape-preview.d.ts +47 -0
- package/dist/types/overlays/composer-shape-registry.d.ts +50 -0
- package/dist/types/overlays/copy-selector.d.ts +48 -0
- package/dist/types/overlays/copy-targets.d.ts +63 -0
- package/dist/types/overlays/error-banner.d.ts +13 -0
- package/dist/types/overlays/extensions/display-text.d.ts +10 -0
- package/dist/types/overlays/extensions/extension-dashboard.d.ts +63 -0
- package/dist/types/overlays/extensions/extension-list.d.ts +54 -0
- package/dist/types/overlays/extensions/inspector-model.d.ts +109 -0
- package/dist/types/overlays/extensions/inspector-panel.d.ts +17 -0
- package/dist/types/overlays/extensions/live-tool-session.d.ts +28 -0
- package/dist/types/overlays/extensions/mcp-runtime.d.ts +107 -0
- package/dist/types/overlays/extensions/state-manager.d.ts +42 -0
- package/dist/types/overlays/extensions/types.d.ts +165 -0
- package/dist/types/overlays/history-search.d.ts +16 -0
- package/dist/types/overlays/hook-editor.d.ts +44 -0
- package/dist/types/overlays/hook-input.d.ts +20 -0
- package/dist/types/overlays/hook-selector.d.ts +71 -0
- package/dist/types/overlays/hub-frame.d.ts +77 -0
- package/dist/types/overlays/login-dialog.d.ts +43 -0
- package/dist/types/overlays/logout-account-selector.d.ts +15 -0
- package/dist/types/overlays/mcp-add-wizard.d.ts +87 -0
- package/dist/types/overlays/model-browser.d.ts +213 -0
- package/dist/types/overlays/model-hub.d.ts +72 -0
- package/dist/types/overlays/model-picker.d.ts +74 -0
- package/dist/types/overlays/model-selector.d.ts +52 -0
- package/dist/types/overlays/move-overlay.d.ts +39 -0
- package/dist/types/overlays/oauth-selector.d.ts +41 -0
- package/dist/types/overlays/omfg-panel.d.ts +20 -0
- package/dist/types/overlays/pause-screen.d.ts +43 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +89 -0
- package/dist/types/overlays/plan-save-overlay.d.ts +19 -0
- package/dist/types/overlays/plan-toc.d.ts +41 -0
- package/dist/types/overlays/plugin-selector.d.ts +28 -0
- package/dist/types/overlays/plugin-settings.d.ts +180 -0
- package/dist/types/overlays/queue-mode-selector.d.ts +11 -0
- package/dist/types/overlays/reset-usage-selector.d.ts +23 -0
- package/dist/types/overlays/rewind-selector.d.ts +61 -0
- package/dist/types/overlays/running-subagent-badge.d.ts +6 -0
- package/dist/types/overlays/session-account-selector.d.ts +24 -0
- package/dist/types/overlays/session-info-overlay.d.ts +17 -0
- package/dist/types/overlays/session-observer-registry.d.ts +77 -0
- package/dist/types/overlays/session-selector.d.ts +131 -0
- package/dist/types/overlays/settings-defs.d.ts +119 -0
- package/dist/types/overlays/settings-selector.d.ts +73 -0
- package/dist/types/overlays/show-images-selector.d.ts +11 -0
- package/dist/types/overlays/snapcompact-shape-preview.d.ts +31 -0
- package/dist/types/overlays/theme-selector.d.ts +12 -0
- package/dist/types/overlays/thinking-selector.d.ts +12 -0
- package/dist/types/overlays/tiny-title-download-progress.d.ts +21 -0
- package/dist/types/overlays/tree-selector.d.ts +92 -0
- package/dist/types/overlays/usage-dashboard.d.ts +94 -0
- package/dist/types/overlays/usage-display.d.ts +5 -0
- package/dist/types/overlays/usage-row.d.ts +17 -0
- package/dist/types/prompt/attachment-chips.d.ts +19 -0
- package/dist/types/prompt/composer-attachments.d.ts +71 -0
- package/dist/types/prompt/composer-cache.d.ts +37 -0
- package/dist/types/prompt/composer.d.ts +159 -0
- package/dist/types/prompt/custom-editor.d.ts +261 -0
- package/dist/types/prompt/editor-top-gap.d.ts +18 -0
- package/dist/types/prompt/emoji-autocomplete.d.ts +16 -0
- package/dist/types/prompt/github-ref-autocomplete.d.ts +35 -0
- package/dist/types/prompt/gradient-highlight.d.ts +32 -0
- package/dist/types/prompt/image-format.d.ts +4 -0
- package/dist/types/prompt/image-references.d.ts +26 -0
- package/dist/types/prompt/interactive-context-helpers.d.ts +43 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +56 -0
- package/dist/types/prompt/macos-spelling.d.ts +44 -0
- package/dist/types/prompt/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/prompt/magic-keywords.d.ts +26 -0
- package/dist/types/prompt/markdown-prose.d.ts +15 -0
- package/dist/types/prompt/model-mention-autocomplete.d.ts +25 -0
- package/dist/types/prompt/model-mention-syntax.d.ts +24 -0
- package/dist/types/prompt/orchestrate.d.ts +13 -0
- package/dist/types/prompt/prompt-action-autocomplete.d.ts +56 -0
- package/dist/types/prompt/queue-input.d.ts +8 -0
- package/dist/types/prompt/skill-tokens.d.ts +13 -0
- package/dist/types/prompt/tools-markdown.d.ts +12 -0
- package/dist/types/prompt/ultrathink.d.ts +13 -0
- package/dist/types/prompt/usage-amounts.d.ts +26 -0
- package/dist/types/prompt/video.d.ts +18 -0
- package/dist/types/prompt/welcome.d.ts +83 -0
- package/dist/types/prompt/workflow.d.ts +13 -0
- package/dist/types/render/code-cell.d.ts +51 -0
- package/dist/types/render/file-list.d.ts +22 -0
- package/dist/types/render/hyperlink.d.ts +80 -0
- package/dist/types/render/index.d.ts +16 -0
- package/dist/types/render/output-block.d.ts +54 -0
- package/dist/types/render/output-pane.d.ts +80 -0
- package/dist/types/render/render-utils.d.ts +312 -0
- package/dist/types/render/sixel.d.ts +21 -0
- package/dist/types/render/status-line.d.ts +23 -0
- package/dist/types/render/tool-card.d.ts +71 -0
- package/dist/types/render/tree-list.d.ts +29 -0
- package/dist/types/render/types.d.ts +17 -0
- package/dist/types/render/utils.d.ts +43 -0
- package/dist/types/render/width-aware-text.d.ts +23 -0
- package/dist/types/setup/lazy.d.ts +3 -0
- package/dist/types/setup/scenes/composer.d.ts +3 -0
- package/dist/types/setup/scenes/glyph.d.ts +3 -0
- package/dist/types/setup/scenes/model.d.ts +3 -0
- package/dist/types/setup/scenes/outro.d.ts +2 -0
- package/dist/types/setup/scenes/providers.d.ts +3 -0
- package/dist/types/setup/scenes/sign-in.d.ts +21 -0
- package/dist/types/setup/scenes/splash.d.ts +11 -0
- package/dist/types/setup/scenes/theme.d.ts +3 -0
- package/dist/types/setup/scenes/types.d.ts +110 -0
- package/dist/types/setup/scenes/web-search.d.ts +22 -0
- package/dist/types/setup/setup-version.d.ts +11 -0
- package/dist/types/setup/startup-splash.d.ts +9 -0
- package/dist/types/setup/wizard-overlay.d.ts +16 -0
- package/dist/types/setup/wizard.d.ts +25 -0
- package/dist/types/status-line/component.d.ts +182 -0
- package/dist/types/status-line/context-usage.d.ts +156 -0
- package/dist/types/status-line/footer.d.ts +30 -0
- package/dist/types/status-line/git-utils.d.ts +22 -0
- package/dist/types/status-line/host.d.ts +129 -0
- package/dist/types/status-line/index.d.ts +6 -0
- package/dist/types/status-line/loop.d.ts +18 -0
- package/dist/types/status-line/metrics.d.ts +21 -0
- package/dist/types/status-line/presets.d.ts +3 -0
- package/dist/types/status-line/schema.d.ts +15 -0
- package/dist/types/status-line/segments.d.ts +5 -0
- package/dist/types/status-line/separators.d.ts +3 -0
- package/dist/types/status-line/types.d.ts +236 -0
- package/dist/types/stdin-buffer.d.ts +4 -0
- package/dist/types/theme/color.d.ts +24 -0
- package/dist/types/theme/defaults/index.d.ts +9489 -0
- package/dist/types/theme/index.d.ts +4 -0
- package/dist/types/theme/loader.d.ts +23 -0
- package/dist/types/theme/mermaid-cache.d.ts +26 -0
- package/dist/types/theme/schema-validation.d.ts +3 -0
- package/dist/types/theme/schema.d.ts +25 -0
- package/dist/types/theme/session-color.d.ts +43 -0
- package/dist/types/theme/shimmer.d.ts +44 -0
- package/dist/types/theme/symbols.d.ts +34 -0
- package/dist/types/theme/theme-class.d.ts +299 -0
- package/dist/types/theme/theme.d.ts +111 -0
- package/dist/types/theme/tui-adapters.d.ts +30 -0
- package/dist/types/thinking.d.ts +165 -0
- package/dist/types/tools/agent-tree.d.ts +36 -0
- package/dist/types/tools/ask.d.ts +67 -0
- package/dist/types/tools/ast-edit.d.ts +53 -0
- package/dist/types/tools/ast-grep.d.ts +52 -0
- package/dist/types/tools/autoresearch.d.ts +156 -0
- package/dist/types/tools/bash-interactive.d.ts +28 -0
- package/dist/types/tools/bash.d.ts +103 -0
- package/dist/types/tools/conflict-detect.d.ts +43 -0
- package/dist/types/tools/debug.d.ts +60 -0
- package/dist/types/tools/default-renderer.d.ts +24 -0
- package/dist/types/tools/edit.d.ts +158 -0
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +5 -0
- package/dist/types/tools/eval-format/index.d.ts +5 -0
- package/dist/types/tools/eval-format/javascript.d.ts +2 -0
- package/dist/types/tools/eval-format/python.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +94 -0
- package/dist/types/tools/fetch.d.ts +42 -0
- package/dist/types/tools/gh-format.d.ts +8 -0
- package/dist/types/tools/github.d.ts +144 -0
- package/dist/types/tools/glob.d.ts +44 -0
- package/dist/types/tools/goal.d.ts +42 -0
- package/dist/types/tools/grep.d.ts +64 -0
- package/dist/types/tools/grouped-file-output.d.ts +71 -0
- package/dist/types/tools/hashline-format.d.ts +28 -0
- package/dist/types/tools/hub.d.ts +340 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/json-tree.d.ts +59 -0
- package/dist/types/tools/line-ranges.d.ts +29 -0
- package/dist/types/tools/list-limit.d.ts +15 -0
- package/dist/types/tools/lsp.d.ts +72 -0
- package/dist/types/tools/match-line-format.d.ts +11 -0
- package/dist/types/tools/mcp.d.ts +81 -0
- package/dist/types/tools/memory.d.ts +67 -0
- package/dist/types/tools/output-meta.d.ts +151 -0
- package/dist/types/tools/read.d.ts +139 -0
- package/dist/types/tools/renderer.d.ts +98 -0
- package/dist/types/tools/report-tool-issue.d.ts +8 -0
- package/dist/types/tools/resolve.d.ts +47 -0
- package/dist/types/tools/streaming-output.d.ts +282 -0
- package/dist/types/tools/subprocess.d.ts +24 -0
- package/dist/types/tools/task-repair-args.d.ts +53 -0
- package/dist/types/tools/task-yield-assembly.d.ts +20 -0
- package/dist/types/tools/task.d.ts +425 -0
- package/dist/types/tools/terminal-output.d.ts +8 -0
- package/dist/types/tools/think.d.ts +13 -0
- package/dist/types/tools/todo.d.ts +118 -0
- package/dist/types/tools/tool-errors.d.ts +7 -0
- package/dist/types/tools/vibe.d.ts +120 -0
- package/dist/types/tools/web-search.d.ts +195 -0
- package/dist/types/tools/write.d.ts +76 -0
- package/dist/types/tools/xd-url.d.ts +13 -0
- package/dist/types/tools/xdev.d.ts +52 -0
- package/dist/types/tui.d.ts +18 -0
- package/dist/types/utils.d.ts +2 -0
- package/package.json +37 -3
- package/src/app-keybindings.ts +760 -0
- package/src/apps/autoresearch-dashboard.ts +500 -0
- package/src/apps/autoresearch-data.ts +64 -0
- package/src/apps/cleanse-board.ts +341 -0
- package/src/apps/cleanse-picker.ts +57 -0
- package/src/apps/debug/log-formatting.ts +53 -0
- package/src/apps/debug/log-viewer.ts +868 -0
- package/src/apps/debug/protocol-probe.ts +267 -0
- package/src/apps/debug/raw-sse-buffer.ts +449 -0
- package/src/apps/debug/raw-sse.ts +282 -0
- package/src/apps/debug/terminal-info.ts +115 -0
- package/src/apps/debug/viewer-frame.ts +174 -0
- package/src/apps/git/avatar.ts +52 -0
- package/src/apps/git/colors.ts +109 -0
- package/src/apps/git/diff-pane.ts +1512 -0
- package/src/apps/git/git-tui.ts +889 -0
- package/src/apps/git/sidebar.ts +1204 -0
- package/src/apps/git/state.ts +113 -0
- package/src/apps/if-bench-board.ts +324 -0
- package/src/apps/live-visualizer.ts +238 -0
- package/src/apps/ps-data.ts +116 -0
- package/src/apps/ps-top.ts +422 -0
- package/src/apps/session-picker.ts +95 -0
- package/src/apps/setup-model-picker.ts +21 -0
- package/src/apps/standalone-picker.ts +111 -0
- package/src/autocomplete.ts +9 -13
- package/src/chat/advisor-message.ts +175 -0
- package/src/chat/assistant-message.ts +1214 -0
- package/src/chat/background-tan-message.ts +35 -0
- package/src/chat/bash-execution.ts +394 -0
- package/src/chat/cache-invalidation-marker.ts +94 -0
- package/src/chat/chat-transcript-builder.ts +593 -0
- package/src/chat/collab-prompt-message.ts +34 -0
- package/src/chat/compaction-summary-message.ts +261 -0
- package/src/chat/custom-message.ts +23 -0
- package/src/chat/display-preferences.ts +24 -0
- package/src/chat/eval-execution.ts +172 -0
- package/src/chat/execution-shared.ts +113 -0
- package/src/chat/extension-types.ts +46 -0
- package/src/chat/hook-message.ts +20 -0
- package/src/chat/image-loading.ts +122 -0
- package/src/chat/late-diagnostics-message.ts +92 -0
- package/src/chat/messages.ts +305 -0
- package/src/chat/reaction.ts +65 -0
- package/src/chat/read-target.ts +7 -0
- package/src/chat/read-tool-group.ts +873 -0
- package/src/chat/served-model-marker.ts +91 -0
- package/src/chat/skill-message.ts +220 -0
- package/src/chat/skill-title-input.ts +41 -0
- package/src/chat/stripped-tool-calls-placeholder.ts +35 -0
- package/src/chat/thinking-display.ts +312 -0
- package/src/chat/todo-reminder.ts +33 -0
- package/src/chat/tool-execution.ts +1395 -0
- package/src/chat/transcript-browser.ts +268 -0
- package/src/chat/transcript-entry.ts +96 -0
- package/src/chat/transcript-outline.ts +260 -0
- package/src/chat/transcript-render-helpers.ts +315 -0
- package/src/chat/ttsr-notification.ts +123 -0
- package/src/chat/user-message.ts +287 -0
- package/src/chrome/chat-block.ts +110 -0
- package/src/chrome/collab-qrcode.ts +74 -0
- package/src/chrome/context-thresholds.ts +85 -0
- package/src/chrome/countdown-timer.ts +74 -0
- package/src/chrome/diff.ts +266 -0
- package/src/chrome/dynamic-border.ts +36 -0
- package/src/chrome/error-block.ts +63 -0
- package/src/chrome/form-theme.ts +10 -0
- package/src/chrome/format.ts +66 -0
- package/src/chrome/index.ts +21 -0
- package/src/chrome/keybinding-hints.ts +55 -0
- package/src/chrome/live-board.ts +123 -0
- package/src/chrome/local-date.ts +20 -0
- package/src/chrome/message-divider.ts +51 -0
- package/src/chrome/message-frame.ts +164 -0
- package/src/chrome/message-notice.ts +103 -0
- package/src/chrome/overlay-box.ts +301 -0
- package/src/chrome/qrcode.ts +539 -0
- package/src/chrome/segment-track.ts +86 -0
- package/src/chrome/select-list-mouse-routing.ts +11 -0
- package/src/chrome/selector-helpers.ts +119 -0
- package/src/chrome/shared.ts +27 -0
- package/src/chrome/streaming-panel.ts +72 -0
- package/src/chrome/tool-activity.ts +44 -0
- package/src/chrome/transcript-container.ts +902 -0
- package/src/chrome/transcript-status.ts +27 -0
- package/src/chrome/visual-truncate.ts +70 -0
- package/src/components/disclosure.ts +235 -0
- package/src/components/editor.ts +10 -12
- package/src/components/form.ts +566 -0
- package/src/components/input.ts +6 -27
- package/src/components/key-value-list.ts +88 -0
- package/src/components/layout/geometry.ts +202 -0
- package/src/components/layout/row.ts +283 -0
- package/src/components/layout/split-pane.ts +349 -0
- package/src/components/layout/stack.ts +261 -0
- package/src/components/markdown.ts +16 -12
- package/src/components/menu-selection.ts +248 -0
- package/src/components/metric.ts +132 -0
- package/src/components/progress-bar.ts +140 -0
- package/src/components/scroll-view.ts +372 -70
- package/src/components/scroll-viewport.ts +154 -0
- package/src/components/section.ts +77 -0
- package/src/components/select-list.ts +238 -159
- package/src/components/settings-list.ts +103 -80
- package/src/components/table.ts +127 -0
- package/src/components/tree-view.ts +481 -0
- package/src/components/wizard-step.ts +229 -0
- package/src/deccara.ts +1 -3
- package/src/hotkeys-markdown.ts +100 -0
- package/src/index.ts +18 -0
- package/src/keybinding-matchers.ts +99 -0
- package/src/keys.ts +2 -2
- package/src/lang-from-path.ts +252 -0
- package/src/overlays/advisor-config.ts +761 -0
- package/src/overlays/agent-activity.ts +113 -0
- package/src/overlays/agent-hub-projection.ts +250 -0
- package/src/overlays/agent-hub-renderer.ts +281 -0
- package/src/overlays/agent-hub-types.ts +74 -0
- package/src/overlays/agent-hub.ts +1562 -0
- package/src/overlays/agent-transcript-viewer.ts +669 -0
- package/src/overlays/agents-hub.ts +1203 -0
- package/src/overlays/ask-dialog.ts +1202 -0
- package/src/overlays/bordered-loader.ts +41 -0
- package/src/overlays/btw-history-panel.ts +591 -0
- package/src/overlays/btw-history.ts +33 -0
- package/src/overlays/btw-panel.ts +167 -0
- package/src/overlays/cleanse-panel.ts +167 -0
- package/src/overlays/codex-reset-fireworks.ts +375 -0
- package/src/overlays/composer-shape-preview.ts +137 -0
- package/src/overlays/composer-shape-registry.ts +82 -0
- package/src/overlays/copy-selector.ts +618 -0
- package/src/overlays/copy-targets.ts +210 -0
- package/src/overlays/error-banner.ts +39 -0
- package/src/overlays/extensions/display-text.ts +39 -0
- package/src/overlays/extensions/extension-dashboard.ts +645 -0
- package/src/overlays/extensions/extension-list.ts +667 -0
- package/src/overlays/extensions/inspector-model.ts +484 -0
- package/src/overlays/extensions/inspector-panel.ts +669 -0
- package/src/overlays/extensions/live-tool-session.ts +67 -0
- package/src/overlays/extensions/mcp-runtime.ts +289 -0
- package/src/overlays/extensions/state-manager.ts +337 -0
- package/src/overlays/extensions/types.ts +208 -0
- package/src/overlays/history-search.ts +268 -0
- package/src/overlays/hook-editor.ts +272 -0
- package/src/overlays/hook-input.ts +84 -0
- package/src/overlays/hook-selector.ts +635 -0
- package/src/overlays/hub-frame.ts +262 -0
- package/src/overlays/login-dialog.ts +231 -0
- package/src/overlays/logout-account-selector.ts +115 -0
- package/src/overlays/mcp-add-wizard.ts +1436 -0
- package/src/overlays/model-browser.ts +1176 -0
- package/src/overlays/model-hub.ts +2279 -0
- package/src/overlays/model-picker.ts +285 -0
- package/src/overlays/model-selector.ts +107 -0
- package/src/overlays/move-overlay.ts +197 -0
- package/src/overlays/oauth-selector.ts +446 -0
- package/src/overlays/omfg-panel.ts +136 -0
- package/src/overlays/pause-screen.ts +198 -0
- package/src/overlays/plan-review-overlay.ts +1227 -0
- package/src/overlays/plan-save-overlay.ts +74 -0
- package/src/overlays/plan-toc.ts +139 -0
- package/src/overlays/plugin-selector.ts +97 -0
- package/src/overlays/plugin-settings.ts +839 -0
- package/src/overlays/queue-mode-selector.ts +55 -0
- package/src/overlays/reset-usage-selector.ts +170 -0
- package/src/overlays/rewind-selector.ts +499 -0
- package/src/overlays/running-subagent-badge.ts +19 -0
- package/src/overlays/session-account-selector.ts +68 -0
- package/src/overlays/session-info-overlay.ts +96 -0
- package/src/overlays/session-observer-registry.ts +289 -0
- package/src/overlays/session-selector.ts +1137 -0
- package/src/overlays/settings-defs.ts +315 -0
- package/src/overlays/settings-selector.ts +1415 -0
- package/src/overlays/show-images-selector.ts +44 -0
- package/src/overlays/snapcompact-shape-preview-doc.md +14 -0
- package/src/overlays/snapcompact-shape-preview.ts +192 -0
- package/src/overlays/theme-selector.ts +62 -0
- package/src/overlays/thinking-selector.ts +51 -0
- package/src/overlays/tiny-title-download-progress.ts +102 -0
- package/src/overlays/tree-selector.ts +1121 -0
- package/src/overlays/usage-dashboard.ts +629 -0
- package/src/overlays/usage-display.ts +45 -0
- package/src/overlays/usage-row.ts +118 -0
- package/src/prompt/attachment-chips.ts +201 -0
- package/src/prompt/composer-attachments.ts +280 -0
- package/src/prompt/composer-cache.ts +304 -0
- package/src/prompt/composer.ts +934 -0
- package/src/prompt/custom-editor.ts +1392 -0
- package/src/prompt/data/emojis.json +1 -0
- package/src/prompt/editor-top-gap.ts +28 -0
- package/src/prompt/emoji-autocomplete.ts +285 -0
- package/src/prompt/github-ref-autocomplete.ts +75 -0
- package/src/prompt/gradient-highlight.ts +101 -0
- package/src/prompt/image-format.ts +28 -0
- package/src/prompt/image-references.ts +102 -0
- package/src/prompt/interactive-context-helpers.ts +126 -0
- package/src/prompt/internal-url-autocomplete.ts +155 -0
- package/src/prompt/macos-spelling.ts +456 -0
- package/src/prompt/magic-keyword-boundary.ts +23 -0
- package/src/prompt/magic-keywords.ts +42 -0
- package/src/prompt/markdown-prose.ts +245 -0
- package/src/prompt/model-mention-autocomplete.ts +94 -0
- package/src/prompt/model-mention-syntax.ts +41 -0
- package/src/prompt/orchestrate.ts +26 -0
- package/src/prompt/prompt-action-autocomplete.ts +337 -0
- package/src/prompt/queue-input.ts +132 -0
- package/src/prompt/skill-tokens.ts +36 -0
- package/src/prompt/tips.txt +27 -0
- package/src/prompt/tools-markdown.ts +29 -0
- package/src/prompt/ultrathink.ts +26 -0
- package/src/prompt/usage-amounts.ts +99 -0
- package/src/prompt/video.ts +47 -0
- package/src/prompt/welcome.ts +605 -0
- package/src/prompt/workflow.ts +26 -0
- package/src/render/code-cell.ts +244 -0
- package/src/render/file-list.ts +58 -0
- package/src/render/hyperlink.ts +192 -0
- package/src/render/index.ts +17 -0
- package/src/render/output-block.ts +267 -0
- package/src/render/output-pane.ts +299 -0
- package/src/render/render-utils.ts +1182 -0
- package/src/render/sixel.ts +69 -0
- package/src/render/status-line.ts +55 -0
- package/src/render/tool-card.ts +272 -0
- package/src/render/tree-list.ts +188 -0
- package/src/render/types.ts +19 -0
- package/src/render/utils.ts +109 -0
- package/src/render/width-aware-text.ts +60 -0
- package/src/setup/lazy.ts +17 -0
- package/src/setup/scenes/composer.ts +127 -0
- package/src/setup/scenes/glyph.ts +120 -0
- package/src/setup/scenes/model.ts +138 -0
- package/src/setup/scenes/outro.ts +24 -0
- package/src/setup/scenes/providers.ts +107 -0
- package/src/setup/scenes/sign-in.ts +373 -0
- package/src/setup/scenes/splash.ts +192 -0
- package/src/setup/scenes/theme.ts +343 -0
- package/src/setup/scenes/types.ts +111 -0
- package/src/setup/scenes/web-search.ts +165 -0
- package/src/setup/setup-version.ts +11 -0
- package/src/setup/startup-splash.ts +107 -0
- package/src/setup/wizard-overlay.ts +319 -0
- package/src/setup/wizard.ts +101 -0
- package/src/status-line/component.ts +3003 -0
- package/src/status-line/context-usage.ts +688 -0
- package/src/status-line/footer.ts +323 -0
- package/src/status-line/git-utils.ts +42 -0
- package/src/status-line/host.ts +97 -0
- package/src/status-line/index.ts +6 -0
- package/src/status-line/loop.ts +28 -0
- package/src/status-line/metrics.ts +94 -0
- package/src/status-line/presets.ts +107 -0
- package/src/status-line/schema.ts +57 -0
- package/src/status-line/segments.ts +934 -0
- package/src/status-line/separators.ts +55 -0
- package/src/status-line/types.ts +227 -0
- package/src/stdin-buffer.ts +4 -2
- package/src/theme/color.ts +95 -0
- package/src/theme/dark.json +95 -0
- package/src/theme/defaults/alabaster.json +93 -0
- package/src/theme/defaults/amethyst.json +96 -0
- package/src/theme/defaults/anthracite.json +93 -0
- package/src/theme/defaults/basalt.json +91 -0
- package/src/theme/defaults/birch.json +95 -0
- package/src/theme/defaults/dark-abyss.json +91 -0
- package/src/theme/defaults/dark-arctic.json +104 -0
- package/src/theme/defaults/dark-aurora.json +95 -0
- package/src/theme/defaults/dark-catppuccin.json +107 -0
- package/src/theme/defaults/dark-cavern.json +91 -0
- package/src/theme/defaults/dark-celestial.json +106 -0
- package/src/theme/defaults/dark-copper.json +95 -0
- package/src/theme/defaults/dark-cosmos.json +90 -0
- package/src/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/theme/defaults/dark-dracula.json +98 -0
- package/src/theme/defaults/dark-eclipse.json +91 -0
- package/src/theme/defaults/dark-ember.json +95 -0
- package/src/theme/defaults/dark-equinox.json +90 -0
- package/src/theme/defaults/dark-forest.json +96 -0
- package/src/theme/defaults/dark-github.json +105 -0
- package/src/theme/defaults/dark-gruvbox.json +112 -0
- package/src/theme/defaults/dark-lavender.json +95 -0
- package/src/theme/defaults/dark-lunar.json +89 -0
- package/src/theme/defaults/dark-midnight.json +95 -0
- package/src/theme/defaults/dark-monochrome.json +94 -0
- package/src/theme/defaults/dark-monokai.json +98 -0
- package/src/theme/defaults/dark-nebula.json +90 -0
- package/src/theme/defaults/dark-nord.json +98 -0
- package/src/theme/defaults/dark-ocean.json +101 -0
- package/src/theme/defaults/dark-one.json +100 -0
- package/src/theme/defaults/dark-poimandres.json +115 -0
- package/src/theme/defaults/dark-rainforest.json +91 -0
- package/src/theme/defaults/dark-reef.json +91 -0
- package/src/theme/defaults/dark-retro.json +92 -0
- package/src/theme/defaults/dark-rose-pine.json +96 -0
- package/src/theme/defaults/dark-sakura.json +95 -0
- package/src/theme/defaults/dark-slate.json +95 -0
- package/src/theme/defaults/dark-solarized.json +97 -0
- package/src/theme/defaults/dark-solstice.json +90 -0
- package/src/theme/defaults/dark-starfall.json +91 -0
- package/src/theme/defaults/dark-sunset.json +99 -0
- package/src/theme/defaults/dark-swamp.json +90 -0
- package/src/theme/defaults/dark-synthwave.json +103 -0
- package/src/theme/defaults/dark-taiga.json +91 -0
- package/src/theme/defaults/dark-terminal.json +95 -0
- package/src/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/theme/defaults/dark-tundra.json +91 -0
- package/src/theme/defaults/dark-twilight.json +91 -0
- package/src/theme/defaults/dark-volcanic.json +91 -0
- package/src/theme/defaults/graphite.json +92 -0
- package/src/theme/defaults/index.ts +201 -0
- package/src/theme/defaults/light-arctic.json +107 -0
- package/src/theme/defaults/light-aurora-day.json +91 -0
- package/src/theme/defaults/light-canyon.json +91 -0
- package/src/theme/defaults/light-catppuccin.json +106 -0
- package/src/theme/defaults/light-cirrus.json +90 -0
- package/src/theme/defaults/light-coral.json +95 -0
- package/src/theme/defaults/light-cyberpunk.json +96 -0
- package/src/theme/defaults/light-dawn.json +90 -0
- package/src/theme/defaults/light-dunes.json +91 -0
- package/src/theme/defaults/light-eucalyptus.json +95 -0
- package/src/theme/defaults/light-forest.json +100 -0
- package/src/theme/defaults/light-frost.json +95 -0
- package/src/theme/defaults/light-github.json +115 -0
- package/src/theme/defaults/light-glacier.json +91 -0
- package/src/theme/defaults/light-gruvbox.json +108 -0
- package/src/theme/defaults/light-haze.json +90 -0
- package/src/theme/defaults/light-honeycomb.json +95 -0
- package/src/theme/defaults/light-lagoon.json +91 -0
- package/src/theme/defaults/light-lavender.json +95 -0
- package/src/theme/defaults/light-meadow.json +91 -0
- package/src/theme/defaults/light-mint.json +95 -0
- package/src/theme/defaults/light-monochrome.json +101 -0
- package/src/theme/defaults/light-ocean.json +99 -0
- package/src/theme/defaults/light-one.json +99 -0
- package/src/theme/defaults/light-opal.json +91 -0
- package/src/theme/defaults/light-orchard.json +91 -0
- package/src/theme/defaults/light-paper.json +95 -0
- package/src/theme/defaults/light-poimandres.json +115 -0
- package/src/theme/defaults/light-prism.json +90 -0
- package/src/theme/defaults/light-retro.json +98 -0
- package/src/theme/defaults/light-sand.json +95 -0
- package/src/theme/defaults/light-savanna.json +91 -0
- package/src/theme/defaults/light-solarized.json +102 -0
- package/src/theme/defaults/light-soleil.json +90 -0
- package/src/theme/defaults/light-sunset.json +99 -0
- package/src/theme/defaults/light-synthwave.json +98 -0
- package/src/theme/defaults/light-tokyo-night.json +111 -0
- package/src/theme/defaults/light-wetland.json +91 -0
- package/src/theme/defaults/light-zenith.json +89 -0
- package/src/theme/defaults/limestone.json +94 -0
- package/src/theme/defaults/mahogany.json +97 -0
- package/src/theme/defaults/marble.json +93 -0
- package/src/theme/defaults/obsidian.json +91 -0
- package/src/theme/defaults/onyx.json +91 -0
- package/src/theme/defaults/pearl.json +93 -0
- package/src/theme/defaults/porcelain.json +91 -0
- package/src/theme/defaults/quartz.json +96 -0
- package/src/theme/defaults/sandstone.json +95 -0
- package/src/theme/defaults/titanium.json +90 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/light.json +93 -0
- package/src/theme/loader.ts +200 -0
- package/src/theme/mermaid-cache.ts +92 -0
- package/src/theme/schema-validation.ts +119 -0
- package/src/theme/schema.ts +168 -0
- package/src/theme/session-color.ts +253 -0
- package/src/theme/shimmer.ts +307 -0
- package/src/theme/symbols.ts +1433 -0
- package/src/theme/theme-class.ts +787 -0
- package/src/theme/theme-schema.json +463 -0
- package/src/theme/theme.ts +810 -0
- package/src/theme/tui-adapters.ts +339 -0
- package/src/thinking.ts +380 -0
- package/src/tools/agent-tree.ts +128 -0
- package/src/tools/ask.ts +482 -0
- package/src/tools/ast-edit.ts +210 -0
- package/src/tools/ast-grep.ts +171 -0
- package/src/tools/autoresearch.ts +287 -0
- package/src/tools/bash-interactive.ts +295 -0
- package/src/tools/bash.ts +468 -0
- package/src/tools/conflict-detect.ts +77 -0
- package/src/tools/debug.ts +167 -0
- package/src/tools/default-renderer.ts +172 -0
- package/src/tools/edit.ts +1258 -0
- package/src/tools/eval-format/code-mode-declarations.ts +61 -0
- package/src/tools/eval-format/index.ts +16 -0
- package/src/tools/eval-format/javascript.ts +952 -0
- package/src/tools/eval-format/python.ts +544 -0
- package/src/tools/eval.ts +835 -0
- package/src/tools/fetch.ts +226 -0
- package/src/tools/gh-format.ts +18 -0
- package/src/tools/github.ts +686 -0
- package/src/tools/glob.ts +213 -0
- package/src/tools/goal.ts +153 -0
- package/src/tools/grep.ts +385 -0
- package/src/tools/grouped-file-output.ts +211 -0
- package/src/tools/hashline-format.ts +59 -0
- package/src/tools/hub.ts +1488 -0
- package/src/tools/index.ts +71 -0
- package/src/tools/json-tree.ts +388 -0
- package/src/tools/line-ranges.ts +98 -0
- package/src/tools/list-limit.ts +43 -0
- package/src/tools/lsp.ts +702 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/mcp.ts +309 -0
- package/src/tools/memory.ts +220 -0
- package/src/tools/output-meta.ts +367 -0
- package/src/tools/read.ts +628 -0
- package/src/tools/renderer.ts +107 -0
- package/src/tools/report-tool-issue.ts +13 -0
- package/src/tools/resolve.ts +139 -0
- package/src/tools/streaming-output.ts +1576 -0
- package/src/tools/subprocess.ts +39 -0
- package/src/tools/task-repair-args.ts +118 -0
- package/src/tools/task-yield-assembly.ts +147 -0
- package/src/tools/task.ts +2110 -0
- package/src/tools/terminal-output.ts +153 -0
- package/src/tools/think.ts +28 -0
- package/src/tools/todo.ts +580 -0
- package/src/tools/tool-errors.ts +15 -0
- package/src/tools/vibe.ts +420 -0
- package/src/tools/web-search.ts +409 -0
- package/src/tools/write.ts +511 -0
- package/src/tools/xd-url.ts +28 -0
- package/src/tools/xdev.ts +201 -0
- package/src/tui.ts +57 -4
- package/src/utils.ts +8 -0
|
@@ -0,0 +1,1258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit tool renderer.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type EditInspection, editInspect } from "@oh-my-pi/pi-natives";
|
|
6
|
+
import type { Component } from "../tui";
|
|
7
|
+
import { sliceWithWidth, visibleWidth, wrapTextWithAnsi } from "../utils";
|
|
8
|
+
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
9
|
+
import type { RenderResultOptions, ToolRenderer } from "./renderer";
|
|
10
|
+
import type { FileDiagnosticsResult } from "./lsp";
|
|
11
|
+
import { renderDiff as renderDiffColored } from "../chrome/diff";
|
|
12
|
+
import { getLanguageFromPath } from "../lang-from-path";
|
|
13
|
+
import type { Theme } from "../theme/theme";
|
|
14
|
+
import type { OutputMeta } from "./output-meta";
|
|
15
|
+
import {
|
|
16
|
+
cachedRenderedString,
|
|
17
|
+
cappedHeadLines,
|
|
18
|
+
createRenderedStringCache,
|
|
19
|
+
formatDiagnostics,
|
|
20
|
+
formatExpandHint,
|
|
21
|
+
formatStatusIcon,
|
|
22
|
+
getDiffStats,
|
|
23
|
+
invalidateRenderedStringCache,
|
|
24
|
+
PREVIEW_LIMITS,
|
|
25
|
+
previewWindowRows,
|
|
26
|
+
type RenderedStringCache,
|
|
27
|
+
replaceTabs,
|
|
28
|
+
shortenPath,
|
|
29
|
+
truncateDiffByHunk,
|
|
30
|
+
} from "../render/render-utils";
|
|
31
|
+
import type { ToolActivityContext, ToolActivitySummary } from "./renderer";
|
|
32
|
+
import { fileHyperlink, Hasher, type RenderCache, renderStatusLine, truncateToWidth, WidthAwareText } from "../render";
|
|
33
|
+
import { framedToolCard } from "../render/tool-card";
|
|
34
|
+
import { HL_FILE_PREFIX, HL_FILE_SUFFIX, HL_MOVE_KEYWORD, HL_REM_KEYWORD } from "./hashline-format";
|
|
35
|
+
|
|
36
|
+
/** Edit payload syntax selected by the caller. */
|
|
37
|
+
export type EditMode = "replace" | "patch" | "hashline" | "apply_patch" | "sloppy";
|
|
38
|
+
|
|
39
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
40
|
+
// Tool Details Types
|
|
41
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
42
|
+
|
|
43
|
+
/** Filesystem operation represented by an edit. */
|
|
44
|
+
export type Operation = "create" | "delete" | "update";
|
|
45
|
+
|
|
46
|
+
/** Computed streaming diff or preview error for one file. */
|
|
47
|
+
export interface PerFileDiffPreview {
|
|
48
|
+
path: string;
|
|
49
|
+
diff?: string;
|
|
50
|
+
firstChangedLine?: number;
|
|
51
|
+
error?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type EditDiffPreview =
|
|
55
|
+
| { diff: string; firstChangedLine?: number }
|
|
56
|
+
| { error: string; diff?: undefined; firstChangedLine?: undefined };
|
|
57
|
+
|
|
58
|
+
/** Result metadata for one edited file. */
|
|
59
|
+
export interface EditToolPerFileResult {
|
|
60
|
+
path: string;
|
|
61
|
+
diff: string;
|
|
62
|
+
firstChangedLine?: number;
|
|
63
|
+
diagnostics?: FileDiagnosticsResult;
|
|
64
|
+
op?: Operation;
|
|
65
|
+
move?: string;
|
|
66
|
+
isError?: boolean;
|
|
67
|
+
errorText?: string;
|
|
68
|
+
/** TUI-friendly error text. When present, rendered to the user instead of `errorText`.
|
|
69
|
+
* Set when the underlying error carries a `displayMessage` (e.g. {@link HashlineMismatchError}). */
|
|
70
|
+
displayErrorText?: string;
|
|
71
|
+
meta?: OutputMeta;
|
|
72
|
+
/** Source-of-truth content before the edit; `undefined` for create operations. */
|
|
73
|
+
oldText?: string;
|
|
74
|
+
/** Source-of-truth content after the edit; `undefined` for delete operations. */
|
|
75
|
+
newText?: string;
|
|
76
|
+
/** True when {@link pruneOversizedEditSnapshots} dropped `oldText`/`newText` from this entry. Aggregators check this to suppress misleading combined snapshots when at least one entry of a multi-entry single-path edit was pruned. */
|
|
77
|
+
snapshotsPruned?: boolean;
|
|
78
|
+
/** Pre-move source path; set only when the edit moved/renamed the file. The header renders `sourcePath → path`. */
|
|
79
|
+
sourcePath?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Diff, diagnostics, and snapshots returned by an edit. */
|
|
83
|
+
export interface EditToolDetails {
|
|
84
|
+
/** Unified diff of the changes made */
|
|
85
|
+
diff: string;
|
|
86
|
+
/** Line number of the first change in the new file (for editor navigation) */
|
|
87
|
+
firstChangedLine?: number;
|
|
88
|
+
/** Diagnostic result (if available) */
|
|
89
|
+
diagnostics?: FileDiagnosticsResult;
|
|
90
|
+
/** Operation type (patch mode only) */
|
|
91
|
+
op?: Operation;
|
|
92
|
+
/** New path after move/rename (patch mode only) */
|
|
93
|
+
move?: string;
|
|
94
|
+
/** Structured output metadata */
|
|
95
|
+
meta?: OutputMeta;
|
|
96
|
+
/** Per-file results (multi-file edits) */
|
|
97
|
+
perFileResults?: EditToolPerFileResult[];
|
|
98
|
+
/** Absolute file path for single-file edit results. Required by ACP diff metadata consumers. */
|
|
99
|
+
path?: string;
|
|
100
|
+
/** Source-of-truth content before the edit; `undefined` for create operations. */
|
|
101
|
+
oldText?: string;
|
|
102
|
+
/** Source-of-truth content after the edit; `undefined` for delete operations. */
|
|
103
|
+
newText?: string;
|
|
104
|
+
/** True when {@link pruneOversizedEditSnapshots} dropped `oldText`/`newText` from this entry. Aggregators check this to suppress misleading combined snapshots when at least one entry of a multi-entry single-path edit was pruned. */
|
|
105
|
+
snapshotsPruned?: boolean;
|
|
106
|
+
/** Pre-move source path; set only when the edit moved/renamed the file. The header renders `sourcePath → path`. */
|
|
107
|
+
sourcePath?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
111
|
+
// TUI Renderer
|
|
112
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
113
|
+
|
|
114
|
+
/** Memoized native inspection of the streamed payload, tagged onto the args it came from. */
|
|
115
|
+
const kInspectedInput = Symbol("edit.inspectedInput");
|
|
116
|
+
|
|
117
|
+
type InspectedInput = { mode: EditMode; input: string } & (
|
|
118
|
+
| { entries: InspectedInputEntry[]; error?: undefined }
|
|
119
|
+
| { entries?: undefined; error: unknown }
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
interface EditRenderArgs {
|
|
123
|
+
path?: unknown;
|
|
124
|
+
file_path?: unknown;
|
|
125
|
+
oldText?: string;
|
|
126
|
+
newText?: string;
|
|
127
|
+
patch?: string;
|
|
128
|
+
input?: string;
|
|
129
|
+
_input?: string;
|
|
130
|
+
replace_all?: boolean;
|
|
131
|
+
// Patch mode fields
|
|
132
|
+
op?: Operation;
|
|
133
|
+
rename?: unknown;
|
|
134
|
+
diff?: string;
|
|
135
|
+
/**
|
|
136
|
+
* Computed preview diff (used when tool args don't include a diff, e.g. hashline mode).
|
|
137
|
+
*/
|
|
138
|
+
previewDiff?: string;
|
|
139
|
+
__partialJson?: string;
|
|
140
|
+
// Hashline mode fields
|
|
141
|
+
edits?: EditRenderEntry[];
|
|
142
|
+
[kInspectedInput]?: InspectedInput;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
type EditRenderEntry = {
|
|
146
|
+
path?: unknown;
|
|
147
|
+
rename?: unknown;
|
|
148
|
+
move?: unknown;
|
|
149
|
+
op?: Operation;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
interface HashlineInputEntry {
|
|
153
|
+
path: string;
|
|
154
|
+
op?: Operation;
|
|
155
|
+
rename?: string;
|
|
156
|
+
/** A PUT/CUT line edit precedes the file op — keeps a move framed. */
|
|
157
|
+
hasLineEdits?: boolean;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
interface HashlineInputRenderSummary {
|
|
161
|
+
entries: HashlineInputEntry[];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface InspectedInputEntry {
|
|
165
|
+
path: string;
|
|
166
|
+
op?: Operation;
|
|
167
|
+
rename?: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
interface ApplyPatchRenderSummary {
|
|
171
|
+
entries: InspectedInputEntry[];
|
|
172
|
+
error?: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Extended context for edit tool rendering */
|
|
176
|
+
export interface EditRenderContext {
|
|
177
|
+
/** Edit mode resolved by the caller; lets the renderer dispatch without shape-sniffing */
|
|
178
|
+
editMode?: EditMode;
|
|
179
|
+
/** Pre-computed diff preview (computed before tool executes) */
|
|
180
|
+
editDiffPreview?: EditDiffPreview;
|
|
181
|
+
/** Multi-file streaming diff preview (edits spanning several files) */
|
|
182
|
+
perFileDiffPreview?: PerFileDiffPreview[];
|
|
183
|
+
/** Raw in-flight edit text shown while a computed diff preview is unavailable */
|
|
184
|
+
editStreamingFallback?: string;
|
|
185
|
+
/** Function to render diff text with syntax highlighting */
|
|
186
|
+
renderDiff?: (diffText: string, options?: { filePath?: string }) => string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Lazily grown per-file preview cache slots: the file count of a streaming
|
|
191
|
+
* multi-file patch is discovered mid-stream, so a fixed-size array would
|
|
192
|
+
* silently bypass caching for late files.
|
|
193
|
+
*/
|
|
194
|
+
function previewCacheAt(caches: RenderedStringCache[] | undefined, index: number): RenderedStringCache | undefined {
|
|
195
|
+
if (!caches) return undefined;
|
|
196
|
+
let cache = caches[index];
|
|
197
|
+
if (cache === undefined) {
|
|
198
|
+
cache = createRenderedStringCache();
|
|
199
|
+
caches[index] = cache;
|
|
200
|
+
}
|
|
201
|
+
return cache;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const CALL_TEXT_PREVIEW_LINES = 6;
|
|
205
|
+
const CALL_TEXT_PREVIEW_WIDTH = 80;
|
|
206
|
+
|
|
207
|
+
/** Render replacement text until a computed diff preview is available. */
|
|
208
|
+
export function renderStreamingFallback(editMode: EditMode, args: unknown, theme: Theme): string {
|
|
209
|
+
if (editMode !== "replace" || !args || typeof args !== "object") return "";
|
|
210
|
+
const record = args as Record<string, unknown>;
|
|
211
|
+
const newString =
|
|
212
|
+
typeof record.new_string === "string"
|
|
213
|
+
? record.new_string
|
|
214
|
+
: typeof record.newText === "string"
|
|
215
|
+
? record.newText
|
|
216
|
+
: undefined;
|
|
217
|
+
return newString ? renderPlainTextPreview(newString, theme) : "";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Extract file path from an edit entry. */
|
|
221
|
+
function filePathFromEditEntry(p: unknown): string | undefined {
|
|
222
|
+
if (typeof p !== "string") {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
return p;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function decodePartialJsonStringFragment(fragment: string): string {
|
|
229
|
+
// Trim a trailing partial escape so JSON.parse sees a well-formed string.
|
|
230
|
+
let text = fragment.replace(/\\u[0-9a-fA-F]{0,3}$/, "");
|
|
231
|
+
const trailingBackslashes = text.match(/\\+$/)?.[0].length ?? 0;
|
|
232
|
+
if (trailingBackslashes % 2 === 1) text = text.slice(0, -1);
|
|
233
|
+
try {
|
|
234
|
+
return JSON.parse(`"${text}"`) as string;
|
|
235
|
+
} catch {
|
|
236
|
+
// Streaming fragment isn't a valid JSON string yet; surface it raw rather
|
|
237
|
+
// than ad-hoc unescaping that mishandles surrogates and partial escapes.
|
|
238
|
+
return text;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function extractPartialJsonString(partialJson: string | undefined, key: string): string | undefined {
|
|
243
|
+
if (!partialJson) return undefined;
|
|
244
|
+
const pattern = new RegExp(`"${key}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)`, "u");
|
|
245
|
+
const match = pattern.exec(partialJson);
|
|
246
|
+
if (!match) return undefined;
|
|
247
|
+
return decodePartialJsonStringFragment(match[1]);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function getPartialJsonEditPath(args: EditRenderArgs): string | undefined {
|
|
251
|
+
return filePathFromEditEntry(extractPartialJsonString(args.__partialJson, "path"));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Count distinct file paths in an edits array. */
|
|
255
|
+
function countEditFiles(edits: EditRenderEntry[]): number {
|
|
256
|
+
return new Set(edits.map(edit => filePathFromEditEntry(edit.path)).filter(Boolean)).size;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function getOperationTitle(op: Operation | undefined): string {
|
|
260
|
+
return op === "create" ? "Create" : op === "delete" ? "Delete" : "Edit";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
interface EditPathDisplayOptions {
|
|
264
|
+
rename?: string;
|
|
265
|
+
firstChangedLine?: number;
|
|
266
|
+
linkPath?: string;
|
|
267
|
+
renameLinkPath?: string;
|
|
268
|
+
maxPathWidth?: number;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function truncateEditTitlePath(displayPath: string, maxWidth: number | undefined): string {
|
|
272
|
+
if (maxWidth === undefined) return displayPath;
|
|
273
|
+
const width = visibleWidth(displayPath);
|
|
274
|
+
const safeMaxWidth = Math.max(0, Math.floor(maxWidth));
|
|
275
|
+
if (width <= safeMaxWidth) return displayPath;
|
|
276
|
+
|
|
277
|
+
const contentWidth = safeMaxWidth - 1;
|
|
278
|
+
if (contentWidth <= 0) return "…";
|
|
279
|
+
|
|
280
|
+
const headWidth = Math.floor(contentWidth / 2);
|
|
281
|
+
const tailWidth = contentWidth - headWidth;
|
|
282
|
+
const head = sliceWithWidth(displayPath, 0, headWidth, true).text;
|
|
283
|
+
const tail = sliceWithWidth(displayPath, Math.max(0, width - tailWidth), tailWidth, true).text;
|
|
284
|
+
return `${head}…${tail}`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function formatEditTitlePath(pathValue: string, maxWidth?: number): string {
|
|
288
|
+
return truncateEditTitlePath(replaceTabs(shortenPath(pathValue)), maxWidth);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function formatEditPathDisplay(
|
|
292
|
+
rawPath: string,
|
|
293
|
+
uiTheme: Theme,
|
|
294
|
+
options?: EditPathDisplayOptions,
|
|
295
|
+
): { text: string; pathWidth: number } {
|
|
296
|
+
// `rawPath`/`rename` are shown (cwd-relative) but the OSC 8 link targets the
|
|
297
|
+
// absolute path when known — a relative `rawPath` would otherwise yield a
|
|
298
|
+
// `file:///rel` URI that resolves against filesystem root instead of cwd.
|
|
299
|
+
const linkTarget = options?.linkPath || rawPath;
|
|
300
|
+
const lineLink = options?.firstChangedLine ? { line: options.firstChangedLine } : undefined;
|
|
301
|
+
const primaryDisplay = rawPath ? formatEditTitlePath(rawPath, options?.maxPathWidth) : "…";
|
|
302
|
+
let pathDisplay = rawPath
|
|
303
|
+
? fileHyperlink(linkTarget, uiTheme.fg("accent", primaryDisplay), lineLink)
|
|
304
|
+
: uiTheme.fg("toolOutput", primaryDisplay);
|
|
305
|
+
let pathWidth = visibleWidth(primaryDisplay);
|
|
306
|
+
|
|
307
|
+
if (options?.rename) {
|
|
308
|
+
const renameTarget = options.renameLinkPath || options.rename;
|
|
309
|
+
const renameDisplay = formatEditTitlePath(options.rename, options.maxPathWidth);
|
|
310
|
+
pathDisplay += ` ${uiTheme.fg("dim", "→")} ${fileHyperlink(renameTarget, uiTheme.fg("accent", renameDisplay))}`;
|
|
311
|
+
pathWidth += visibleWidth(renameDisplay);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return { text: pathDisplay, pathWidth };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function formatEditDescription(
|
|
318
|
+
rawPath: string,
|
|
319
|
+
uiTheme: Theme,
|
|
320
|
+
options?: EditPathDisplayOptions,
|
|
321
|
+
): { language: string; description: string; pathWidth: number } {
|
|
322
|
+
const language = getLanguageFromPath(rawPath) ?? "text";
|
|
323
|
+
const icon = uiTheme.fg("muted", uiTheme.getLangIcon(language));
|
|
324
|
+
const pathDisplay = formatEditPathDisplay(rawPath, uiTheme, options);
|
|
325
|
+
return {
|
|
326
|
+
language,
|
|
327
|
+
description: `${icon} ${pathDisplay.text}`,
|
|
328
|
+
pathWidth: pathDisplay.pathWidth,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function editHeaderLabelBudget(width: number, uiTheme: Theme): number {
|
|
333
|
+
const leftGlyphs = `${uiTheme.boxRound.topLeft}${uiTheme.boxRound.horizontal.repeat(3)}`;
|
|
334
|
+
return Math.max(0, width - visibleWidth(leftGlyphs) - visibleWidth(uiTheme.boxRound.topRight) - 2);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function renderEditHeader(
|
|
338
|
+
width: number,
|
|
339
|
+
uiTheme: Theme,
|
|
340
|
+
options: {
|
|
341
|
+
icon?: "pending" | "success" | "error";
|
|
342
|
+
iconOverride?: string;
|
|
343
|
+
op?: Operation;
|
|
344
|
+
rawPath: string;
|
|
345
|
+
rename?: string;
|
|
346
|
+
firstChangedLine?: number;
|
|
347
|
+
linkPath?: string;
|
|
348
|
+
statsSuffix?: string;
|
|
349
|
+
extraSuffix?: string;
|
|
350
|
+
title?: string;
|
|
351
|
+
},
|
|
352
|
+
): string {
|
|
353
|
+
const title = options.title ?? getOperationTitle(options.op);
|
|
354
|
+
const descriptionOptions: EditPathDisplayOptions = {
|
|
355
|
+
rename: options.rename,
|
|
356
|
+
firstChangedLine: options.firstChangedLine,
|
|
357
|
+
linkPath: options.linkPath,
|
|
358
|
+
};
|
|
359
|
+
const formatted = formatEditDescription(options.rawPath, uiTheme, descriptionOptions);
|
|
360
|
+
const suffix = `${options.statsSuffix ?? ""}${options.extraSuffix ?? ""}`;
|
|
361
|
+
const buildHeader = (description: string): string =>
|
|
362
|
+
renderStatusLine(
|
|
363
|
+
{
|
|
364
|
+
icon: options.icon,
|
|
365
|
+
iconOverride: options.iconOverride,
|
|
366
|
+
title,
|
|
367
|
+
description,
|
|
368
|
+
},
|
|
369
|
+
uiTheme,
|
|
370
|
+
) + suffix;
|
|
371
|
+
|
|
372
|
+
const header = buildHeader(formatted.description);
|
|
373
|
+
const overflow = visibleWidth(header) - editHeaderLabelBudget(width, uiTheme);
|
|
374
|
+
if (overflow <= 0 || formatted.pathWidth <= 1) return header;
|
|
375
|
+
|
|
376
|
+
const pathCount = Math.max(1, (options.rawPath ? 1 : 0) + (options.rename ? 1 : 0));
|
|
377
|
+
const fittedPathWidth = Math.max(1, Math.floor((formatted.pathWidth - overflow) / pathCount));
|
|
378
|
+
const fitted = formatEditDescription(options.rawPath, uiTheme, {
|
|
379
|
+
...descriptionOptions,
|
|
380
|
+
maxPathWidth: fittedPathWidth,
|
|
381
|
+
});
|
|
382
|
+
return buildHeader(fitted.description);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Inline status row for delete / move-only edits — they carry no diff, so they
|
|
387
|
+
* render as a single line instead of an empty framed container. The completed
|
|
388
|
+
* result uses the eraser/move glyph; a still-streaming call uses the shared
|
|
389
|
+
* pending hourglass like every other tool.
|
|
390
|
+
*/
|
|
391
|
+
function renderInlineEditRow(
|
|
392
|
+
uiTheme: Theme,
|
|
393
|
+
opts: { op?: Operation; rename?: string; rawPath: string; linkPath?: string; pending: boolean },
|
|
394
|
+
): Component {
|
|
395
|
+
const isDelete = opts.op === "delete";
|
|
396
|
+
return new WidthAwareText(
|
|
397
|
+
width =>
|
|
398
|
+
renderEditHeader(width, uiTheme, {
|
|
399
|
+
icon: opts.pending ? "pending" : undefined,
|
|
400
|
+
iconOverride: opts.pending
|
|
401
|
+
? undefined
|
|
402
|
+
: uiTheme.styledSymbol(isDelete ? "tool.delete" : "tool.move", "accent"),
|
|
403
|
+
op: opts.op,
|
|
404
|
+
title: isDelete ? "Delete" : "Move",
|
|
405
|
+
rawPath: opts.rawPath,
|
|
406
|
+
rename: opts.rename,
|
|
407
|
+
linkPath: opts.linkPath,
|
|
408
|
+
}),
|
|
409
|
+
0,
|
|
410
|
+
0,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Whether a streaming edit call carries any payload worth boxing (a diff
|
|
416
|
+
* preview, replacement text, or a non-empty edits array). Used to keep a
|
|
417
|
+
* move-with-edits framed while a payload-less move/delete folds to an inline
|
|
418
|
+
* row — gated on args, not the async preview, so it can't flash inline before
|
|
419
|
+
* the diff arrives.
|
|
420
|
+
*/
|
|
421
|
+
function hasEditCallPayload(args: EditRenderArgs, renderContext: EditRenderContext | undefined): boolean {
|
|
422
|
+
const multi = renderContext?.perFileDiffPreview;
|
|
423
|
+
if (multi && multi.length > 1 && multi.some(p => p.diff || p.error)) return true;
|
|
424
|
+
if (args.previewDiff || args.diff || args.newText || args.patch) return true;
|
|
425
|
+
if (Array.isArray(args.edits) && args.edits.length > 0) return true;
|
|
426
|
+
if (renderContext?.editStreamingFallback) return true;
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function renderPlainTextPreview(text: string, uiTheme: Theme, _filePath?: string): string {
|
|
431
|
+
const previewLines = cappedHeadLines(sanitizeText(text).split("\n"), CALL_TEXT_PREVIEW_LINES);
|
|
432
|
+
let preview = "\n\n";
|
|
433
|
+
for (const line of previewLines.lines) {
|
|
434
|
+
preview += `${uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line), CALL_TEXT_PREVIEW_WIDTH))}\n`;
|
|
435
|
+
}
|
|
436
|
+
if (previewLines.hidden > 0) {
|
|
437
|
+
preview += uiTheme.fg("dim", `… ${previewLines.hidden} more lines`);
|
|
438
|
+
}
|
|
439
|
+
return preview.trimEnd();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
interface StreamingDiffTail {
|
|
443
|
+
content: string;
|
|
444
|
+
hidden: boolean;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Select the trailing physical lines that fit the live preview budget.
|
|
449
|
+
*
|
|
450
|
+
* Walk backward from the end instead of splitting the complete diff. This keeps
|
|
451
|
+
* allocation and scanning proportional to the visible suffix. One physical line
|
|
452
|
+
* may exceed the budget, but it is still kept so the newest change is visible.
|
|
453
|
+
*/
|
|
454
|
+
function sliceStreamingDiffTail(diff: string, innerWidth: number, budget: number): StreamingDiffTail {
|
|
455
|
+
let end = diff.length;
|
|
456
|
+
while (end > 0 && diff.charCodeAt(end - 1) === 10) end--;
|
|
457
|
+
if (end === 0) return { content: "", hidden: false };
|
|
458
|
+
|
|
459
|
+
const rowLimit = Math.max(1, budget);
|
|
460
|
+
let start = end;
|
|
461
|
+
let cursor = end;
|
|
462
|
+
let visualRows = 0;
|
|
463
|
+
while (cursor >= 0) {
|
|
464
|
+
const newline = cursor > 0 ? diff.lastIndexOf("\n", cursor - 1) : -1;
|
|
465
|
+
const lineStart = newline + 1;
|
|
466
|
+
const lineRows = Math.max(1, wrapTextWithAnsi(replaceTabs(diff.slice(lineStart, cursor)), innerWidth).length);
|
|
467
|
+
if (visualRows > 0 && visualRows + lineRows > rowLimit) break;
|
|
468
|
+
visualRows += lineRows;
|
|
469
|
+
start = lineStart;
|
|
470
|
+
if (newline < 0) break;
|
|
471
|
+
cursor = newline;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return { content: diff.slice(start, end), hidden: start > 0 };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function formatStreamingDiff(
|
|
478
|
+
diff: string,
|
|
479
|
+
rawPath: string,
|
|
480
|
+
width: number,
|
|
481
|
+
uiTheme: Theme,
|
|
482
|
+
expanded: boolean,
|
|
483
|
+
label = "streaming",
|
|
484
|
+
spinnerFrame?: number,
|
|
485
|
+
cache?: RenderedStringCache,
|
|
486
|
+
): string {
|
|
487
|
+
if (!diff) return "";
|
|
488
|
+
// Clamp the tail to the viewport so a tall or fast-growing diff cannot
|
|
489
|
+
// outgrow the live window. Otherwise its mutating rows scroll above the
|
|
490
|
+
// native-scrollback commit boundary mid-stream and freeze into immutable
|
|
491
|
+
// history as a stale preview snapshot; the finalize repair then recommits
|
|
492
|
+
// the final render below it — a duplicated block on the tape. Collapsed
|
|
493
|
+
// gets a short fixed tail; expanded widens it to the viewport-sized window,
|
|
494
|
+
// never unbounded. The budget is VISUAL rows (a long wrapped line counts
|
|
495
|
+
// for more than one) at the framed block's inner width (border only —
|
|
496
|
+
// contentPaddingLeft is 0); only the visible suffix is syntax-colored, so
|
|
497
|
+
// the cheap raw-line wrap walk keeps the per-chunk cost bounded.
|
|
498
|
+
// innerWidth/budget are in the cache salt so a resize re-slices.
|
|
499
|
+
const innerWidth = Math.max(1, width - 2);
|
|
500
|
+
const budget = expanded ? previewWindowRows() : Math.min(PREVIEW_LIMITS.EXPANDED_LINES, previewWindowRows());
|
|
501
|
+
let text = cachedRenderedString(cache, uiTheme, expanded, `${rawPath}:${innerWidth}:${budget}`, diff, () => {
|
|
502
|
+
// "Cursor" tail window: pin the last rows to the bottom so freshly streamed
|
|
503
|
+
// changes stay on screen. The whole-file diff is recomputed every chunk and
|
|
504
|
+
// its Myers alignment is not monotonic in payload length, so a hunk-aware
|
|
505
|
+
// window stutters as rows move between hunks. Expanded widens the window
|
|
506
|
+
// to the viewport; the full diff appears once the result finalizes.
|
|
507
|
+
const tail = sliceStreamingDiffTail(diff, innerWidth, budget);
|
|
508
|
+
let rendered = "\n\n";
|
|
509
|
+
if (tail.hidden) {
|
|
510
|
+
// Exact hidden line/hunk counts require scanning the discarded prefix,
|
|
511
|
+
// which would make every streaming update scale with the complete diff.
|
|
512
|
+
rendered += `${uiTheme.fg("dim", "… (content above)")}\n`;
|
|
513
|
+
}
|
|
514
|
+
rendered += renderDiffColored(tail.content, { filePath: rawPath, theme: uiTheme });
|
|
515
|
+
return rendered;
|
|
516
|
+
});
|
|
517
|
+
// The animated glyph rides this trailing line — inside the transcript's
|
|
518
|
+
// volatile-tail holdback — never the block header: an animating head row
|
|
519
|
+
// pins the native-scrollback commit boundary at the top of the block, so a
|
|
520
|
+
// tall expanded preview could never scroll-append mid-stream.
|
|
521
|
+
const spinner = spinnerFrame !== undefined ? `${formatStatusIcon("running", uiTheme, spinnerFrame)} ` : "";
|
|
522
|
+
// Expanded approval previews hide the "(preview)" label (#1992) but keep
|
|
523
|
+
// the animated glyph when one is active so the volatile tail stays live.
|
|
524
|
+
const hideLabel = expanded && label === "preview";
|
|
525
|
+
if (spinner || !hideLabel) {
|
|
526
|
+
text += `\n${hideLabel ? spinner.trimEnd() : `${spinner}${uiTheme.fg("dim", `(${label})`)}`}`;
|
|
527
|
+
}
|
|
528
|
+
return text;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function formatMultiFileStreamingDiff(
|
|
532
|
+
previews: PerFileDiffPreview[],
|
|
533
|
+
width: number,
|
|
534
|
+
uiTheme: Theme,
|
|
535
|
+
expanded: boolean,
|
|
536
|
+
spinnerFrame?: number,
|
|
537
|
+
caches?: RenderedStringCache[],
|
|
538
|
+
): string {
|
|
539
|
+
const parts: string[] = [];
|
|
540
|
+
for (let index = 0; index < previews.length; index++) {
|
|
541
|
+
const preview = previews[index]!;
|
|
542
|
+
if (!preview.diff && !preview.error) continue;
|
|
543
|
+
const header = uiTheme.fg("dim", `\n\n── ${shortenPath(preview.path)} ──`);
|
|
544
|
+
if (preview.error) {
|
|
545
|
+
parts.push(`${header}\n${uiTheme.fg("error", replaceTabs(preview.error))}`);
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (preview.diff) {
|
|
549
|
+
// Only the last file's preview carries the animated streaming glyph;
|
|
550
|
+
// earlier files have settled and must stay byte-stable so their rows
|
|
551
|
+
// can commit to native scrollback mid-stream.
|
|
552
|
+
const isLast = index === previews.length - 1;
|
|
553
|
+
const cache = previewCacheAt(caches, index);
|
|
554
|
+
parts.push(
|
|
555
|
+
`${header}${formatStreamingDiff(preview.diff, preview.path, width, uiTheme, expanded, "preview", isLast ? spinnerFrame : undefined, cache)}`,
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return parts.join("");
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function getCallPreview(
|
|
563
|
+
args: EditRenderArgs,
|
|
564
|
+
rawPath: string,
|
|
565
|
+
width: number,
|
|
566
|
+
uiTheme: Theme,
|
|
567
|
+
renderContext: EditRenderContext | undefined,
|
|
568
|
+
expanded: boolean,
|
|
569
|
+
spinnerFrame?: number,
|
|
570
|
+
caches?: RenderedStringCache[],
|
|
571
|
+
): string {
|
|
572
|
+
const multi = renderContext?.perFileDiffPreview;
|
|
573
|
+
if (multi && multi.length > 1 && multi.some(p => p.diff || p.error)) {
|
|
574
|
+
return formatMultiFileStreamingDiff(multi, width, uiTheme, expanded, spinnerFrame, caches);
|
|
575
|
+
}
|
|
576
|
+
const cache = previewCacheAt(caches, 0);
|
|
577
|
+
if (args.previewDiff) {
|
|
578
|
+
return formatStreamingDiff(args.previewDiff, rawPath, width, uiTheme, expanded, "preview", spinnerFrame, cache);
|
|
579
|
+
}
|
|
580
|
+
if (args.diff && args.op) {
|
|
581
|
+
return formatStreamingDiff(args.diff, rawPath, width, uiTheme, expanded, "streaming", spinnerFrame, cache);
|
|
582
|
+
}
|
|
583
|
+
if (args.diff) {
|
|
584
|
+
return renderPlainTextPreview(args.diff, uiTheme, rawPath);
|
|
585
|
+
}
|
|
586
|
+
if (args.newText || args.patch) {
|
|
587
|
+
return renderPlainTextPreview(args.newText ?? args.patch ?? "", uiTheme, rawPath);
|
|
588
|
+
}
|
|
589
|
+
if (renderContext?.editStreamingFallback) {
|
|
590
|
+
return renderContext.editStreamingFallback;
|
|
591
|
+
}
|
|
592
|
+
const contextPreview = renderContext?.editDiffPreview;
|
|
593
|
+
if (contextPreview && "error" in contextPreview && contextPreview.error) {
|
|
594
|
+
return uiTheme.fg("error", replaceTabs(contextPreview.error));
|
|
595
|
+
}
|
|
596
|
+
return "";
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const MISSING_APPLY_PATCH_END_ERROR = "The last line of the patch must be '*** End Patch'";
|
|
600
|
+
|
|
601
|
+
function normalizeHashlineInputPreviewPath(rawPath: string): string {
|
|
602
|
+
const trimmed = rawPath.trim();
|
|
603
|
+
const hashStart = /#[0-9a-fA-F]{4}$/u.exec(trimmed)?.index;
|
|
604
|
+
const withoutHash = hashStart === undefined ? trimmed : trimmed.slice(0, hashStart);
|
|
605
|
+
if (withoutHash.length < 2) return withoutHash;
|
|
606
|
+
const first = withoutHash[0];
|
|
607
|
+
const last = withoutHash[withoutHash.length - 1];
|
|
608
|
+
if ((first === '"' || first === "'") && first === last) {
|
|
609
|
+
return withoutHash.slice(1, -1);
|
|
610
|
+
}
|
|
611
|
+
return withoutHash;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function parseHashlineInputPreviewHeader(line: string): string | null {
|
|
615
|
+
const trimmed = line.trimEnd();
|
|
616
|
+
if (!trimmed.startsWith(HL_FILE_PREFIX)) return null;
|
|
617
|
+
// Keep streaming previews tolerant while the closing bracket is still
|
|
618
|
+
// being generated; the parser enforces the final `[path#TAG]` shape.
|
|
619
|
+
const bodyEnd = trimmed.endsWith(HL_FILE_SUFFIX) ? trimmed.length - HL_FILE_SUFFIX.length : trimmed.length;
|
|
620
|
+
const body = trimmed.slice(HL_FILE_PREFIX.length, bodyEnd).trim();
|
|
621
|
+
const previewPath = normalizeHashlineInputPreviewPath(body);
|
|
622
|
+
return previewPath.length > 0 ? previewPath : null;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Line-editing op headers (PUT/CUT family), distinct from file-level
|
|
626
|
+
// REM/MV ops. Body rows are `+TEXT`, so this only matches real headers.
|
|
627
|
+
const HL_LINE_OP_HEADER = /^(?:PUT|CUT)\b/;
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Walk a (possibly mid-stream) hashline payload into per-section descriptors:
|
|
631
|
+
* the target path plus any file-level op (`REM` → delete, `MV dest` → rename)
|
|
632
|
+
* and whether a line edit precedes it. Tolerant of partial input so the call
|
|
633
|
+
* preview can label a delete/move before the payload finishes streaming.
|
|
634
|
+
*/
|
|
635
|
+
function getHashlineInputSections(input: string): HashlineInputEntry[] {
|
|
636
|
+
const stripped = input.startsWith("\uFEFF") ? input.slice(1) : input;
|
|
637
|
+
const entries: HashlineInputEntry[] = [];
|
|
638
|
+
let current: HashlineInputEntry | undefined;
|
|
639
|
+
for (const rawLine of stripped.split("\n")) {
|
|
640
|
+
const line = rawLine.replace(/\r$/, "");
|
|
641
|
+
const headerPath = parseHashlineInputPreviewHeader(line);
|
|
642
|
+
if (headerPath) {
|
|
643
|
+
current = { path: headerPath };
|
|
644
|
+
entries.push(current);
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (!current) continue;
|
|
648
|
+
const trimmed = line.trim();
|
|
649
|
+
if (trimmed === HL_REM_KEYWORD) {
|
|
650
|
+
current.op = "delete";
|
|
651
|
+
} else if (trimmed.startsWith(`${HL_MOVE_KEYWORD} `)) {
|
|
652
|
+
current.rename = normalizeHashlineInputPreviewPath(trimmed.slice(HL_MOVE_KEYWORD.length + 1));
|
|
653
|
+
} else if (HL_LINE_OP_HEADER.test(trimmed)) {
|
|
654
|
+
current.hasLineEdits = true;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return entries;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function getHashlineInputRenderSummary(
|
|
661
|
+
args: EditRenderArgs,
|
|
662
|
+
editMode: EditMode | undefined,
|
|
663
|
+
): HashlineInputRenderSummary | undefined {
|
|
664
|
+
const input = args.input ?? args._input;
|
|
665
|
+
if (editMode !== "hashline" || typeof input !== "string") {
|
|
666
|
+
return undefined;
|
|
667
|
+
}
|
|
668
|
+
return { entries: getHashlineInputSections(input) };
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Per-file entries of a possibly partial payload. The native inspect re-parses
|
|
673
|
+
* the whole payload, and the call header and compact activity row both ask on
|
|
674
|
+
* every frame, so the answer (or the parser's rejection) is memoized on `args`
|
|
675
|
+
* for as long as `input` is unchanged.
|
|
676
|
+
*/
|
|
677
|
+
function inspectInputEntries(args: EditRenderArgs, mode: EditMode, input: string): InspectedInputEntry[] {
|
|
678
|
+
const cached = args[kInspectedInput];
|
|
679
|
+
if (cached && cached.mode === mode && cached.input === input) {
|
|
680
|
+
if (cached.entries) return cached.entries;
|
|
681
|
+
throw cached.error;
|
|
682
|
+
}
|
|
683
|
+
let inspection: EditInspection;
|
|
684
|
+
try {
|
|
685
|
+
inspection = editInspect(mode, JSON.stringify({ input }));
|
|
686
|
+
} catch (error) {
|
|
687
|
+
args[kInspectedInput] = { mode, input, error };
|
|
688
|
+
throw error;
|
|
689
|
+
}
|
|
690
|
+
const entries = new Map<string, InspectedInputEntry>();
|
|
691
|
+
for (const path of inspection.paths) entries.set(path, { path });
|
|
692
|
+
for (const intent of inspection.fileOps) {
|
|
693
|
+
const entry = entries.get(intent.path) ?? { path: intent.path };
|
|
694
|
+
if (intent.kind === "delete") {
|
|
695
|
+
entry.op = "delete";
|
|
696
|
+
} else if (intent.kind === "move") {
|
|
697
|
+
entry.op = "update";
|
|
698
|
+
entry.rename = intent.to;
|
|
699
|
+
}
|
|
700
|
+
entries.set(intent.path, entry);
|
|
701
|
+
}
|
|
702
|
+
const result = [...entries.values()];
|
|
703
|
+
args[kInspectedInput] = { mode, input, entries: result };
|
|
704
|
+
return result;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/** Per-file descriptors for a possibly partial sloppy payload. */
|
|
708
|
+
function getSloppyInputRenderSummary(
|
|
709
|
+
args: EditRenderArgs,
|
|
710
|
+
editMode: EditMode | undefined,
|
|
711
|
+
): { entries: InspectedInputEntry[] } | undefined {
|
|
712
|
+
const input = args.input ?? args._input;
|
|
713
|
+
if (editMode !== "sloppy" || typeof input !== "string") return undefined;
|
|
714
|
+
try {
|
|
715
|
+
const entries = inspectInputEntries(args, "sloppy", input);
|
|
716
|
+
return entries.length > 0 ? { entries } : undefined;
|
|
717
|
+
} catch {
|
|
718
|
+
return undefined;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function getApplyPatchRenderSummary(
|
|
723
|
+
args: EditRenderArgs,
|
|
724
|
+
isPartial: boolean,
|
|
725
|
+
editMode: EditMode | undefined,
|
|
726
|
+
): ApplyPatchRenderSummary | undefined {
|
|
727
|
+
if ((editMode !== undefined && editMode !== "apply_patch") || typeof args.input !== "string") return undefined;
|
|
728
|
+
try {
|
|
729
|
+
return { entries: inspectInputEntries(args, "apply_patch", args.input) };
|
|
730
|
+
} catch (err) {
|
|
731
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
732
|
+
return isPartial && error === MISSING_APPLY_PATCH_END_ERROR ? { entries: [] } : { entries: [], error };
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
/** Header facts (path, op, rename, file count) resolved from streamed edit args; shared by the framed call header and the compact activity summary. */
|
|
736
|
+
interface EditCallFacts {
|
|
737
|
+
rawPath: string;
|
|
738
|
+
rename?: string;
|
|
739
|
+
op?: Operation;
|
|
740
|
+
/** Distinct files touched by the call (0 when unknown). */
|
|
741
|
+
fileCount: number;
|
|
742
|
+
/** Apply-patch envelope parse error, when the payload failed to parse. */
|
|
743
|
+
applyPatchError?: string;
|
|
744
|
+
/** A hashline PUT/CUT line edit precedes the file op — keeps a move framed. */
|
|
745
|
+
hasHashlineLineEdits: boolean;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Length gate for the streamed-preview facts path: payloads only grow while
|
|
750
|
+
* streaming, so facts recompute only after K new bytes (the header/path/op
|
|
751
|
+
* fields tolerate a small lag) or when the payload shrinks (rewind). The
|
|
752
|
+
* final frame (`isPartial === false`) always recomputes. This turns the
|
|
753
|
+
* per-frame O(n) stringify+parse+scan into O(n^2/K) total per call.
|
|
754
|
+
*/
|
|
755
|
+
const EDIT_FACTS_MIN_GROWTH = 512;
|
|
756
|
+
let lastFactsCache:
|
|
757
|
+
| {
|
|
758
|
+
editArgs: EditRenderArgs;
|
|
759
|
+
isPartial: boolean;
|
|
760
|
+
editMode: EditMode | undefined;
|
|
761
|
+
length: number;
|
|
762
|
+
facts: EditCallFacts;
|
|
763
|
+
}
|
|
764
|
+
| undefined;
|
|
765
|
+
|
|
766
|
+
function editFactsInputLength(editArgs: EditRenderArgs): number {
|
|
767
|
+
const input = editArgs.input ?? editArgs._input;
|
|
768
|
+
if (typeof input === "string") return input.length;
|
|
769
|
+
// Structured `edits[]` calls carry no `input` string; their facts derive
|
|
770
|
+
// from the array, so length-gate on its size instead of a constant 0
|
|
771
|
+
// (which would reuse forever after the first compute).
|
|
772
|
+
return Array.isArray(editArgs.edits) ? editArgs.edits.length : 0;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function resolveEditCallFacts(
|
|
776
|
+
editArgs: EditRenderArgs,
|
|
777
|
+
isPartial: boolean,
|
|
778
|
+
editMode: EditMode | undefined,
|
|
779
|
+
): EditCallFacts {
|
|
780
|
+
const cached = lastFactsCache;
|
|
781
|
+
if (
|
|
782
|
+
cached !== undefined &&
|
|
783
|
+
cached.editArgs === editArgs &&
|
|
784
|
+
cached.isPartial === isPartial &&
|
|
785
|
+
cached.editMode === editMode
|
|
786
|
+
) {
|
|
787
|
+
const length = editFactsInputLength(editArgs);
|
|
788
|
+
// Same args object, still growing gradually: reuse. A rewind
|
|
789
|
+
// (shorter), a jump past the gate, or the final frame recomputes.
|
|
790
|
+
if (isPartial && length >= cached.length && length - cached.length < EDIT_FACTS_MIN_GROWTH) {
|
|
791
|
+
return cached.facts;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
const facts = resolveEditCallFactsUncached(editArgs, isPartial, editMode);
|
|
795
|
+
lastFactsCache = { editArgs, isPartial, editMode, length: editFactsInputLength(editArgs), facts };
|
|
796
|
+
return facts;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function resolveEditCallFactsUncached(
|
|
800
|
+
editArgs: EditRenderArgs,
|
|
801
|
+
isPartial: boolean,
|
|
802
|
+
editMode: EditMode | undefined,
|
|
803
|
+
): EditCallFacts {
|
|
804
|
+
const hashlineInputSummary = getHashlineInputRenderSummary(editArgs, editMode);
|
|
805
|
+
const sloppyInputSummary = getSloppyInputRenderSummary(editArgs, editMode);
|
|
806
|
+
const applyPatchSummary = getApplyPatchRenderSummary(editArgs, isPartial, editMode);
|
|
807
|
+
const firstApplyPatchEntry = applyPatchSummary?.entries[0];
|
|
808
|
+
const firstHashlineInputEntry = hashlineInputSummary?.entries[0];
|
|
809
|
+
// Extract path from first edit entry when top-level path is absent (new schema)
|
|
810
|
+
const firstEdit = Array.isArray(editArgs.edits) && editArgs.edits.length > 0 ? editArgs.edits[0] : undefined;
|
|
811
|
+
const rawPath =
|
|
812
|
+
typeof editArgs.file_path === "string"
|
|
813
|
+
? editArgs.file_path
|
|
814
|
+
: typeof editArgs.path === "string"
|
|
815
|
+
? editArgs.path
|
|
816
|
+
: (filePathFromEditEntry(firstEdit?.path) ??
|
|
817
|
+
getPartialJsonEditPath(editArgs) ??
|
|
818
|
+
firstHashlineInputEntry?.path ??
|
|
819
|
+
sloppyInputSummary?.entries[0]?.path ??
|
|
820
|
+
firstApplyPatchEntry?.path ??
|
|
821
|
+
"");
|
|
822
|
+
const rename =
|
|
823
|
+
(typeof editArgs.rename === "string" ? editArgs.rename : undefined) ??
|
|
824
|
+
filePathFromEditEntry(firstEdit?.rename) ??
|
|
825
|
+
filePathFromEditEntry(firstEdit?.move) ??
|
|
826
|
+
firstApplyPatchEntry?.rename ??
|
|
827
|
+
firstHashlineInputEntry?.rename;
|
|
828
|
+
const op = editArgs.op || firstEdit?.op || firstApplyPatchEntry?.op || firstHashlineInputEntry?.op;
|
|
829
|
+
let fileCount =
|
|
830
|
+
hashlineInputSummary?.entries.length ??
|
|
831
|
+
sloppyInputSummary?.entries.length ??
|
|
832
|
+
applyPatchSummary?.entries.length ??
|
|
833
|
+
0;
|
|
834
|
+
if (Array.isArray(editArgs.edits)) {
|
|
835
|
+
fileCount = countEditFiles(editArgs.edits);
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
rawPath,
|
|
839
|
+
rename,
|
|
840
|
+
op,
|
|
841
|
+
fileCount,
|
|
842
|
+
applyPatchError: applyPatchSummary?.error,
|
|
843
|
+
hasHashlineLineEdits: Boolean(firstHashlineInputEntry?.hasLineEdits),
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function formatDiffStatsSuffix(diff: string, uiTheme: Theme): string {
|
|
848
|
+
const { added, removed } = getDiffStats(diff);
|
|
849
|
+
if (added === 0 && removed === 0) return "";
|
|
850
|
+
const stats = [
|
|
851
|
+
added > 0 ? uiTheme.fg("toolDiffAdded", `+${added}`) : undefined,
|
|
852
|
+
removed > 0 ? uiTheme.fg("toolDiffRemoved", `-${removed}`) : undefined,
|
|
853
|
+
].filter(value => value !== undefined);
|
|
854
|
+
return ` ${uiTheme.fg("dim", uiTheme.format.bracketLeft)}${stats.join(uiTheme.fg("dim", "/"))}${uiTheme.fg("dim", uiTheme.format.bracketRight)}`;
|
|
855
|
+
}
|
|
856
|
+
function renderDiffSection(
|
|
857
|
+
diff: string,
|
|
858
|
+
rawPath: string,
|
|
859
|
+
expanded: boolean,
|
|
860
|
+
innerWidth: number,
|
|
861
|
+
uiTheme: Theme,
|
|
862
|
+
renderDiffFn: (t: string, o?: { filePath?: string }) => string,
|
|
863
|
+
renderCache?: RenderedStringCache,
|
|
864
|
+
sectionCache?: RenderedStringCache,
|
|
865
|
+
): string {
|
|
866
|
+
return cachedRenderedString(sectionCache, uiTheme, expanded, `${rawPath}:${innerWidth}`, diff, () => {
|
|
867
|
+
const {
|
|
868
|
+
text: truncatedDiff,
|
|
869
|
+
hiddenHunks,
|
|
870
|
+
hiddenLines: logicallyHiddenLines,
|
|
871
|
+
} = expanded
|
|
872
|
+
? { text: diff, hiddenHunks: 0, hiddenLines: 0 }
|
|
873
|
+
: truncateDiffByHunk(diff, PREVIEW_LIMITS.DIFF_COLLAPSED_HUNKS, PREVIEW_LIMITS.DIFF_COLLAPSED_LINES);
|
|
874
|
+
|
|
875
|
+
const renderedDiff = cachedRenderedString(renderCache, uiTheme, expanded, rawPath, truncatedDiff, () =>
|
|
876
|
+
renderDiffFn(truncatedDiff, { filePath: rawPath }),
|
|
877
|
+
);
|
|
878
|
+
const { text: visibleDiff, hiddenLines: visuallyHiddenLines } = expanded
|
|
879
|
+
? { text: renderedDiff, hiddenLines: 0 }
|
|
880
|
+
: sliceCollapsedDiffRows(renderedDiff, innerWidth, PREVIEW_LIMITS.DIFF_COLLAPSED_LINES);
|
|
881
|
+
const hiddenLines = logicallyHiddenLines + visuallyHiddenLines;
|
|
882
|
+
|
|
883
|
+
let text = `\n${visibleDiff}`;
|
|
884
|
+
if (!expanded && (hiddenHunks > 0 || hiddenLines > 0)) {
|
|
885
|
+
const remainder: string[] = [];
|
|
886
|
+
if (hiddenHunks > 0) remainder.push(`${hiddenHunks} more hunks`);
|
|
887
|
+
if (hiddenLines > 0) remainder.push(`${hiddenLines} more lines`);
|
|
888
|
+
text += uiTheme.fg("toolOutput", `\n… (${remainder.join(", ")}) ${formatExpandHint(uiTheme)}`);
|
|
889
|
+
}
|
|
890
|
+
return text;
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function wrapEditRendererLine(line: string, width: number): string[] {
|
|
895
|
+
if (width <= 0) return [line];
|
|
896
|
+
if (line.length === 0) return [""];
|
|
897
|
+
|
|
898
|
+
const startAnsi = line.match(/^((?:\x1b\[[0-9;]*m)*)/)?.[1] ?? "";
|
|
899
|
+
const bodyWithReset = line.slice(startAnsi.length);
|
|
900
|
+
const body = bodyWithReset.endsWith("\x1b[39m") ? bodyWithReset.slice(0, -"\x1b[39m".length) : bodyWithReset;
|
|
901
|
+
// Gutter shapes produced by formatCodeFrameLine: "-315│", " 313│", "+322│",
|
|
902
|
+
// plus the deduplicated forms " +│" and " │" whose repeated line number
|
|
903
|
+
// renderDiff blanked (single-line replacement pairs and insert-then-context
|
|
904
|
+
// runs) — all │-separated. ASCII "|" gutters may arrive from injected
|
|
905
|
+
// renderers that preserve raw canonical rows; those always carry a marker
|
|
906
|
+
// column ("+"/"-"/space) and a line number. So
|
|
907
|
+
// the number is optional for "│", while "|" requires the full canonical
|
|
908
|
+
// shape; anything else (a body line merely starting with "|", error text
|
|
909
|
+
// like "123|…") is not a diff row and wraps generically.
|
|
910
|
+
const diffMatch = /^(\s*[+-]?\s*\d*)([|│])(.*)$/s.exec(body);
|
|
911
|
+
|
|
912
|
+
if (!diffMatch || diffMatch[1].length === 0 || (diffMatch[2] === "|" && !/^[+\-\s]\s*\d+$/.test(diffMatch[1]))) {
|
|
913
|
+
return wrapTextWithAnsi(line, width);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const [, gutter, separator, content] = diffMatch;
|
|
917
|
+
const prefix = `${gutter}${separator}`;
|
|
918
|
+
const prefixWidth = visibleWidth(prefix);
|
|
919
|
+
const contentWidth = Math.max(1, width - prefixWidth);
|
|
920
|
+
const continuationPrefix = `${" ".repeat(Math.max(0, prefixWidth - 1))}${separator}`;
|
|
921
|
+
const wrappedContent = wrapTextWithAnsi(content ?? "", contentWidth);
|
|
922
|
+
|
|
923
|
+
// Each visual row is a standalone terminal line: wrapTextWithAnsi re-opens
|
|
924
|
+
// active SGR state at the next row's start, so a row that breaks inside an
|
|
925
|
+
// intra-line diff highlight still ends with inverse video active. Close it
|
|
926
|
+
// alongside the foreground reset — otherwise the frame padding appended
|
|
927
|
+
// after the row is painted as an inverse block (default-foreground cells).
|
|
928
|
+
return wrappedContent.map(
|
|
929
|
+
(segment, index) => `${startAnsi}${index === 0 ? prefix : continuationPrefix}${segment}\x1b[27m\x1b[39m`,
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function sliceCollapsedDiffRows(
|
|
934
|
+
renderedDiff: string,
|
|
935
|
+
innerWidth: number,
|
|
936
|
+
maxRows: number,
|
|
937
|
+
): { text: string; hiddenLines: number } {
|
|
938
|
+
const lines = renderedDiff.split("\n");
|
|
939
|
+
const visibleRows: string[] = [];
|
|
940
|
+
let completeLines = 0;
|
|
941
|
+
|
|
942
|
+
for (const line of lines) {
|
|
943
|
+
const wrapped = wrapEditRendererLine(line, innerWidth);
|
|
944
|
+
const remainingRows = maxRows - visibleRows.length;
|
|
945
|
+
if (wrapped.length <= remainingRows) {
|
|
946
|
+
visibleRows.push(...wrapped);
|
|
947
|
+
completeLines++;
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (remainingRows > 0) visibleRows.push(...wrapped.slice(0, remainingRows));
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
return {
|
|
955
|
+
text: visibleRows.join("\n"),
|
|
956
|
+
hiddenLines: lines.length - completeLines,
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/** Render edit previews and completed file diffs. */
|
|
961
|
+
export const editToolRenderer = {
|
|
962
|
+
mergeCallAndResult: true,
|
|
963
|
+
/** Compact one-line activity: operation + target path instead of the payload's first line. */
|
|
964
|
+
activitySummary(args: unknown, context: ToolActivityContext): ToolActivitySummary {
|
|
965
|
+
const editArgs = (args ?? {}) as EditRenderArgs;
|
|
966
|
+
const editMode = (context.renderContext as EditRenderContext | undefined)?.editMode;
|
|
967
|
+
const facts = resolveEditCallFacts(editArgs, context.isPartial, editMode);
|
|
968
|
+
const label = getOperationTitle(facts.op);
|
|
969
|
+
if (!facts.rawPath) return { label };
|
|
970
|
+
let detail = formatEditTitlePath(facts.rawPath);
|
|
971
|
+
if (facts.rename) detail += ` → ${formatEditTitlePath(facts.rename)}`;
|
|
972
|
+
if (facts.fileCount > 1) detail += ` (+${facts.fileCount - 1} more)`;
|
|
973
|
+
return { label, detail };
|
|
974
|
+
},
|
|
975
|
+
|
|
976
|
+
renderCall(
|
|
977
|
+
args: EditRenderArgs,
|
|
978
|
+
options: RenderResultOptions & { renderContext?: EditRenderContext },
|
|
979
|
+
uiTheme: Theme,
|
|
980
|
+
): Component {
|
|
981
|
+
const renderContext = options.renderContext;
|
|
982
|
+
const editArgs = args as EditRenderArgs;
|
|
983
|
+
const { rawPath, rename, op, fileCount, applyPatchError, hasHashlineLineEdits } = resolveEditCallFacts(
|
|
984
|
+
editArgs,
|
|
985
|
+
options.isPartial,
|
|
986
|
+
renderContext?.editMode,
|
|
987
|
+
);
|
|
988
|
+
// Delete / payload-less move calls render as an inline pending row (no
|
|
989
|
+
// empty framed container), mirroring the completed result but with the
|
|
990
|
+
// shared hourglass instead of the eraser/move glyph.
|
|
991
|
+
const hasPayload = hasEditCallPayload(editArgs, renderContext) || hasHashlineLineEdits;
|
|
992
|
+
if (fileCount <= 1 && !applyPatchError && (op === "delete" || (rename !== undefined && !hasPayload))) {
|
|
993
|
+
return renderInlineEditRow(uiTheme, { op, rename, rawPath, pending: true });
|
|
994
|
+
}
|
|
995
|
+
const callPreviewCaches: RenderedStringCache[] = [];
|
|
996
|
+
return framedToolCard(uiTheme, ({ width }) => {
|
|
997
|
+
// No status icon on the head row: it's the head of the framed block,
|
|
998
|
+
// and native-scrollback commits are prefix-only — an animated glyph
|
|
999
|
+
// would pin the commit boundary at the top, and the pending hourglass
|
|
1000
|
+
// just adds noise. The liveness cue rides the trailing "(preview)" /
|
|
1001
|
+
// "(streaming)" line instead.
|
|
1002
|
+
const header = renderEditHeader(width, uiTheme, {
|
|
1003
|
+
op,
|
|
1004
|
+
rawPath,
|
|
1005
|
+
rename,
|
|
1006
|
+
extraSuffix: fileCount > 1 ? uiTheme.fg("dim", ` (+${fileCount - 1} more)`) : undefined,
|
|
1007
|
+
});
|
|
1008
|
+
let body = getCallPreview(
|
|
1009
|
+
editArgs,
|
|
1010
|
+
rawPath,
|
|
1011
|
+
width,
|
|
1012
|
+
uiTheme,
|
|
1013
|
+
renderContext,
|
|
1014
|
+
options.expanded,
|
|
1015
|
+
options?.spinnerFrame,
|
|
1016
|
+
callPreviewCaches,
|
|
1017
|
+
);
|
|
1018
|
+
if (applyPatchError) {
|
|
1019
|
+
body += `\n${uiTheme.fg("error", truncateToWidth(replaceTabs(applyPatchError), Math.max(1, width - 2)))}`;
|
|
1020
|
+
}
|
|
1021
|
+
const bodyLines = body ? body.split("\n") : [];
|
|
1022
|
+
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
1023
|
+
return {
|
|
1024
|
+
header,
|
|
1025
|
+
sections: bodyLines.length > 0 ? [{ content: bodyLines }] : [],
|
|
1026
|
+
phase: applyPatchError ? "error" : "pending",
|
|
1027
|
+
borderColor: applyPatchError ? "error" : "borderMuted",
|
|
1028
|
+
contentPaddingLeft: 0,
|
|
1029
|
+
};
|
|
1030
|
+
});
|
|
1031
|
+
},
|
|
1032
|
+
|
|
1033
|
+
renderResult(
|
|
1034
|
+
result: { content: Array<{ type: string; text?: string }>; details?: EditToolDetails; isError?: boolean },
|
|
1035
|
+
options: RenderResultOptions & { renderContext?: EditRenderContext },
|
|
1036
|
+
uiTheme: Theme,
|
|
1037
|
+
args?: EditRenderArgs,
|
|
1038
|
+
): Component {
|
|
1039
|
+
const edits = Array.isArray(args?.edits) ? args.edits : undefined;
|
|
1040
|
+
const perFileResults = result.details?.perFileResults;
|
|
1041
|
+
const totalFiles = edits ? countEditFiles(edits) : 0;
|
|
1042
|
+
if (perFileResults && (perFileResults.length > 1 || totalFiles > 1)) {
|
|
1043
|
+
return renderMultiFileResult(perFileResults, totalFiles, options, uiTheme);
|
|
1044
|
+
}
|
|
1045
|
+
return renderSingleFileResult(result, options, uiTheme, args);
|
|
1046
|
+
},
|
|
1047
|
+
} satisfies ToolRenderer<EditRenderArgs, EditToolDetails>;
|
|
1048
|
+
|
|
1049
|
+
function renderSingleFileResult(
|
|
1050
|
+
result: {
|
|
1051
|
+
content: Array<{ type: string; text?: string }>;
|
|
1052
|
+
details?: EditToolDetails | EditToolPerFileResult;
|
|
1053
|
+
isError?: boolean;
|
|
1054
|
+
},
|
|
1055
|
+
options: RenderResultOptions & { renderContext?: EditRenderContext },
|
|
1056
|
+
uiTheme: Theme,
|
|
1057
|
+
args?: EditRenderArgs,
|
|
1058
|
+
): Component {
|
|
1059
|
+
const details = result.details;
|
|
1060
|
+
const isError = result.isError ?? (details && "isError" in details ? details.isError : false);
|
|
1061
|
+
const edits = Array.isArray(args?.edits) ? args.edits : undefined;
|
|
1062
|
+
const firstEdit = edits?.[0];
|
|
1063
|
+
const hashlineInputSummary = getHashlineInputRenderSummary(args ?? {}, options.renderContext?.editMode);
|
|
1064
|
+
const firstHashlineInputEntry = hashlineInputSummary?.entries[0];
|
|
1065
|
+
const moveSource =
|
|
1066
|
+
details && "sourcePath" in details && typeof details.sourcePath === "string" ? details.sourcePath : undefined;
|
|
1067
|
+
const detailPath = details && "path" in details && typeof details.path === "string" ? details.path : undefined;
|
|
1068
|
+
const rawPath =
|
|
1069
|
+
moveSource ??
|
|
1070
|
+
(typeof args?.file_path === "string"
|
|
1071
|
+
? args.file_path
|
|
1072
|
+
: typeof args?.path === "string"
|
|
1073
|
+
? args.path
|
|
1074
|
+
: (filePathFromEditEntry(firstEdit?.path) ?? detailPath ?? firstHashlineInputEntry?.path ?? ""));
|
|
1075
|
+
const op = args?.op || firstEdit?.op || details?.op;
|
|
1076
|
+
const rename =
|
|
1077
|
+
(typeof args?.rename === "string" ? args.rename : undefined) ??
|
|
1078
|
+
filePathFromEditEntry(firstEdit?.rename) ??
|
|
1079
|
+
filePathFromEditEntry(firstEdit?.move) ??
|
|
1080
|
+
(details && "move" in details && typeof details.move === "string" ? details.move : undefined);
|
|
1081
|
+
|
|
1082
|
+
const displayErrorText = isError && details && "displayErrorText" in details ? details.displayErrorText : undefined;
|
|
1083
|
+
const errorText = isError
|
|
1084
|
+
? displayErrorText ||
|
|
1085
|
+
(details && "errorText" in details && details.errorText) ||
|
|
1086
|
+
(result.content?.find(c => c.type === "text")?.text ?? "")
|
|
1087
|
+
: "";
|
|
1088
|
+
|
|
1089
|
+
// Delete and move-only results carry no diff to box. Per design these render
|
|
1090
|
+
// as an inline status row (eraser / move glyph) rather than an empty framed
|
|
1091
|
+
// container. Errors, no-ops, creates, move-with-edits, and anything with
|
|
1092
|
+
// diagnostics keep the framed block below.
|
|
1093
|
+
if (!isError && !details?.diff && !details?.diagnostics && (op === "delete" || rename)) {
|
|
1094
|
+
const linkPath = details && "path" in details ? details.path : undefined;
|
|
1095
|
+
return renderInlineEditRow(uiTheme, { op, rename, rawPath, linkPath, pending: false });
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const renderFallbackDiff = (diffText: string, diffOptions?: { filePath?: string }): string =>
|
|
1099
|
+
renderDiffColored(diffText, { filePath: diffOptions?.filePath, theme: uiTheme });
|
|
1100
|
+
|
|
1101
|
+
let diffSectionRenderDiffFn: ((t: string, o?: { filePath?: string }) => string) | undefined;
|
|
1102
|
+
const diffSectionCache = createRenderedStringCache();
|
|
1103
|
+
const renderedDiffCache = createRenderedStringCache();
|
|
1104
|
+
const statsSuffixCache = createRenderedStringCache();
|
|
1105
|
+
|
|
1106
|
+
return framedToolCard(uiTheme, ({ width }) => {
|
|
1107
|
+
const { expanded, renderContext } = options;
|
|
1108
|
+
// A finalized result is authoritative: its `details` describe exactly
|
|
1109
|
+
// what happened. The shared streaming `editDiffPreview` is a call-phase
|
|
1110
|
+
// artifact (in a batch it reflects only the first file), so consulting it
|
|
1111
|
+
// for an empty-diff delete/move/no-op result mislabels the card. Fall
|
|
1112
|
+
// back to the preview only when no details exist yet.
|
|
1113
|
+
const editDiffPreview = details ? undefined : renderContext?.editDiffPreview;
|
|
1114
|
+
const renderDiffFn = renderContext?.renderDiff ?? renderFallbackDiff;
|
|
1115
|
+
|
|
1116
|
+
if (diffSectionRenderDiffFn !== renderDiffFn) {
|
|
1117
|
+
diffSectionRenderDiffFn = renderDiffFn;
|
|
1118
|
+
invalidateRenderedStringCache(diffSectionCache);
|
|
1119
|
+
invalidateRenderedStringCache(renderedDiffCache);
|
|
1120
|
+
}
|
|
1121
|
+
const firstChangedLine =
|
|
1122
|
+
(editDiffPreview && "firstChangedLine" in editDiffPreview ? editDiffPreview.firstChangedLine : undefined) ||
|
|
1123
|
+
(details && !isError ? details.firstChangedLine : undefined);
|
|
1124
|
+
const linkPath = details && "path" in details ? details.path : undefined;
|
|
1125
|
+
|
|
1126
|
+
// Change stats ride inline on the header bar next to the path.
|
|
1127
|
+
const previewDiff = editDiffPreview && !("error" in editDiffPreview) ? editDiffPreview.diff : undefined;
|
|
1128
|
+
const headerDiff = isError ? undefined : details?.diff || previewDiff;
|
|
1129
|
+
const statsSuffix = headerDiff
|
|
1130
|
+
? cachedRenderedString(statsSuffixCache, uiTheme, false, "", headerDiff, () =>
|
|
1131
|
+
formatDiffStatsSuffix(headerDiff, uiTheme),
|
|
1132
|
+
)
|
|
1133
|
+
: "";
|
|
1134
|
+
const header = renderEditHeader(width, uiTheme, {
|
|
1135
|
+
icon: isError ? "error" : "success",
|
|
1136
|
+
iconOverride: !isError && !options.isPartial ? uiTheme.styledSymbol("tool.edit", "accent") : undefined,
|
|
1137
|
+
op,
|
|
1138
|
+
rawPath,
|
|
1139
|
+
rename,
|
|
1140
|
+
firstChangedLine,
|
|
1141
|
+
linkPath,
|
|
1142
|
+
statsSuffix,
|
|
1143
|
+
});
|
|
1144
|
+
const innerWidth = Math.max(1, width - 2);
|
|
1145
|
+
|
|
1146
|
+
let body = "";
|
|
1147
|
+
if (isError) {
|
|
1148
|
+
if (errorText) body = uiTheme.fg("error", replaceTabs(errorText));
|
|
1149
|
+
} else if (details?.diff) {
|
|
1150
|
+
body = renderDiffSection(
|
|
1151
|
+
details.diff,
|
|
1152
|
+
rawPath,
|
|
1153
|
+
expanded,
|
|
1154
|
+
innerWidth,
|
|
1155
|
+
uiTheme,
|
|
1156
|
+
renderDiffFn,
|
|
1157
|
+
renderedDiffCache,
|
|
1158
|
+
diffSectionCache,
|
|
1159
|
+
);
|
|
1160
|
+
} else if (details) {
|
|
1161
|
+
// Authoritative result with no textual diff: a delete, a move-only
|
|
1162
|
+
// rename, or a genuine no-op. The header already names the op
|
|
1163
|
+
// (Delete / `src → dst`); only a true no-op needs an explanatory
|
|
1164
|
+
// body so an empty card isn't mistaken for a stalled edit.
|
|
1165
|
+
if (op !== "delete" && op !== "create" && !rename) {
|
|
1166
|
+
const noChangePath = linkPath ? shortenPath(linkPath) : rawPath ? shortenPath(rawPath) : "";
|
|
1167
|
+
body = uiTheme.fg("dim", `No changes were made${noChangePath ? ` to ${noChangePath}` : ""}.`);
|
|
1168
|
+
}
|
|
1169
|
+
} else if (editDiffPreview) {
|
|
1170
|
+
if ("error" in editDiffPreview) body = uiTheme.fg("error", replaceTabs(editDiffPreview.error));
|
|
1171
|
+
else if (editDiffPreview.diff)
|
|
1172
|
+
body = renderDiffSection(
|
|
1173
|
+
editDiffPreview.diff,
|
|
1174
|
+
rawPath,
|
|
1175
|
+
expanded,
|
|
1176
|
+
innerWidth,
|
|
1177
|
+
uiTheme,
|
|
1178
|
+
renderDiffFn,
|
|
1179
|
+
renderedDiffCache,
|
|
1180
|
+
diffSectionCache,
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
if (details?.diagnostics) {
|
|
1184
|
+
body += formatDiagnostics(details.diagnostics, expanded, uiTheme, (fp: string) =>
|
|
1185
|
+
uiTheme.getLangIcon(getLanguageFromPath(fp)),
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Diff lines self-wrap with a continuation gutter; pre-wrap to the frame's
|
|
1190
|
+
// inner width so renderOutputBlock's generic wrap is a no-op. Edit frames
|
|
1191
|
+
// use a flush left border because code-frame gutters already provide padding.
|
|
1192
|
+
const bodyLines = body.length > 0 ? body.split("\n").flatMap(line => wrapEditRendererLine(line, innerWidth)) : [];
|
|
1193
|
+
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
1194
|
+
|
|
1195
|
+
return {
|
|
1196
|
+
header,
|
|
1197
|
+
sections: bodyLines.length > 0 ? [{ content: bodyLines }] : [],
|
|
1198
|
+
phase: isError ? "error" : options.isPartial ? "partial" : "success",
|
|
1199
|
+
borderColor: isError ? "error" : "borderMuted",
|
|
1200
|
+
contentPaddingLeft: 0,
|
|
1201
|
+
};
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function renderMultiFileResult(
|
|
1206
|
+
perFileResults: EditToolPerFileResult[],
|
|
1207
|
+
totalFiles: number,
|
|
1208
|
+
options: RenderResultOptions & { renderContext?: EditRenderContext },
|
|
1209
|
+
uiTheme: Theme,
|
|
1210
|
+
): Component {
|
|
1211
|
+
const fileComponents = perFileResults.map(fileResult =>
|
|
1212
|
+
renderSingleFileResult({ content: [], details: fileResult, isError: fileResult.isError }, options, uiTheme),
|
|
1213
|
+
);
|
|
1214
|
+
const remaining = Math.max(0, totalFiles - perFileResults.length);
|
|
1215
|
+
|
|
1216
|
+
let cached: RenderCache | undefined;
|
|
1217
|
+
|
|
1218
|
+
return {
|
|
1219
|
+
render(width) {
|
|
1220
|
+
const key = new Hasher().bool(options.expanded).u32(width).u32(perFileResults.length).u32(remaining).digest();
|
|
1221
|
+
if (cached?.key === key) return cached.lines;
|
|
1222
|
+
|
|
1223
|
+
const allLines: string[] = [];
|
|
1224
|
+
for (let i = 0; i < fileComponents.length; i++) {
|
|
1225
|
+
if (i > 0) {
|
|
1226
|
+
allLines.push("");
|
|
1227
|
+
}
|
|
1228
|
+
allLines.push(...fileComponents[i].render(width));
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// Show pending indicator for files still being processed
|
|
1232
|
+
if (remaining > 0) {
|
|
1233
|
+
if (allLines.length > 0) allLines.push("");
|
|
1234
|
+
const spinnerFrame = options.spinnerFrame;
|
|
1235
|
+
const spinner = spinnerFrame !== undefined ? formatStatusIcon("running", uiTheme, spinnerFrame) : "";
|
|
1236
|
+
// Spinner while actively rendering, otherwise no icon — never the
|
|
1237
|
+
// pending hourglass on the head row.
|
|
1238
|
+
allLines.push(
|
|
1239
|
+
renderStatusLine(
|
|
1240
|
+
{
|
|
1241
|
+
iconOverride: spinner,
|
|
1242
|
+
title: "Edit",
|
|
1243
|
+
description: uiTheme.fg("dim", `${remaining} more file${remaining > 1 ? "s" : ""} pending…`),
|
|
1244
|
+
},
|
|
1245
|
+
uiTheme,
|
|
1246
|
+
),
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
cached = { key, lines: allLines };
|
|
1251
|
+
return allLines;
|
|
1252
|
+
},
|
|
1253
|
+
invalidate() {
|
|
1254
|
+
cached = undefined;
|
|
1255
|
+
for (const c of fileComponents) c.invalidate?.();
|
|
1256
|
+
},
|
|
1257
|
+
};
|
|
1258
|
+
}
|