@linxiraos/pi-tui 1.1.26 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -491
- package/README.md +13 -7
- package/dist/types/active-terminal.d.ts +30 -0
- package/dist/types/app-keybindings.d.ts +7 -2
- package/dist/types/apps/cleanse-board.d.ts +1 -1
- package/dist/types/apps/debug/log-viewer.d.ts +4 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
- package/dist/types/apps/git/git-tui.d.ts +5 -4
- package/dist/types/apps/git/help.d.ts +3 -1
- package/dist/types/apps/if-bench-board.d.ts +2 -2
- package/dist/types/apps/setup-model-picker.d.ts +1 -1
- package/dist/types/autocomplete.d.ts +5 -2
- package/dist/types/charts/chart-plan.d.ts +674 -0
- package/dist/types/charts/chart-svg.d.ts +59 -0
- package/dist/types/charts/table-data.d.ts +175 -0
- package/dist/types/chat/assistant-message.d.ts +11 -1
- package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
- package/dist/types/chat/compaction-summary-message.d.ts +1 -0
- package/dist/types/chat/execution-shared.d.ts +6 -5
- package/dist/types/chat/fence-figure.d.ts +42 -0
- package/dist/types/chat/figure-markdown.d.ts +42 -0
- package/dist/types/chat/messages.d.ts +1 -1
- package/dist/types/chat/svg-figure.d.ts +33 -0
- package/dist/types/chat/svg-source.d.ts +49 -0
- package/dist/types/chat/table-chart.d.ts +89 -0
- package/dist/types/chat/tool-execution.d.ts +2 -0
- package/dist/types/chat/user-message.d.ts +17 -4
- package/dist/types/chrome/shared.d.ts +9 -0
- package/dist/types/chrome/status-notice.d.ts +15 -3
- package/dist/types/chrome/transcript-container.d.ts +6 -0
- package/dist/types/components/box.d.ts +1 -0
- package/dist/types/components/disclosure.d.ts +1 -0
- package/dist/types/components/editor.d.ts +23 -5
- package/dist/types/components/form.d.ts +1 -0
- package/dist/types/components/image.d.ts +26 -44
- package/dist/types/components/input.d.ts +2 -1
- package/dist/types/components/key-value-list.d.ts +1 -0
- package/dist/types/components/layout/row.d.ts +1 -0
- package/dist/types/components/layout/stack.d.ts +1 -0
- package/dist/types/components/loader.d.ts +5 -6
- package/dist/types/components/markdown.d.ts +47 -5
- package/dist/types/components/scroll-view.d.ts +1 -0
- package/dist/types/components/section.d.ts +1 -0
- package/dist/types/components/table.d.ts +1 -0
- package/dist/types/components/text.d.ts +1 -0
- package/dist/types/components/tree-view.d.ts +7 -0
- package/dist/types/components/truncated-text.d.ts +1 -0
- package/dist/types/fuzzy.d.ts +29 -1
- package/dist/types/key-hint-format.d.ts +9 -1
- package/dist/types/keybinding-matchers.d.ts +3 -0
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/kitty-graphics.d.ts +2 -2
- package/dist/types/loop-watchdog.d.ts +8 -7
- package/dist/types/multiplexers/cmux.d.ts +16 -0
- package/dist/types/multiplexers/herdr.d.ts +26 -0
- package/dist/types/multiplexers/notify.d.ts +13 -0
- package/dist/types/multiplexers/orca.d.ts +12 -0
- package/dist/types/multiplexers/screen.d.ts +9 -0
- package/dist/types/multiplexers/tmux.d.ts +29 -0
- package/dist/types/multiplexers/types.d.ts +100 -0
- package/dist/types/multiplexers/wmux.d.ts +12 -0
- package/dist/types/multiplexers/zellij.d.ts +12 -0
- package/dist/types/native/apply.d.ts +4 -3
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +7 -3
- package/dist/types/native/encode.d.ts +8 -6
- package/dist/types/native/node.d.ts +36 -5
- package/dist/types/native/overlay.d.ts +8 -3
- package/dist/types/native/settle.d.ts +4 -3
- package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
- package/dist/types/overlays/agent-hub-types.d.ts +9 -0
- package/dist/types/overlays/agent-hub.d.ts +3 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +12 -2
- package/dist/types/overlays/agents-hub.d.ts +9 -0
- package/dist/types/overlays/annotation-overlay.d.ts +19 -2
- package/dist/types/overlays/annotation-types.d.ts +2 -0
- package/dist/types/overlays/ask-dialog.d.ts +5 -2
- package/dist/types/overlays/btw-history-panel.d.ts +13 -1
- package/dist/types/overlays/copy-selector.d.ts +1 -0
- package/dist/types/overlays/history-search.d.ts +10 -0
- package/dist/types/overlays/hook-selector.d.ts +9 -4
- package/dist/types/overlays/hub-frame.d.ts +4 -1
- package/dist/types/overlays/jobs-panel.d.ts +1 -7
- package/dist/types/overlays/model-browser.d.ts +54 -7
- package/dist/types/overlays/model-hub.d.ts +37 -0
- package/dist/types/overlays/oauth-selector.d.ts +0 -1
- package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
- package/dist/types/overlays/report-panel.d.ts +68 -0
- package/dist/types/overlays/rewind-selector.d.ts +1 -0
- package/dist/types/overlays/session-observer-registry.d.ts +2 -0
- package/dist/types/overlays/session-selector.d.ts +6 -2
- package/dist/types/overlays/settings-defs.d.ts +1 -0
- package/dist/types/overlays/thinking-selector.d.ts +2 -2
- package/dist/types/overlays/usage-dashboard.d.ts +12 -8
- package/dist/types/overlays/usage-display.d.ts +3 -1
- package/dist/types/prompt/composer-cache.d.ts +0 -16
- package/dist/types/prompt/composer.d.ts +18 -3
- package/dist/types/prompt/custom-editor.d.ts +25 -13
- package/dist/types/prompt/image-source.d.ts +2 -1
- package/dist/types/prompt/interactive-context-helpers.d.ts +5 -1
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/prompt/welcome.d.ts +7 -2
- package/dist/types/render/composite.d.ts +2 -0
- package/dist/types/render/output-pane.d.ts +1 -0
- package/dist/types/render/render-utils.d.ts +1 -0
- package/dist/types/render/tool-card.d.ts +6 -0
- package/dist/types/render/width-aware-text.d.ts +2 -0
- package/dist/types/setup/scenes/types.d.ts +1 -0
- package/dist/types/space-hold.d.ts +40 -25
- package/dist/types/status-line/component.d.ts +3 -0
- package/dist/types/status-line/context-usage.d.ts +7 -5
- package/dist/types/status-line/host.d.ts +4 -1
- package/dist/types/status-line/segments.d.ts +2 -0
- package/dist/types/status-line/types.d.ts +14 -3
- package/dist/types/terminal-capabilities.d.ts +41 -36
- package/dist/types/terminal-multiplexer.d.ts +127 -14
- package/dist/types/terminal.d.ts +23 -16
- package/dist/types/theme/active-symbols.d.ts +1 -1
- package/dist/types/theme/schema.d.ts +11 -2
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +7 -0
- package/dist/types/theme/theme.d.ts +1 -1
- package/dist/types/theme/tui-adapters.d.ts +3 -0
- package/dist/types/thinking.d.ts +1 -1
- package/dist/types/tmux.d.ts +11 -4
- package/dist/types/tools/bash.d.ts +7 -2
- package/dist/types/tools/daemon.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +22 -3
- package/dist/types/tools/glob.d.ts +4 -0
- package/dist/types/tools/native-view.d.ts +47 -0
- package/dist/types/tools/output-meta.d.ts +11 -1
- package/dist/types/tools/renderer.d.ts +37 -2
- package/dist/types/tools/streaming-output.d.ts +20 -1
- package/dist/types/tools/task.d.ts +2 -3
- package/dist/types/tools/write.d.ts +9 -2
- package/dist/types/tools/yield.d.ts +22 -0
- package/dist/types/tui.d.ts +36 -0
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/vim.d.ts +19 -5
- package/dist/types/windows-input-mode.d.ts +33 -1
- package/package.json +13 -9
- package/src/active-terminal.d.ts +30 -0
- package/src/active-terminal.ts +80 -0
- package/src/app-keybindings.d.ts +7 -2
- package/src/app-keybindings.ts +7 -2
- package/src/apps/autoresearch-dashboard.ts +7 -3
- package/src/apps/cleanse-board.ts +2 -2
- package/src/apps/cleanse-picker.ts +1 -1
- package/src/apps/debug/log-viewer.ts +188 -72
- package/src/apps/debug/raw-sse-buffer.ts +8 -0
- package/src/apps/debug/raw-sse.ts +39 -5
- package/src/apps/git/diff-pane.ts +96 -23
- package/src/apps/git/git-tui.ts +23 -16
- package/src/apps/git/help.ts +29 -20
- package/src/apps/git/sidebar.ts +8 -3
- package/src/apps/if-bench-board.ts +2 -2
- package/src/apps/ps-top.ts +2 -2
- package/src/apps/setup-model-picker.ts +1 -1
- package/src/autocomplete.d.ts +5 -2
- package/src/autocomplete.ts +67 -43
- package/src/bracketed-paste.ts +10 -3
- package/src/charts/chart-plan.d.ts +674 -0
- package/src/charts/chart-plan.ts +5081 -0
- package/src/charts/chart-svg.d.ts +59 -0
- package/src/charts/chart-svg.ts +4657 -0
- package/src/charts/table-data.d.ts +175 -0
- package/src/charts/table-data.ts +1478 -0
- package/src/chat/advisor-message.ts +11 -0
- package/src/chat/assistant-message.d.ts +11 -1
- package/src/chat/assistant-message.ts +245 -54
- package/src/chat/chat-transcript-builder.d.ts +3 -0
- package/src/chat/chat-transcript-builder.ts +9 -3
- package/src/chat/compaction-summary-message.d.ts +1 -0
- package/src/chat/compaction-summary-message.ts +8 -0
- package/src/chat/execution-shared.d.ts +6 -5
- package/src/chat/execution-shared.ts +42 -25
- package/src/chat/fence-figure.d.ts +42 -0
- package/src/chat/fence-figure.ts +80 -0
- package/src/chat/figure-markdown.d.ts +42 -0
- package/src/chat/figure-markdown.ts +174 -0
- package/src/chat/messages.d.ts +1 -1
- package/src/chat/messages.ts +1 -1
- package/src/chat/read-tool-group.ts +6 -4
- package/src/chat/skill-message.ts +6 -0
- package/src/chat/svg-figure.d.ts +33 -0
- package/src/chat/svg-figure.ts +399 -0
- package/src/chat/svg-source.d.ts +49 -0
- package/src/chat/svg-source.ts +188 -0
- package/src/chat/table-chart.d.ts +89 -0
- package/src/chat/table-chart.ts +294 -0
- package/src/chat/tool-execution.d.ts +2 -0
- package/src/chat/tool-execution.ts +86 -7
- package/src/chat/user-message.d.ts +17 -4
- package/src/chat/user-message.ts +71 -8
- package/src/chrome/shared.ts +53 -0
- package/src/chrome/status-notice.ts +25 -7
- package/src/chrome/transcript-container.d.ts +6 -0
- package/src/chrome/transcript-container.ts +162 -19
- package/src/components/box.d.ts +1 -0
- package/src/components/box.ts +5 -0
- package/src/components/disclosure.d.ts +1 -0
- package/src/components/disclosure.ts +8 -0
- package/src/components/editor.d.ts +23 -5
- package/src/components/editor.ts +316 -47
- package/src/components/form.d.ts +1 -0
- package/src/components/form.ts +8 -0
- package/src/components/image.d.ts +26 -44
- package/src/components/image.ts +115 -152
- package/src/components/input.d.ts +2 -1
- package/src/components/input.ts +17 -7
- package/src/components/key-value-list.d.ts +1 -0
- package/src/components/key-value-list.ts +4 -0
- package/src/components/layout/row.d.ts +1 -0
- package/src/components/layout/row.ts +7 -0
- package/src/components/layout/stack.d.ts +1 -0
- package/src/components/layout/stack.ts +7 -0
- package/src/components/loader.d.ts +5 -6
- package/src/components/loader.ts +10 -17
- package/src/components/markdown.d.ts +47 -5
- package/src/components/markdown.ts +786 -303
- package/src/components/scroll-view.d.ts +1 -0
- package/src/components/scroll-view.ts +5 -0
- package/src/components/section.d.ts +1 -0
- package/src/components/section.ts +5 -0
- package/src/components/settings-list.ts +2 -0
- package/src/components/table.d.ts +1 -0
- package/src/components/table.ts +4 -0
- package/src/components/text.d.ts +1 -0
- package/src/components/text.ts +7 -0
- package/src/components/tree-view.d.ts +7 -0
- package/src/components/tree-view.ts +38 -8
- package/src/components/truncated-text.d.ts +1 -0
- package/src/components/truncated-text.ts +5 -0
- package/src/desktop-notify.ts +3 -2
- package/src/fuzzy.d.ts +29 -1
- package/src/fuzzy.ts +115 -30
- package/src/hotkeys-markdown.ts +9 -2
- package/src/key-hint-format.d.ts +9 -1
- package/src/key-hint-format.ts +26 -11
- package/src/keybinding-matchers.d.ts +3 -0
- package/src/keybinding-matchers.ts +6 -2
- package/src/keybindings.d.ts +1 -1
- package/src/keybindings.ts +1 -1
- package/src/kitty-graphics.d.ts +2 -2
- package/src/kitty-graphics.ts +11 -5
- package/src/loop-watchdog.d.ts +8 -7
- package/src/loop-watchdog.ts +18 -13
- package/src/multiplexers/cmux.d.ts +16 -0
- package/src/multiplexers/cmux.ts +29 -0
- package/src/multiplexers/herdr.d.ts +26 -0
- package/src/multiplexers/herdr.ts +64 -0
- package/src/multiplexers/notify.d.ts +13 -0
- package/src/multiplexers/notify.ts +24 -0
- package/src/multiplexers/orca.d.ts +12 -0
- package/src/multiplexers/orca.ts +16 -0
- package/src/multiplexers/screen.d.ts +9 -0
- package/src/multiplexers/screen.ts +15 -0
- package/src/multiplexers/tmux.d.ts +29 -0
- package/src/multiplexers/tmux.ts +40 -0
- package/src/multiplexers/types.d.ts +100 -0
- package/src/multiplexers/types.ts +110 -0
- package/src/multiplexers/wmux.d.ts +12 -0
- package/src/multiplexers/wmux.ts +16 -0
- package/src/multiplexers/zellij.d.ts +12 -0
- package/src/multiplexers/zellij.ts +24 -0
- package/src/native/apply.d.ts +4 -3
- package/src/native/apply.ts +4 -3
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +199 -22
- package/src/native/blobs.d.ts +7 -3
- package/src/native/blobs.ts +47 -12
- package/src/native/encode.d.ts +8 -6
- package/src/native/encode.ts +76 -34
- package/src/native/node.d.ts +36 -5
- package/src/native/node.ts +31 -5
- package/src/native/overlay.d.ts +8 -3
- package/src/native/overlay.ts +17 -4
- package/src/native/reconcile.ts +16 -4
- package/src/native/settle.d.ts +4 -3
- package/src/native/settle.ts +4 -3
- package/src/overlays/agent-hub-projection.d.ts +6 -5
- package/src/overlays/agent-hub-projection.ts +124 -13
- package/src/overlays/agent-hub-types.d.ts +9 -0
- package/src/overlays/agent-hub-types.ts +6 -0
- package/src/overlays/agent-hub.ts +48 -29
- package/src/overlays/agent-transcript-viewer.ts +93 -23
- package/src/overlays/agents-hub.ts +28 -8
- package/src/overlays/annotation-overlay.ts +293 -90
- package/src/overlays/annotation-types.ts +2 -0
- package/src/overlays/ask-dialog.ts +10 -17
- package/src/overlays/btw-history-panel.ts +136 -39
- package/src/overlays/copy-selector.d.ts +1 -0
- package/src/overlays/copy-selector.ts +32 -8
- package/src/overlays/extensions/extension-dashboard.ts +4 -4
- package/src/overlays/extensions/state-manager.ts +52 -12
- package/src/overlays/history-search.ts +100 -7
- package/src/overlays/hook-selector.ts +12 -5
- package/src/overlays/hub-frame.ts +19 -1
- package/src/overlays/jobs-panel.ts +33 -52
- package/src/overlays/model-browser.ts +367 -122
- package/src/overlays/model-hub.ts +435 -59
- package/src/overlays/move-overlay.ts +28 -14
- package/src/overlays/oauth-selector.ts +54 -70
- package/src/overlays/pause-screen.ts +2 -2
- package/src/overlays/plan-review-overlay.ts +208 -70
- package/src/overlays/plugin-settings.ts +3 -3
- package/src/overlays/report-panel.ts +191 -0
- package/src/overlays/rewind-selector.ts +88 -17
- package/src/overlays/session-observer-registry.d.ts +2 -0
- package/src/overlays/session-observer-registry.ts +10 -2
- package/src/overlays/session-selector.ts +64 -26
- package/src/overlays/settings-defs.ts +5 -4
- package/src/overlays/settings-selector.ts +33 -8
- package/src/overlays/snapcompact-shape-preview.ts +2 -3
- package/src/overlays/stats-notice.ts +1 -1
- package/src/overlays/thinking-selector.ts +11 -10
- package/src/overlays/tree-selector.ts +41 -20
- package/src/overlays/usage-dashboard.ts +117 -58
- package/src/overlays/usage-display.ts +10 -4
- package/src/overlays/usage-row.ts +22 -8
- package/src/prompt/attachment-chips.ts +15 -11
- package/src/prompt/composer-attachments.ts +6 -4
- package/src/prompt/composer-cache.ts +7 -86
- package/src/prompt/composer.ts +37 -26
- package/src/prompt/custom-editor.ts +282 -105
- package/src/prompt/image-source.ts +2 -1
- package/src/prompt/interactive-context-helpers.ts +32 -18
- package/src/prompt/macos-spelling.ts +7 -0
- package/src/prompt/magic-keywords.ts +11 -2
- package/src/prompt/model-mention-autocomplete.ts +14 -14
- package/src/prompt/queued-messages.ts +6 -4
- package/src/prompt/tips.txt +13 -9
- package/src/prompt/welcome.ts +64 -46
- package/src/prompt/word-completion.ts +38 -5
- package/src/render/composite.d.ts +2 -0
- package/src/render/composite.ts +64 -0
- package/src/render/output-pane.d.ts +1 -0
- package/src/render/output-pane.ts +6 -0
- package/src/render/render-utils.d.ts +1 -0
- package/src/render/render-utils.ts +16 -1
- package/src/render/tool-card.d.ts +6 -0
- package/src/render/tool-card.ts +15 -0
- package/src/render/width-aware-text.d.ts +2 -0
- package/src/render/width-aware-text.ts +5 -0
- package/src/setup/scenes/model.ts +2 -1
- package/src/setup/scenes/types.ts +1 -0
- package/src/space-hold.d.ts +40 -25
- package/src/space-hold.ts +156 -52
- package/src/status-line/component.ts +292 -78
- package/src/status-line/context-usage.ts +21 -46
- package/src/status-line/host.ts +4 -1
- package/src/status-line/segments.ts +40 -22
- package/src/status-line/types.ts +15 -3
- package/src/terminal-capabilities.d.ts +41 -36
- package/src/terminal-capabilities.ts +137 -213
- package/src/terminal-multiplexer.d.ts +127 -14
- package/src/terminal-multiplexer.ts +112 -33
- package/src/terminal.d.ts +23 -16
- package/src/terminal.ts +203 -99
- package/src/theme/active-symbols.d.ts +1 -1
- package/src/theme/active-symbols.ts +1 -1
- package/src/theme/schema-validation.ts +1 -0
- package/src/theme/schema.d.ts +11 -2
- package/src/theme/schema.ts +14 -1
- package/src/theme/session-color.ts +1 -7
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +11 -4
- package/src/theme/theme-class.d.ts +7 -0
- package/src/theme/theme-class.ts +26 -1
- package/src/theme/theme-schema.json +5 -1
- package/src/theme/theme.d.ts +1 -1
- package/src/theme/theme.ts +8 -4
- package/src/theme/tui-adapters.d.ts +3 -0
- package/src/theme/tui-adapters.ts +68 -0
- package/src/thinking.d.ts +1 -1
- package/src/thinking.ts +1 -1
- package/src/tmux.d.ts +11 -4
- package/src/tmux.ts +47 -11
- package/src/tools/ast-edit.ts +59 -35
- package/src/tools/ast-grep.ts +35 -16
- package/src/tools/bash.d.ts +7 -2
- package/src/tools/bash.ts +116 -47
- package/src/tools/daemon.d.ts +1 -1
- package/src/tools/daemon.ts +1 -1
- package/src/tools/default-renderer.ts +35 -19
- package/src/tools/edit.ts +142 -120
- package/src/tools/eval-format/index.ts +28 -2
- package/src/tools/eval-format/javascript.ts +7 -5
- package/src/tools/eval.d.ts +22 -3
- package/src/tools/eval.ts +364 -208
- package/src/tools/fetch.ts +5 -6
- package/src/tools/glob.d.ts +4 -0
- package/src/tools/glob.ts +33 -65
- package/src/tools/grep.ts +81 -48
- package/src/tools/index.ts +2 -0
- package/src/tools/lsp.ts +81 -59
- package/src/tools/mcp.ts +111 -68
- package/src/tools/native-view.d.ts +47 -0
- package/src/tools/native-view.ts +102 -1
- package/src/tools/output-meta.d.ts +11 -1
- package/src/tools/output-meta.ts +23 -3
- package/src/tools/read.ts +7 -5
- package/src/tools/renderer.d.ts +37 -2
- package/src/tools/renderer.ts +42 -2
- package/src/tools/streaming-output.d.ts +20 -1
- package/src/tools/streaming-output.ts +154 -50
- package/src/tools/task.d.ts +2 -3
- package/src/tools/task.ts +115 -62
- package/src/tools/todo.ts +119 -30
- package/src/tools/wait.ts +34 -24
- package/src/tools/web-search.ts +1 -1
- package/src/tools/write.d.ts +9 -2
- package/src/tools/write.ts +147 -95
- package/src/tools/yield.d.ts +22 -0
- package/src/tools/yield.ts +225 -0
- package/src/tui.d.ts +36 -0
- package/src/tui.ts +666 -217
- package/src/utils.d.ts +6 -0
- package/src/utils.ts +19 -0
- package/src/vim.d.ts +19 -5
- package/src/vim.ts +333 -18
- package/src/windows-input-mode.d.ts +33 -1
- package/src/windows-input-mode.ts +212 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type
|
|
2
|
+
type Focusable,
|
|
3
3
|
Editor,
|
|
4
4
|
Ellipsis,
|
|
5
5
|
matchesKey,
|
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
visibleWidth,
|
|
12
12
|
wrapTextWithAnsi,
|
|
13
13
|
} from "../index";
|
|
14
|
-
import {
|
|
14
|
+
import { compositeLineAt } from "../render/composite";
|
|
15
|
+
import { wrapLiteralLine } from "../utils";
|
|
16
|
+
import { editorKey } from "../chrome/keybinding-hints";
|
|
15
17
|
import { formatKeyHint, formatKeyHints, type KeybindingsManager } from "../app-keybindings";
|
|
16
18
|
import type { Keybinding } from "../keybindings";
|
|
17
19
|
import { sanitizeText } from "@linxiraos/pi-utils";
|
|
@@ -36,11 +38,19 @@ import {
|
|
|
36
38
|
topBorder,
|
|
37
39
|
topBorderSplit,
|
|
38
40
|
} from "../chrome/overlay-box";
|
|
39
|
-
import { matchesAppExternalEditor } from "../keybinding-matchers";
|
|
41
|
+
import { appExternalEditorKey, matchesAppExternalEditor } from "../keybinding-matchers";
|
|
40
42
|
import type { KeyName } from "../key-hint-format";
|
|
41
43
|
import { item, keyed, node, row as rowNode, span, text } from "../native/describe";
|
|
42
44
|
import { leafKey, type NativeChild, type NativeNode, type NativeUiEvent } from "../native/node";
|
|
43
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
escCloseButton,
|
|
47
|
+
hintsRow,
|
|
48
|
+
itemIndex,
|
|
49
|
+
type NativeHint,
|
|
50
|
+
noteSpans,
|
|
51
|
+
overlayCard,
|
|
52
|
+
selectList,
|
|
53
|
+
} from "../native/overlay";
|
|
44
54
|
|
|
45
55
|
function fit(text: string, width: number): string {
|
|
46
56
|
if (width <= 0) return "";
|
|
@@ -60,6 +70,8 @@ export interface AnnotationOverlayCallbacks {
|
|
|
60
70
|
onWarning?(message: string): void;
|
|
61
71
|
/** Open the draft in the host's editor and invoke `commit` with its result. */
|
|
62
72
|
onAnnotationExternalEditor?(draft: string, commit: (text: string | null) => void): void | Promise<void>;
|
|
73
|
+
/** Open the current source in the host's editor. The overlay stays open. */
|
|
74
|
+
onExternalEditor?(): void | Promise<void>;
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
export interface CodeReviewOverlayCallbacks extends AnnotationOverlayCallbacks {
|
|
@@ -94,7 +106,8 @@ interface CommittedTextAnnotation {
|
|
|
94
106
|
interface RenderedDiffBody {
|
|
95
107
|
lines: string[];
|
|
96
108
|
renderedRowBySource: number[];
|
|
97
|
-
|
|
109
|
+
sourceIndexByRenderedRow: Array<number | undefined>;
|
|
110
|
+
selectedSourceLines: Array<string | undefined>;
|
|
98
111
|
}
|
|
99
112
|
|
|
100
113
|
interface RenderedBody {
|
|
@@ -136,7 +149,8 @@ function sanitizeStatusText(text: string): string {
|
|
|
136
149
|
.trim();
|
|
137
150
|
}
|
|
138
151
|
|
|
139
|
-
|
|
152
|
+
/** Splits a text source into the overlay's 1-based annotatable lines. */
|
|
153
|
+
export function splitTextLines(text: string): string[] {
|
|
140
154
|
return text.split(/\r\n|\n|\r/);
|
|
141
155
|
}
|
|
142
156
|
|
|
@@ -145,7 +159,8 @@ function isTextSource(value: readonly ReviewDiffFile[] | TextReviewSource): valu
|
|
|
145
159
|
}
|
|
146
160
|
|
|
147
161
|
/** A fullscreen, annotated diff picker that only relies on public OMP APIs. */
|
|
148
|
-
export class AnnotationOverlay implements
|
|
162
|
+
export class AnnotationOverlay implements Focusable {
|
|
163
|
+
focused = false;
|
|
149
164
|
#scrollView: ScrollView;
|
|
150
165
|
#editor: Editor;
|
|
151
166
|
#focus: FocusRegion = "files";
|
|
@@ -159,6 +174,8 @@ export class AnnotationOverlay implements Component {
|
|
|
159
174
|
#editingAnnotationIndex: number | undefined;
|
|
160
175
|
#annotationChooser: AnnotationChooser | undefined;
|
|
161
176
|
#externalOperation = false;
|
|
177
|
+
/** Set once {@link replaceTextSource} runs; the paste result then carries the edited text. */
|
|
178
|
+
#textSourceEdited = false;
|
|
162
179
|
#finished = false;
|
|
163
180
|
#annotations: CommittedAnnotation[] = [];
|
|
164
181
|
#textAnnotations: CommittedTextAnnotation[] = [];
|
|
@@ -167,9 +184,11 @@ export class AnnotationOverlay implements Component {
|
|
|
167
184
|
#actions: readonly string[] = CODE_REVIEW_ACTIONS;
|
|
168
185
|
#textSource: TextReviewSource | undefined;
|
|
169
186
|
#textLines: readonly string[] = [];
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
#
|
|
187
|
+
#viewportDriven = false;
|
|
188
|
+
/** Last viewport move went toward the end, so a viewport resting at its maximum offset selects the last visible row. */
|
|
189
|
+
#viewportTowardEnd = false;
|
|
190
|
+
#renderedRowBySource: readonly number[] = [];
|
|
191
|
+
#staticRenderedDiffBodies = new WeakMap<ReviewDiffFile, { width: number; body: RenderedDiffBody }>();
|
|
173
192
|
/** Bumped on every annotation change (each one pushes an undo snapshot) and on undo. */
|
|
174
193
|
#annotationRev = 0;
|
|
175
194
|
#nativeBody: NativeBody | undefined;
|
|
@@ -266,15 +285,16 @@ export class AnnotationOverlay implements Component {
|
|
|
266
285
|
});
|
|
267
286
|
this.#editor.setBorderVisible(false);
|
|
268
287
|
this.#editor.setPromptGutter("> ");
|
|
269
|
-
this.#editor.setUseTerminalCursor(false);
|
|
270
|
-
// Keep the editor's CURSOR_MARKER for terminal cursor placement, but
|
|
271
|
-
// replace its visible end-of-input caret with a stable blank cell.
|
|
272
|
-
this.#editor.cursorOverride = " ";
|
|
273
288
|
this.#editor.setScrollbarVisible(true);
|
|
274
289
|
this.#editor.onSubmit = value => this.#commitAnnotation(value);
|
|
275
290
|
this.#resetSourceCursor();
|
|
276
291
|
}
|
|
277
292
|
|
|
293
|
+
/** Keep the nested editor's cursor mode aligned with the overlay focus target. */
|
|
294
|
+
setUseTerminalCursor(useTerminalCursor: boolean): void {
|
|
295
|
+
this.#editor.setUseTerminalCursor(useTerminalCursor);
|
|
296
|
+
}
|
|
297
|
+
|
|
278
298
|
invalidate(): void {
|
|
279
299
|
this.#staticRenderedDiffBodies = new WeakMap();
|
|
280
300
|
}
|
|
@@ -291,6 +311,61 @@ export class AnnotationOverlay implements Component {
|
|
|
291
311
|
return this.#textAnnotations.map(entry => ({ ...entry.annotation }));
|
|
292
312
|
}
|
|
293
313
|
|
|
314
|
+
/** Repo-relative path of the diff file under the cursor, if this is a code review. */
|
|
315
|
+
reviewFilePath(): string | undefined {
|
|
316
|
+
const file = this.#files[this.#fileIndex];
|
|
317
|
+
if (!file) return undefined;
|
|
318
|
+
return file.newPath ?? file.oldPath ?? file.path;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Current text-source bytes, including edits applied by {@link replaceTextSource}. */
|
|
322
|
+
textSourceText(): string | undefined {
|
|
323
|
+
return this.#textSource?.text;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Replace the text under review. Line notes whose quote still sits on the
|
|
328
|
+
* same row stay; a quote that moved to exactly one row is retargeted;
|
|
329
|
+
* every other line note is dropped. Returns how many were dropped.
|
|
330
|
+
*/
|
|
331
|
+
replaceTextSource(text: string): number {
|
|
332
|
+
if (!this.#textSource) return 0;
|
|
333
|
+
this.#textSource = { ...this.#textSource, text };
|
|
334
|
+
this.#textSourceEdited = true;
|
|
335
|
+
this.#textLines = splitTextLines(text);
|
|
336
|
+
this.#viewportDriven = false;
|
|
337
|
+
this.#sourceIndex = Math.min(this.#sourceIndex, Math.max(0, this.#textLines.length - 1));
|
|
338
|
+
let dropped = 0;
|
|
339
|
+
const kept: CommittedTextAnnotation[] = [];
|
|
340
|
+
for (const entry of this.#textAnnotations) {
|
|
341
|
+
if (entry.annotation.scope !== "line") {
|
|
342
|
+
kept.push(entry);
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const quote = entry.annotation.quote;
|
|
346
|
+
if (this.#textLines[entry.sourceIndex] === quote) {
|
|
347
|
+
kept.push(entry);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const matches = this.#textLines.flatMap((line, index) => (line === quote ? [index] : []));
|
|
351
|
+
const only = matches.length === 1 ? matches[0] : undefined;
|
|
352
|
+
if (only !== undefined) {
|
|
353
|
+
kept.push({
|
|
354
|
+
sourceIndex: only,
|
|
355
|
+
annotation: { ...entry.annotation, line: only + 1 },
|
|
356
|
+
});
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
dropped++;
|
|
360
|
+
}
|
|
361
|
+
this.#textAnnotations = kept;
|
|
362
|
+
// Earlier snapshots hold notes anchored to the old text; undo must not resurrect them.
|
|
363
|
+
this.#undoStack = [];
|
|
364
|
+
this.#annotationRev++;
|
|
365
|
+
this.#nativeBody = undefined;
|
|
366
|
+
return dropped;
|
|
367
|
+
}
|
|
368
|
+
|
|
294
369
|
handleInput(data: string): void {
|
|
295
370
|
if (this.#finished || this.#externalOperation) return;
|
|
296
371
|
if (this.#annotationChooser) {
|
|
@@ -310,7 +385,11 @@ export class AnnotationOverlay implements Component {
|
|
|
310
385
|
return;
|
|
311
386
|
}
|
|
312
387
|
if (this.#keybindings.matches(data, "tui.select.cancel")) {
|
|
313
|
-
this.#
|
|
388
|
+
this.#escape();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (this.#callbacks.onExternalEditor && matchesAppExternalEditor(data)) {
|
|
392
|
+
void this.#openSourceEditor();
|
|
314
393
|
return;
|
|
315
394
|
}
|
|
316
395
|
if (data === "A" && (this.#focus === "files" || this.#focus === "diff")) {
|
|
@@ -362,6 +441,13 @@ export class AnnotationOverlay implements Component {
|
|
|
362
441
|
}
|
|
363
442
|
}
|
|
364
443
|
|
|
444
|
+
/** Esc, by state: leave the note chooser, drop the note draft, else close the review. */
|
|
445
|
+
#escape(): void {
|
|
446
|
+
if (this.#annotationChooser) this.#annotationChooser = undefined;
|
|
447
|
+
else if (this.#annotating) this.#cancelAnnotation();
|
|
448
|
+
else this.#finish(undefined);
|
|
449
|
+
}
|
|
450
|
+
|
|
365
451
|
#cycleFocus(direction: number): void {
|
|
366
452
|
const regions: FocusRegion[] = this.#sidebarShown ? ["files", "diff", "actions"] : ["diff", "actions"];
|
|
367
453
|
const current = regions.indexOf(this.#focus);
|
|
@@ -417,34 +503,48 @@ export class AnnotationOverlay implements Component {
|
|
|
417
503
|
return;
|
|
418
504
|
}
|
|
419
505
|
if (this.#keybindings.matches(data, "tui.select.pageUp")) {
|
|
420
|
-
if (this.#textSource) {
|
|
421
|
-
this.#scrollView.page(-1);
|
|
422
|
-
this.#textViewportDriven = true;
|
|
423
|
-
this.#syncTextCursorToViewport();
|
|
424
|
-
} else {
|
|
506
|
+
if (this.#tui.nativeRendering && !this.#textSource) {
|
|
425
507
|
this.#moveSourceCursor(-Math.max(1, this.#bodyHeight - 1));
|
|
508
|
+
} else {
|
|
509
|
+
this.#scrollView.page(-1);
|
|
510
|
+
this.#viewportDriven = true;
|
|
511
|
+
this.#viewportTowardEnd = false;
|
|
512
|
+
this.#syncCursorToViewport();
|
|
426
513
|
}
|
|
427
514
|
return;
|
|
428
515
|
}
|
|
429
516
|
if (this.#keybindings.matches(data, "tui.select.pageDown")) {
|
|
430
|
-
if (this.#textSource) {
|
|
431
|
-
this.#scrollView.page(1);
|
|
432
|
-
this.#textViewportDriven = true;
|
|
433
|
-
this.#syncTextCursorToViewport();
|
|
434
|
-
} else {
|
|
517
|
+
if (this.#tui.nativeRendering && !this.#textSource) {
|
|
435
518
|
this.#moveSourceCursor(Math.max(1, this.#bodyHeight - 1));
|
|
519
|
+
} else {
|
|
520
|
+
this.#scrollView.page(1);
|
|
521
|
+
this.#viewportDriven = true;
|
|
522
|
+
this.#viewportTowardEnd = true;
|
|
523
|
+
this.#syncCursorToViewport();
|
|
436
524
|
}
|
|
437
525
|
return;
|
|
438
526
|
}
|
|
439
527
|
if (data === "g" || matchesKey(data, "home")) {
|
|
440
528
|
this.#sourceIndex = 0;
|
|
441
|
-
this.#
|
|
529
|
+
if (this.#textSource || this.#tui.nativeRendering) {
|
|
530
|
+
this.#viewportDriven = false;
|
|
531
|
+
} else {
|
|
532
|
+
this.#scrollView.scrollToTop();
|
|
533
|
+
this.#viewportDriven = true;
|
|
534
|
+
this.#viewportTowardEnd = false;
|
|
535
|
+
}
|
|
442
536
|
} else if (data === "G" || matchesKey(data, "end")) {
|
|
443
537
|
this.#sourceIndex = Math.max(
|
|
444
538
|
0,
|
|
445
539
|
(this.#textSource ? this.#textLines.length : this.#currentSourceRows().length) - 1,
|
|
446
540
|
);
|
|
447
|
-
this.#
|
|
541
|
+
if (this.#textSource || this.#tui.nativeRendering) {
|
|
542
|
+
this.#viewportDriven = false;
|
|
543
|
+
} else {
|
|
544
|
+
this.#scrollView.scrollToBottom();
|
|
545
|
+
this.#viewportDriven = true;
|
|
546
|
+
this.#viewportTowardEnd = true;
|
|
547
|
+
}
|
|
448
548
|
}
|
|
449
549
|
}
|
|
450
550
|
|
|
@@ -478,6 +578,7 @@ export class AnnotationOverlay implements Component {
|
|
|
478
578
|
this.#finish({
|
|
479
579
|
action: "paste",
|
|
480
580
|
annotations: this.getTextAnnotations(),
|
|
581
|
+
...(this.#textSourceEdited ? { editedText: this.#textSource.text } : {}),
|
|
481
582
|
});
|
|
482
583
|
} else {
|
|
483
584
|
this.#finish({
|
|
@@ -501,6 +602,7 @@ export class AnnotationOverlay implements Component {
|
|
|
501
602
|
|
|
502
603
|
#resetSourceCursor(): void {
|
|
503
604
|
this.#sourceIndex = 0;
|
|
605
|
+
this.#viewportDriven = false;
|
|
504
606
|
}
|
|
505
607
|
|
|
506
608
|
#currentFile(): ReviewDiffFile | undefined {
|
|
@@ -511,20 +613,20 @@ export class AnnotationOverlay implements Component {
|
|
|
511
613
|
const file = this.#currentFile();
|
|
512
614
|
return file ? file.rows.filter(isSourceRow) : [];
|
|
513
615
|
}
|
|
514
|
-
#
|
|
515
|
-
if (
|
|
616
|
+
#syncCursorToViewport(): void {
|
|
617
|
+
if (this.#renderedRowBySource.length === 0) return;
|
|
516
618
|
const viewportTop = this.#scrollView.getScrollOffset();
|
|
517
619
|
const viewportBottom = viewportTop + Math.max(0, this.#bodyHeight - 1);
|
|
518
620
|
let topSourceIndex = 0;
|
|
519
621
|
let lastVisibleSourceIndex = 0;
|
|
520
|
-
for (let index = 1; index < this.#
|
|
521
|
-
const renderedRow = this.#
|
|
622
|
+
for (let index = 1; index < this.#renderedRowBySource.length; index++) {
|
|
623
|
+
const renderedRow = this.#renderedRowBySource[index];
|
|
522
624
|
if (renderedRow === undefined || renderedRow > viewportBottom) break;
|
|
523
625
|
lastVisibleSourceIndex = index;
|
|
524
626
|
if (renderedRow <= viewportTop) topSourceIndex = index;
|
|
525
627
|
}
|
|
526
|
-
|
|
527
|
-
|
|
628
|
+
// A short pane has no scroll range, so paging toward the end must still reach the last row.
|
|
629
|
+
const atBottom = this.#viewportTowardEnd && viewportTop >= this.#scrollView.getMaxScrollOffset();
|
|
528
630
|
this.#sourceIndex = atBottom ? lastVisibleSourceIndex : topSourceIndex;
|
|
529
631
|
}
|
|
530
632
|
|
|
@@ -532,7 +634,7 @@ export class AnnotationOverlay implements Component {
|
|
|
532
634
|
const rowCount = this.#textSource ? this.#textLines.length : this.#currentSourceRows().length;
|
|
533
635
|
if (rowCount === 0) return;
|
|
534
636
|
this.#sourceIndex = Math.max(0, Math.min(rowCount - 1, this.#sourceIndex + delta));
|
|
535
|
-
|
|
637
|
+
this.#viewportDriven = false;
|
|
536
638
|
}
|
|
537
639
|
|
|
538
640
|
#startAnnotation(scope: AnnotationScope, existingIndex?: number): void {
|
|
@@ -795,6 +897,22 @@ export class AnnotationOverlay implements Component {
|
|
|
795
897
|
}
|
|
796
898
|
}
|
|
797
899
|
|
|
900
|
+
async #openSourceEditor(): Promise<void> {
|
|
901
|
+
const openEditor = this.#callbacks.onExternalEditor;
|
|
902
|
+
if (this.#externalOperation || !openEditor) return;
|
|
903
|
+
this.#externalOperation = true;
|
|
904
|
+
try {
|
|
905
|
+
await openEditor();
|
|
906
|
+
} catch (error) {
|
|
907
|
+
this.#callbacks.onWarning?.(
|
|
908
|
+
`Failed to open external editor: ${error instanceof Error ? error.message : String(error)}`,
|
|
909
|
+
);
|
|
910
|
+
} finally {
|
|
911
|
+
this.#externalOperation = false;
|
|
912
|
+
this.#tui.requestRender(true);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
798
916
|
#annotationCount(fileIndex: number): number {
|
|
799
917
|
let count = 0;
|
|
800
918
|
for (const entry of this.#annotations) if (entry.fileIndex === fileIndex) count++;
|
|
@@ -809,7 +927,7 @@ export class AnnotationOverlay implements Component {
|
|
|
809
927
|
lines: [this.#theme.fg("dim", "No reviewable files")],
|
|
810
928
|
renderedRowBySource: [],
|
|
811
929
|
};
|
|
812
|
-
const staticBody = this.#getStaticRenderedBody(file);
|
|
930
|
+
const staticBody = this.#getStaticRenderedBody(file, contentWidth);
|
|
813
931
|
const lines: string[] = [];
|
|
814
932
|
const renderedRowBySource: number[] = [];
|
|
815
933
|
|
|
@@ -821,12 +939,10 @@ export class AnnotationOverlay implements Component {
|
|
|
821
939
|
}
|
|
822
940
|
}
|
|
823
941
|
|
|
824
|
-
let sourceIndex = 0;
|
|
825
942
|
for (let staticRow = 0; staticRow < staticBody.lines.length; staticRow++) {
|
|
826
|
-
const currentSourceIndex =
|
|
827
|
-
staticBody.renderedRowBySource[sourceIndex] === staticRow ? sourceIndex++ : undefined;
|
|
943
|
+
const currentSourceIndex = staticBody.sourceIndexByRenderedRow[staticRow];
|
|
828
944
|
let renderedLine = staticBody.lines[staticRow] ?? "";
|
|
829
|
-
if (currentSourceIndex !== undefined) {
|
|
945
|
+
if (currentSourceIndex !== undefined && staticBody.renderedRowBySource[currentSourceIndex] === staticRow) {
|
|
830
946
|
// Line notes belong immediately before their source row. This keeps
|
|
831
947
|
// the selected row and all notes in a stable, readable order.
|
|
832
948
|
for (const entry of this.#annotations) {
|
|
@@ -839,12 +955,10 @@ export class AnnotationOverlay implements Component {
|
|
|
839
955
|
}
|
|
840
956
|
}
|
|
841
957
|
renderedRowBySource[currentSourceIndex] = lines.length;
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
);
|
|
847
|
-
}
|
|
958
|
+
}
|
|
959
|
+
if (this.#focus === "diff" && currentSourceIndex === this.#sourceIndex) {
|
|
960
|
+
renderedLine = staticBody.selectedSourceLines[staticRow] ?? renderedLine;
|
|
961
|
+
renderedLine = this.#theme.bg("selectedBg", fit(renderedLine, contentWidth));
|
|
848
962
|
}
|
|
849
963
|
lines.push(truncateToWidth(renderedLine, contentWidth));
|
|
850
964
|
}
|
|
@@ -870,8 +984,7 @@ export class AnnotationOverlay implements Component {
|
|
|
870
984
|
}
|
|
871
985
|
renderedRowBySource[sourceIndex] = lines.length;
|
|
872
986
|
const displayLine = replaceTabs(sanitizeText(sourceLine));
|
|
873
|
-
const
|
|
874
|
-
const visualRows = wrapped.length > 0 ? wrapped : [""];
|
|
987
|
+
const visualRows = wrapLiteralLine(displayLine, textWidth);
|
|
875
988
|
for (const [rowIndex, visualRow] of visualRows.entries()) {
|
|
876
989
|
const selected = this.#focus === "diff" && sourceIndex === this.#sourceIndex;
|
|
877
990
|
const gutter =
|
|
@@ -885,47 +998,63 @@ export class AnnotationOverlay implements Component {
|
|
|
885
998
|
return { lines, renderedRowBySource };
|
|
886
999
|
}
|
|
887
1000
|
|
|
888
|
-
#getStaticRenderedBody(file: ReviewDiffFile): RenderedDiffBody {
|
|
1001
|
+
#getStaticRenderedBody(file: ReviewDiffFile, contentWidth: number): RenderedDiffBody {
|
|
1002
|
+
const normalizedWidth = Math.max(1, Math.floor(contentWidth));
|
|
889
1003
|
const cached = this.#staticRenderedDiffBodies.get(file);
|
|
890
|
-
if (cached) return cached;
|
|
1004
|
+
if (cached?.width === normalizedWidth) return cached.body;
|
|
891
1005
|
const lines: string[] = [];
|
|
892
1006
|
const renderedRowBySource: number[] = [];
|
|
893
|
-
const
|
|
1007
|
+
const sourceIndexByRenderedRow: Array<number | undefined> = [];
|
|
1008
|
+
const selectedSourceLines: Array<string | undefined> = [];
|
|
1009
|
+
const pushStaticLine = (line: string) => {
|
|
1010
|
+
lines.push(line);
|
|
1011
|
+
sourceIndexByRenderedRow.push(undefined);
|
|
1012
|
+
selectedSourceLines.push(undefined);
|
|
1013
|
+
};
|
|
894
1014
|
if (file.isBinary) {
|
|
895
|
-
|
|
1015
|
+
pushStaticLine(this.#theme.fg("dim", "Binary diff; no annotatable source rows"));
|
|
896
1016
|
} else if (file.rows.length === 0) {
|
|
897
|
-
|
|
1017
|
+
pushStaticLine(this.#theme.fg("dim", "No diff hunks; this may be a rename-only change"));
|
|
898
1018
|
} else {
|
|
899
1019
|
let sourceIndex = 0;
|
|
900
1020
|
for (const diffRow of file.rows) {
|
|
901
1021
|
if (!isSourceRow(diffRow)) {
|
|
902
|
-
|
|
1022
|
+
pushStaticLine(
|
|
903
1023
|
this.#theme.fg(diffRow.kind === "hunk" ? "accent" : "dim", replaceTabs(sanitizeText(diffRow.raw))),
|
|
904
1024
|
);
|
|
905
1025
|
continue;
|
|
906
1026
|
}
|
|
907
|
-
renderedRowBySource[sourceIndex] = lines.length;
|
|
908
1027
|
const marker = diffRow.kind === "added" ? "+" : diffRow.kind === "removed" ? "-" : " ";
|
|
909
1028
|
const lineNumber = diffRow.kind === "removed" ? diffRow.oldLine : (diffRow.newLine ?? diffRow.oldLine);
|
|
910
|
-
const
|
|
911
|
-
const
|
|
1029
|
+
const prefix = `${marker}${String(lineNumber ?? "").padStart(4)} `;
|
|
1030
|
+
const prefixWidth = visibleWidth(prefix);
|
|
1031
|
+
const contentColor: DiffColor =
|
|
912
1032
|
diffRow.kind === "added"
|
|
913
1033
|
? "toolDiffAdded"
|
|
914
1034
|
: diffRow.kind === "removed"
|
|
915
1035
|
? "toolDiffRemoved"
|
|
916
1036
|
: "toolDiffContext";
|
|
917
|
-
const
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
)
|
|
923
|
-
|
|
1037
|
+
const sourceContent = replaceTabs(sanitizeText(diffRow.content));
|
|
1038
|
+
const contentWidthForRow = Math.max(1, normalizedWidth - SOURCE_SELECTION_GUTTER_WIDTH - prefixWidth);
|
|
1039
|
+
const visualRows = wrapLiteralLine(sourceContent, contentWidthForRow);
|
|
1040
|
+
const firstRenderedRow = lines.length;
|
|
1041
|
+
renderedRowBySource[sourceIndex] = firstRenderedRow;
|
|
1042
|
+
for (const [rowIndex, visualRow] of visualRows.entries()) {
|
|
1043
|
+
const first = rowIndex === 0;
|
|
1044
|
+
const gutter = " ".repeat(SOURCE_SELECTION_GUTTER_WIDTH);
|
|
1045
|
+
const continuationPrefix = " ".repeat(prefixWidth);
|
|
1046
|
+
const prefixForRow = first ? this.#theme.fg(contentColor, prefix) : continuationPrefix;
|
|
1047
|
+
const selectedGutter = first ? fit(`${this.#theme.nav.cursor} `, SOURCE_SELECTION_GUTTER_WIDTH) : gutter;
|
|
1048
|
+
const styledRow = this.#theme.fg(contentColor, visualRow);
|
|
1049
|
+
lines.push(`${gutter}${prefixForRow}${styledRow}`);
|
|
1050
|
+
sourceIndexByRenderedRow.push(sourceIndex);
|
|
1051
|
+
selectedSourceLines.push(`${selectedGutter}${prefixForRow}${styledRow}`);
|
|
1052
|
+
}
|
|
924
1053
|
sourceIndex++;
|
|
925
1054
|
}
|
|
926
1055
|
}
|
|
927
|
-
const body = { lines, renderedRowBySource, selectedSourceLines };
|
|
928
|
-
this.#staticRenderedDiffBodies.set(file, body);
|
|
1056
|
+
const body = { lines, renderedRowBySource, sourceIndexByRenderedRow, selectedSourceLines };
|
|
1057
|
+
this.#staticRenderedDiffBodies.set(file, { width: normalizedWidth, body });
|
|
929
1058
|
return body;
|
|
930
1059
|
}
|
|
931
1060
|
|
|
@@ -1027,6 +1156,7 @@ export class AnnotationOverlay implements Component {
|
|
|
1027
1156
|
}
|
|
1028
1157
|
|
|
1029
1158
|
#renderFooter(width: number, maxChooserRows = Number.POSITIVE_INFINITY): string[] {
|
|
1159
|
+
this.#editor.focused = this.focused && this.#annotating;
|
|
1030
1160
|
if (this.#annotationChooser) {
|
|
1031
1161
|
return this.#renderAnnotationChooser(width, maxChooserRows);
|
|
1032
1162
|
}
|
|
@@ -1042,9 +1172,7 @@ export class AnnotationOverlay implements Component {
|
|
|
1042
1172
|
`${editorKey("tui.input.newLine")} newline`,
|
|
1043
1173
|
`${this.#key("tui.select.cancel")} cancel`,
|
|
1044
1174
|
];
|
|
1045
|
-
|
|
1046
|
-
if (externalEditorKey) hints.push(`${externalEditorKey} editor`);
|
|
1047
|
-
this.#editor.focused = true;
|
|
1175
|
+
hints.push(`${formatKeyHint(appExternalEditorKey())} editor`);
|
|
1048
1176
|
return [caption, ...this.#editor.render(width), this.#theme.fg("dim", hints.join(" · "))];
|
|
1049
1177
|
}
|
|
1050
1178
|
return [this.#theme.fg("dim", this.#helpText())];
|
|
@@ -1095,16 +1223,23 @@ export class AnnotationOverlay implements Component {
|
|
|
1095
1223
|
: this.#focus === "diff"
|
|
1096
1224
|
? `${upDown} line · ${formatKeyHint("shift")} faster · ${this.#key("tui.select.pageUp")}/${this.#key("tui.select.pageDown")} · ${formatKeyHints(["g", "shift+g"])} ends · ${formatKeyHint("a")} line note · ${formatKeyHint("shift+a")} ${this.#textSource ? "text" : "file"} note · ${editNote}`
|
|
1097
1225
|
: `${upDown} select · ${confirm} confirm`;
|
|
1098
|
-
|
|
1226
|
+
const editorHint = this.#callbacks.onExternalEditor ? ` · ${formatKeyHint(appExternalEditorKey())} editor` : "";
|
|
1227
|
+
return `${focusHelp} · ${formatKeyHint("u")} undo · ${formatKeyHint("tab")} regions${editorHint} · ${this.#key("tui.select.cancel")} cancel`;
|
|
1099
1228
|
}
|
|
1100
1229
|
|
|
1101
|
-
#ensureCursorVisible(renderedRowBySource: readonly number[]):
|
|
1102
|
-
|
|
1230
|
+
#ensureCursorVisible(renderedRowBySource: readonly number[]): boolean {
|
|
1231
|
+
this.#renderedRowBySource = renderedRowBySource;
|
|
1232
|
+
if (this.#viewportDriven) {
|
|
1233
|
+
const sourceIndex = this.#sourceIndex;
|
|
1234
|
+
if (!this.#annotating && !this.#annotationChooser) this.#syncCursorToViewport();
|
|
1235
|
+
return this.#sourceIndex !== sourceIndex;
|
|
1236
|
+
}
|
|
1103
1237
|
const rowIndex = renderedRowBySource[this.#sourceIndex];
|
|
1104
|
-
if (rowIndex === undefined) return;
|
|
1238
|
+
if (rowIndex === undefined) return false;
|
|
1105
1239
|
const offset = this.#scrollView.getScrollOffset();
|
|
1106
1240
|
if (rowIndex < offset) this.#scrollView.setScrollOffset(rowIndex);
|
|
1107
1241
|
else if (rowIndex >= offset + this.#bodyHeight) this.#scrollView.setScrollOffset(rowIndex - this.#bodyHeight + 1);
|
|
1242
|
+
return false;
|
|
1108
1243
|
}
|
|
1109
1244
|
|
|
1110
1245
|
#sidebarWidth(width: number): number {
|
|
@@ -1117,19 +1252,31 @@ export class AnnotationOverlay implements Component {
|
|
|
1117
1252
|
);
|
|
1118
1253
|
}
|
|
1119
1254
|
|
|
1255
|
+
#sidebarStart(rows: number): number {
|
|
1256
|
+
return Math.max(0, Math.min(this.#fileIndex - Math.floor(rows / 2), Math.max(0, this.#files.length - rows)));
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
#renderSidebarBadge(file: ReviewDiffFile, count: number): string {
|
|
1260
|
+
const changes = ` +${file.linesAdded}/-${file.linesRemoved}`;
|
|
1261
|
+
const annotations = count ? ` ✎${count}` : "";
|
|
1262
|
+
return this.#theme.fg("dim", changes) + (annotations ? this.#theme.fg("warning", annotations) : "");
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
#sidebarLabelWidth(file: ReviewDiffFile, count: number, width: number): number {
|
|
1266
|
+
const badge = this.#renderSidebarBadge(file, count);
|
|
1267
|
+
return Math.max(0, width - visibleWidth(badge) - 2);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1120
1270
|
#renderSidebar(rows: number, width: number): string[] {
|
|
1121
|
-
const start =
|
|
1122
|
-
0,
|
|
1123
|
-
Math.min(this.#fileIndex - Math.floor(rows / 2), Math.max(0, this.#files.length - rows)),
|
|
1124
|
-
);
|
|
1271
|
+
const start = this.#sidebarStart(rows);
|
|
1125
1272
|
return Array.from({ length: rows }, (_, rowIndex) => {
|
|
1126
1273
|
const index = start + rowIndex;
|
|
1127
1274
|
const file = this.#files[index];
|
|
1128
1275
|
if (!file) return "";
|
|
1129
1276
|
const selected = index === this.#fileIndex;
|
|
1130
1277
|
const count = this.#annotationCount(index);
|
|
1131
|
-
const badge =
|
|
1132
|
-
const available =
|
|
1278
|
+
const badge = this.#renderSidebarBadge(file, count);
|
|
1279
|
+
const available = this.#sidebarLabelWidth(file, count, width);
|
|
1133
1280
|
const label = truncateToWidth(sanitizeStatusText(displayFileLabel(file)), available, Ellipsis.Unicode);
|
|
1134
1281
|
const cursor = selected ? (this.#focus === "files" ? "› " : "▎ ") : " ";
|
|
1135
1282
|
const line = fit(`${cursor}${label}${badge}`, width);
|
|
@@ -1139,13 +1286,59 @@ export class AnnotationOverlay implements Component {
|
|
|
1139
1286
|
});
|
|
1140
1287
|
}
|
|
1141
1288
|
|
|
1289
|
+
#revealSelectedFile(output: string[], width: number): void {
|
|
1290
|
+
const file = this.#currentFile();
|
|
1291
|
+
if (
|
|
1292
|
+
!file ||
|
|
1293
|
+
!this.#sidebarShown ||
|
|
1294
|
+
this.#textSource ||
|
|
1295
|
+
this.#focus !== "files" ||
|
|
1296
|
+
this.#annotating ||
|
|
1297
|
+
this.#annotationChooser
|
|
1298
|
+
) {
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
const pathColumn = 4;
|
|
1303
|
+
const lineWidth = Math.max(0, width - pathColumn - 1);
|
|
1304
|
+
if (lineWidth === 0) return;
|
|
1305
|
+
|
|
1306
|
+
const selectedRow = 1 + this.#fileIndex - this.#sidebarStart(this.#bodyHeight + 1);
|
|
1307
|
+
const interiorTop = 1;
|
|
1308
|
+
const lastBodyRow = this.#bodyHeight + 1;
|
|
1309
|
+
const interiorRows = lastBodyRow - interiorTop + 1;
|
|
1310
|
+
if (selectedRow < interiorTop || selectedRow > lastBodyRow || interiorRows <= 0) return;
|
|
1311
|
+
|
|
1312
|
+
const label = replaceTabs(sanitizeStatusText(displayFileLabel(file)));
|
|
1313
|
+
if (!label) return;
|
|
1314
|
+
const count = this.#annotationCount(this.#fileIndex);
|
|
1315
|
+
if (visibleWidth(label) <= this.#sidebarLabelWidth(file, count, this.#sidebarWidth(width))) return;
|
|
1316
|
+
|
|
1317
|
+
let wrapped = wrapTextWithAnsi(label, lineWidth);
|
|
1318
|
+
if (wrapped.length > interiorRows) {
|
|
1319
|
+
wrapped = wrapped.slice(-interiorRows);
|
|
1320
|
+
wrapped[0] = truncateToWidth(`…${wrapped[0] ?? ""}`, lineWidth, Ellipsis.Omit);
|
|
1321
|
+
}
|
|
1322
|
+
const overlayTop = Math.max(interiorTop, Math.min(selectedRow, lastBodyRow - wrapped.length + 1));
|
|
1323
|
+
const overlayWidth = Math.min(lineWidth, Math.max(0, ...wrapped.map(visibleWidth)) + 1);
|
|
1324
|
+
for (const [index, line] of wrapped.entries()) {
|
|
1325
|
+
const highlighted = this.#theme.bg("selectedBg", this.#theme.bold(fit(line, overlayWidth)));
|
|
1326
|
+
const targetRow = overlayTop + index;
|
|
1327
|
+
const baseLine = output[targetRow];
|
|
1328
|
+
if (overlayWidth > 0 && baseLine !== undefined) {
|
|
1329
|
+
output[targetRow] = compositeLineAt(baseLine, highlighted, pathColumn, overlayWidth, width);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1142
1334
|
describe(): NativeNode {
|
|
1143
1335
|
// The terminal lays out the file sidebar; diffs always have one, text never does.
|
|
1144
1336
|
this.#sidebarShown = !this.#textSource;
|
|
1145
1337
|
if (!this.#sidebarShown && this.#focus === "files") this.#focus = "diff";
|
|
1146
1338
|
// Page size for PgUp/PgDn, matching the rendered body height.
|
|
1147
|
-
|
|
1148
|
-
this.#
|
|
1339
|
+
const terminalHeight = this.#tui.terminal.rows;
|
|
1340
|
+
this.#bodyHeight = Math.max(MIN_BODY_ROWS, terminalHeight - (this.#actions.length + 8));
|
|
1341
|
+
this.#editor.focused = this.focused && this.#annotating;
|
|
1149
1342
|
const body = this.#describeBodyItems();
|
|
1150
1343
|
const chooser = this.#annotationChooser;
|
|
1151
1344
|
const sig = [
|
|
@@ -1166,7 +1359,10 @@ export class AnnotationOverlay implements Component {
|
|
|
1166
1359
|
role: this.#textSource ? "zeta.overlay.annotateText.lines" : "zeta.overlay.codeReview.diff",
|
|
1167
1360
|
tone: this.#focus === "diff" ? "accent" : undefined,
|
|
1168
1361
|
});
|
|
1169
|
-
|
|
1362
|
+
// The header row ends in a clickable `esc` at the top right.
|
|
1363
|
+
const title = node("col", { grow: 1, min: { w: 0 } }, [this.#describeHeader()], "title");
|
|
1364
|
+
const head = keyed(rowNode([title, escCloseButton()], { gap: "md", align: "center" }), "head");
|
|
1365
|
+
const main = node("col", { grow: 1 }, [head, lines], "main");
|
|
1170
1366
|
if (this.#sidebarShown) {
|
|
1171
1367
|
const files = selectList(
|
|
1172
1368
|
"files",
|
|
@@ -1215,6 +1411,10 @@ export class AnnotationOverlay implements Component {
|
|
|
1215
1411
|
|
|
1216
1412
|
handleNativeEvent(event: NativeUiEvent): void {
|
|
1217
1413
|
if (this.#finished || this.#externalOperation) return;
|
|
1414
|
+
if (event.type === "action" && event.act === "close") {
|
|
1415
|
+
this.#escape();
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1218
1418
|
if (event.type !== "select" && event.type !== "activate") return;
|
|
1219
1419
|
const activate = event.type === "activate";
|
|
1220
1420
|
const leaf = leafKey(event.key);
|
|
@@ -1244,7 +1444,7 @@ export class AnnotationOverlay implements Component {
|
|
|
1244
1444
|
if (index < 0 || index >= rowCount) return;
|
|
1245
1445
|
this.#focus = "diff";
|
|
1246
1446
|
this.#sourceIndex = index;
|
|
1247
|
-
this.#
|
|
1447
|
+
this.#viewportDriven = false;
|
|
1248
1448
|
if (activate) this.#focus = "actions";
|
|
1249
1449
|
return;
|
|
1250
1450
|
}
|
|
@@ -1404,7 +1604,7 @@ export class AnnotationOverlay implements Component {
|
|
|
1404
1604
|
this.#hint("tui.input.submit", "save"),
|
|
1405
1605
|
this.#hint("tui.input.newLine", "newline"),
|
|
1406
1606
|
this.#hint("tui.select.cancel", "cancel"),
|
|
1407
|
-
|
|
1607
|
+
{ keys: [appExternalEditorKey()], label: "editor" },
|
|
1408
1608
|
],
|
|
1409
1609
|
"annotateHints",
|
|
1410
1610
|
),
|
|
@@ -1430,6 +1630,7 @@ export class AnnotationOverlay implements Component {
|
|
|
1430
1630
|
editNote,
|
|
1431
1631
|
]
|
|
1432
1632
|
: [upDown("select"), this.#hint("tui.select.confirm", "confirm")];
|
|
1633
|
+
if (this.#callbacks.onExternalEditor) hints.push({ keys: [appExternalEditorKey()], label: "editor" });
|
|
1433
1634
|
hints.push(key("u", "undo"), key("tab", "regions"), this.#hint("tui.select.cancel", "cancel"));
|
|
1434
1635
|
return [hintsRow(hints)];
|
|
1435
1636
|
}
|
|
@@ -1445,14 +1646,14 @@ export class AnnotationOverlay implements Component {
|
|
|
1445
1646
|
}
|
|
1446
1647
|
|
|
1447
1648
|
render(width: number): readonly string[] {
|
|
1448
|
-
const terminalHeight =
|
|
1649
|
+
const terminalHeight = this.#tui.terminal.rows;
|
|
1449
1650
|
this.#sidebarShown = this.#textSource ? false : this.#canShowSidebar(width);
|
|
1450
1651
|
if (!this.#sidebarShown && this.#focus === "files") this.#focus = "diff";
|
|
1451
1652
|
const sidebarWidth = this.#sidebarShown ? this.#sidebarWidth(width) : 0;
|
|
1452
1653
|
const innerWidth = Math.max(1, width - 4);
|
|
1453
1654
|
const bodyWidth = this.#sidebarShown ? splitBodyWidth(width, sidebarWidth) : innerWidth;
|
|
1454
1655
|
this.#editor.setMaxHeight(Math.max(1, Math.min(MAX_ANNOTATION_EDITOR_ROWS, terminalHeight - 12)));
|
|
1455
|
-
this.#editor.focused = this.#annotating;
|
|
1656
|
+
this.#editor.focused = this.focused && this.#annotating;
|
|
1456
1657
|
const chooserBaseRows = terminalHeight - (this.#actions.length + 7);
|
|
1457
1658
|
const chooserFooterRows = this.#annotationChooser
|
|
1458
1659
|
? Math.max(0, Math.min(chooserBaseRows, Math.max(3, chooserBaseRows - MIN_BODY_ROWS)))
|
|
@@ -1463,14 +1664,15 @@ export class AnnotationOverlay implements Component {
|
|
|
1463
1664
|
this.#bodyHeight = this.#annotationChooser
|
|
1464
1665
|
? Math.max(0, availableBodyRows)
|
|
1465
1666
|
: Math.max(MIN_BODY_ROWS, availableBodyRows);
|
|
1466
|
-
const
|
|
1667
|
+
const bodyContentWidth = Math.max(1, bodyWidth - 1);
|
|
1668
|
+
let renderedBody = this.#renderBody(bodyContentWidth);
|
|
1467
1669
|
this.#scrollView.setLines(renderedBody.lines);
|
|
1468
1670
|
this.#scrollView.setHeight(this.#bodyHeight);
|
|
1469
|
-
if (this.#
|
|
1470
|
-
|
|
1471
|
-
|
|
1671
|
+
if (this.#ensureCursorVisible(renderedBody.renderedRowBySource)) {
|
|
1672
|
+
renderedBody = this.#renderBody(bodyContentWidth);
|
|
1673
|
+
this.#scrollView.setLines(renderedBody.lines);
|
|
1674
|
+
this.#renderedRowBySource = renderedBody.renderedRowBySource;
|
|
1472
1675
|
}
|
|
1473
|
-
this.#ensureCursorVisible(renderedBody.renderedRowBySource);
|
|
1474
1676
|
const body = this.#scrollView.render(bodyWidth);
|
|
1475
1677
|
const output: string[] = [];
|
|
1476
1678
|
if (this.#sidebarShown) {
|
|
@@ -1492,6 +1694,7 @@ export class AnnotationOverlay implements Component {
|
|
|
1492
1694
|
output.push(divider(width));
|
|
1493
1695
|
for (const footerLine of footer) output.push(row(footerLine, width));
|
|
1494
1696
|
output.push(bottomBorder(width));
|
|
1697
|
+
this.#revealSelectedFile(output, width);
|
|
1495
1698
|
return output;
|
|
1496
1699
|
}
|
|
1497
1700
|
}
|