@deepseek-harness-tui/dsh-tui 0.9.0 → 0.9.2
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/README.md +69 -256
- package/bin/dsh-tui.js +172 -5
- package/cordis.patch.yml +16 -4
- package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -13
- package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -59
- package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -15
- package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -32
- package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -59
- package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -166
- package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -42
- package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -143
- package/dsh-ecosystem-spec/registry/README.md +14 -14
- package/dsh-ecosystem-spec/registry/host-descriptor.tui.example.json +56 -56
- package/dsh-ecosystem-spec/registry/permissions-0.1.json +13 -13
- package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -149
- package/lib/types/cc/hyperlink.d.ts +7 -1
- package/lib/types/cc/hyperlink.d.ts.map +1 -1
- package/lib/types/cc/hyperlink.js +3 -2
- package/lib/types/cc/markdown.d.ts.map +1 -1
- package/lib/types/cc/markdown.js +33 -3
- package/lib/types/cc/sessionColors.d.ts +27 -0
- package/lib/types/cc/sessionColors.d.ts.map +1 -0
- package/lib/types/cc/sessionColors.js +39 -0
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +6 -0
- package/lib/types/components/AutoRecapRow.d.ts +23 -0
- package/lib/types/components/AutoRecapRow.d.ts.map +1 -0
- package/lib/types/components/AutoRecapRow.js +23 -0
- package/lib/types/components/ColorPicker.d.ts +17 -0
- package/lib/types/components/ColorPicker.d.ts.map +1 -0
- package/lib/types/components/ColorPicker.js +20 -0
- package/lib/types/components/CommandSuggestions.d.ts +3 -1
- package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
- package/lib/types/components/ContextBarView.d.ts +33 -0
- package/lib/types/components/ContextBarView.d.ts.map +1 -0
- package/lib/types/components/ContextBarView.js +46 -0
- package/lib/types/components/EffortInputBorder.d.ts +12 -1
- package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
- package/lib/types/components/EffortInputBorder.js +22 -5
- package/lib/types/components/FileActionsPanel.d.ts +22 -0
- package/lib/types/components/FileActionsPanel.d.ts.map +1 -0
- package/lib/types/components/FileActionsPanel.js +28 -0
- package/lib/types/components/FileSuggestions.d.ts +3 -1
- package/lib/types/components/FileSuggestions.d.ts.map +1 -1
- package/lib/types/components/MessageList.d.ts +3 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +13 -6
- package/lib/types/components/ModelPicker.d.ts +26 -4
- package/lib/types/components/ModelPicker.d.ts.map +1 -1
- package/lib/types/components/ModelPicker.js +26 -6
- package/lib/types/components/PromptInput.d.ts.map +1 -1
- package/lib/types/components/PromptInput.js +365 -57
- package/lib/types/components/RecapPanel.d.ts +25 -0
- package/lib/types/components/RecapPanel.d.ts.map +1 -0
- package/lib/types/components/RecapPanel.js +55 -0
- package/lib/types/components/ScrollbarGutter.d.ts.map +1 -1
- package/lib/types/components/ScrollbarGutter.js +55 -4
- package/lib/types/components/SubagentCard.d.ts.map +1 -1
- package/lib/types/components/SubagentCard.js +6 -4
- package/lib/types/components/SuggestionCard.d.ts +4 -2
- package/lib/types/components/SuggestionCard.d.ts.map +1 -1
- package/lib/types/components/TimelineRail.d.ts.map +1 -1
- package/lib/types/components/TimelineRail.js +9 -2
- package/lib/types/components/design-system/ThemeProvider.d.ts +7 -0
- package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
- package/lib/types/components/design-system/ThemeProvider.js +2 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +8 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +36 -8
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts +7 -3
- package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
- package/lib/types/components/questions/AskUserQuestionPanel.js +41 -9
- package/lib/types/dsh-adapter/channel.d.ts +101 -2
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +969 -93
- package/lib/types/dsh-adapter/compat/index.d.ts +8 -6
- package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/index.js +8 -6
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts +150 -0
- package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
- package/lib/types/dsh-adapter/compat/sessionLog.js +518 -41
- package/lib/types/dsh-adapter/contract.d.ts +1 -1
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +1 -0
- package/lib/types/dsh-adapter/extension-events.d.ts +1 -1
- package/lib/types/dsh-adapter/extension-events.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.d.ts +10 -0
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +5 -0
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +317 -8
- package/lib/types/dsh-adapter/presets.d.ts +18 -0
- package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
- package/lib/types/dsh-adapter/presets.js +31 -0
- package/lib/types/dsh-adapter/providerWizard.d.ts +29 -1
- package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
- package/lib/types/dsh-adapter/providerWizard.js +89 -5
- package/lib/types/dsh-adapter/questions.d.ts +19 -2
- package/lib/types/dsh-adapter/questions.d.ts.map +1 -1
- package/lib/types/dsh-adapter/questions.js +53 -10
- package/lib/types/dsh-adapter/recap.d.ts +49 -0
- package/lib/types/dsh-adapter/recap.d.ts.map +1 -0
- package/lib/types/dsh-adapter/recap.js +121 -0
- package/lib/types/dsh-adapter/sessionTree.d.ts +314 -0
- package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -0
- package/lib/types/dsh-adapter/sessionTree.js +954 -0
- package/lib/types/dsh-adapter/shortcuts.d.ts +15 -44
- package/lib/types/dsh-adapter/shortcuts.d.ts.map +1 -1
- package/lib/types/dsh-adapter/shortcuts.js +28 -165
- package/lib/types/dsh-adapter/types.d.ts +1 -1
- package/lib/types/dsh-adapter/types.d.ts.map +1 -1
- package/lib/types/herdr.js +1 -1
- package/lib/types/i18n.d.ts +604 -176
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +206 -72
- package/lib/types/ink/log-update.d.ts.map +1 -1
- package/lib/types/ink/log-update.js +21 -0
- package/lib/types/migrationPrefs.d.ts +60 -0
- package/lib/types/migrationPrefs.d.ts.map +1 -0
- package/lib/types/migrationPrefs.js +97 -0
- package/lib/types/modelGroups.d.ts +72 -0
- package/lib/types/modelGroups.d.ts.map +1 -0
- package/lib/types/modelGroups.js +97 -0
- package/lib/types/modelRecents.d.ts +39 -0
- package/lib/types/modelRecents.d.ts.map +1 -0
- package/lib/types/modelRecents.js +97 -0
- package/lib/types/native-ts/yoga-layout/index.d.ts +11 -0
- package/lib/types/native-ts/yoga-layout/index.d.ts.map +1 -1
- package/lib/types/native-ts/yoga-layout/index.js +31 -1
- package/lib/types/oauth.d.ts +15 -0
- package/lib/types/oauth.d.ts.map +1 -0
- package/lib/types/oauth.js +14 -0
- package/lib/types/plugin-spec/registry.d.ts +1 -1
- package/lib/types/plugin-spec/registry.js +1 -1
- package/lib/types/reload.d.ts +109 -0
- package/lib/types/reload.d.ts.map +1 -0
- package/lib/types/reload.js +118 -0
- package/lib/types/screens/Chat.d.ts +3 -1
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +588 -64
- package/lib/types/screens/SessionTree.d.ts +27 -0
- package/lib/types/screens/SessionTree.d.ts.map +1 -0
- package/lib/types/screens/SessionTree.js +555 -0
- package/lib/types/screens/StatusLine.d.ts +0 -9
- package/lib/types/screens/StatusLine.d.ts.map +1 -1
- package/lib/types/screens/StatusLine.js +215 -52
- package/lib/types/screens/StatusMetrics.d.ts +35 -3
- package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
- package/lib/types/screens/StatusMetrics.js +63 -8
- package/lib/types/screens/chatOverlay.d.ts +17 -1
- package/lib/types/screens/chatOverlay.d.ts.map +1 -1
- package/lib/types/screens/chatOverlay.js +3 -1
- package/lib/types/tips.d.ts.map +1 -1
- package/lib/types/tips.js +22 -4
- package/lib/types/update.d.ts +159 -0
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +496 -30
- package/lib/types/utils/debug.d.ts +1 -1
- package/lib/types/utils/debug.js +3 -3
- package/lib/types/utils/fileTarget.d.ts +46 -0
- package/lib/types/utils/fileTarget.d.ts.map +1 -0
- package/lib/types/utils/fileTarget.js +178 -0
- package/lib/types/utils/keymap.d.ts +134 -0
- package/lib/types/utils/keymap.d.ts.map +1 -0
- package/lib/types/utils/keymap.js +311 -0
- package/lib/types/utils/mentions.d.ts +25 -2
- package/lib/types/utils/mentions.d.ts.map +1 -1
- package/lib/types/utils/mentions.js +69 -10
- package/lib/types/utils/openExternal.d.ts +50 -0
- package/lib/types/utils/openExternal.d.ts.map +1 -0
- package/lib/types/utils/openExternal.js +170 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/LICENSE +21 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/README.md +140 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/cordis.patch.yml +11 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/dsh-plugin.json +44 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/command.d.ts +14 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/command.js +67 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.d.ts +64 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.js +170 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.d.ts +79 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.js +196 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.d.ts +68 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.js +193 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/opener.d.ts +32 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/opener.js +101 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.d.ts +31 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.js +68 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/service.d.ts +69 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/lib/service.js +129 -0
- package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +70 -0
- package/node_modules/@dsh-std/command/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/command/LICENSE +21 -21
- package/node_modules/@dsh-std/command/README.md +26 -26
- package/node_modules/@dsh-std/command/README.zh.md +28 -28
- package/node_modules/@dsh-std/command/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/command/package.json +22 -22
- package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/connection/LICENSE +21 -21
- package/node_modules/@dsh-std/connection/README.md +42 -42
- package/node_modules/@dsh-std/connection/README.zh.md +42 -42
- package/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
- package/node_modules/@dsh-std/connection/package.json +20 -20
- package/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/core/LICENSE +21 -21
- package/node_modules/@dsh-std/core/README.md +13 -13
- package/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/core/package.json +21 -21
- package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
- package/node_modules/@dsh-std/manifest/LICENSE +21 -21
- package/node_modules/@dsh-std/manifest/README.md +5 -5
- package/node_modules/@dsh-std/manifest/README.zh.md +5 -5
- package/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/manifest/package.json +20 -20
- package/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
- package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/messages/LICENSE +21 -21
- package/node_modules/@dsh-std/messages/README.md +5 -5
- package/node_modules/@dsh-std/messages/README.zh.md +7 -7
- package/node_modules/@dsh-std/messages/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/messages/package.json +20 -20
- package/node_modules/@dsh-std/messages/schema/message-observer-envelope-0.15.schema.json +54 -54
- package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
- package/node_modules/@dsh-std/presentation/LICENSE +21 -21
- package/node_modules/@dsh-std/presentation/README.md +27 -27
- package/node_modules/@dsh-std/presentation/README.zh.md +29 -29
- package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
- package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/presentation/package.json +25 -25
- package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -8
- package/node_modules/@dsh-std/storage/LICENSE +21 -21
- package/node_modules/@dsh-std/storage/README.md +5 -5
- package/node_modules/@dsh-std/storage/README.zh.md +7 -7
- package/node_modules/@dsh-std/storage/lib/index.js.map +1 -1
- package/node_modules/@dsh-std/storage/package.json +20 -20
- package/node_modules/@dsh-std/storage/schema/local-storage-0.15.schema.json +56 -56
- package/package.json +17 -5
- package/presets/liangshen/.dsh-tui-managed.json +1 -1
- package/presets/liangshen/instruction-hint.mjs +8 -0
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.md +42 -42
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.zh.md +42 -42
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +20 -20
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.md +5 -5
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.zh.md +5 -5
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +20 -20
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/LICENSE +21 -21
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +27 -27
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +29 -29
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +25 -25
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/LICENSE +21 -21
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.md +42 -42
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.zh.md +42 -42
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +20 -20
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +21 -21
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/LICENSE +21 -21
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.md +13 -13
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.zh.md +13 -13
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js.map +1 -1
- package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +21 -21
|
@@ -8,10 +8,12 @@ import { EffortChargeGlyph } from './EffortChargeGlyph.js';
|
|
|
8
8
|
import { EffortInputBorder } from './EffortInputBorder.js';
|
|
9
9
|
import { EffortTierBadge } from './EffortTierBadge.js';
|
|
10
10
|
import { isLightThemeActive } from '../theme.js';
|
|
11
|
+
import { sessionColorHex } from '../cc/sessionColors.js';
|
|
11
12
|
import { useDeclaredCursor } from '../ink/hooks/use-declared-cursor.js';
|
|
12
13
|
import { noteAuxNumber } from '../ink/geometry-trace.js';
|
|
13
14
|
import instances from '../ink/instances.js';
|
|
14
15
|
import { stringWidth } from '../ink/stringWidth.js';
|
|
16
|
+
import { truncateToWidth } from '../ink/truncateToWidth.js';
|
|
15
17
|
import { getGraphemeSegmenter } from '../utils/intl.js';
|
|
16
18
|
import { formatClipboardInsert, readClipboard } from '../utils/clipboard.js';
|
|
17
19
|
import { editInExternalEditor } from '../utils/externalEditor.js';
|
|
@@ -20,11 +22,25 @@ import { appendHistory } from '../history.js';
|
|
|
20
22
|
import { mentionAtCaret } from '../utils/mentions.js';
|
|
21
23
|
import { preserveSelection } from '../utils/fileSuggestions.js';
|
|
22
24
|
import { isMod } from '../utils/modifiers.js';
|
|
25
|
+
import { actionMatches } from '../utils/keymap.js';
|
|
23
26
|
import { CommandSuggestions } from './CommandSuggestions.js';
|
|
24
27
|
import { FileSuggestions } from './FileSuggestions.js';
|
|
25
28
|
import { HelpMenu } from './HelpMenu.js';
|
|
26
29
|
import { OverlayAbove } from './OverlayAbove.js';
|
|
30
|
+
import { SuggestionCard, cardContentWidth } from './SuggestionCard.js';
|
|
27
31
|
const HISTORY_LIMIT = 50;
|
|
32
|
+
/**
|
|
33
|
+
* Paste fold (CC-style collapse with a visible preview, no black box):
|
|
34
|
+
* a paste that leaves the input this big folds into a one-line chip
|
|
35
|
+
* showing the line/char count PLUS the first line of content. Hover peeks
|
|
36
|
+
* at the full text (window pinned to the head); clicking the chip — or
|
|
37
|
+
* the `▾` prefix on the first row while expanded — toggles the fold; Esc
|
|
38
|
+
* or any editing key unfolds first. Enter still submits the FULL text:
|
|
39
|
+
* folding never drops data.
|
|
40
|
+
*/
|
|
41
|
+
const FOLD_MIN_LINES = 6;
|
|
42
|
+
const FOLD_MIN_CHARS = 600;
|
|
43
|
+
const isBigInput = (text) => text.split('\n').length >= FOLD_MIN_LINES || text.length >= FOLD_MIN_CHARS;
|
|
28
44
|
function clipboardImageMediaType(path) {
|
|
29
45
|
if (/\.png$/iu.test(path))
|
|
30
46
|
return 'image/png';
|
|
@@ -179,6 +195,30 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
179
195
|
const [themeName] = useTheme();
|
|
180
196
|
const [value, setValue] = React.useState('');
|
|
181
197
|
const [cursor, setCursor] = React.useState(0);
|
|
198
|
+
/**
|
|
199
|
+
* CC-style fold block: the [start, end) span of `value` that renders as
|
|
200
|
+
* a one-line chip while the text around it stays fully editable. Created
|
|
201
|
+
* by a big paste; only an EXPLICIT expand (chip/card click, Esc) or
|
|
202
|
+
* delete removes it — typing NEVER unfolds the block.
|
|
203
|
+
*/
|
|
204
|
+
const [foldBlock, setFoldBlock] = React.useState(null);
|
|
205
|
+
/** Pointer over the input box (drives the hover peek card). */
|
|
206
|
+
const [hovered, setHovered] = React.useState(false);
|
|
207
|
+
/** 120ms grace so the pointer crossing the input border row from the
|
|
208
|
+
* chip up onto the peek card never flickers the card. */
|
|
209
|
+
const hoverLeaveTimerRef = React.useRef(null);
|
|
210
|
+
const hoverEnter = React.useCallback(() => {
|
|
211
|
+
if (hoverLeaveTimerRef.current) {
|
|
212
|
+
clearTimeout(hoverLeaveTimerRef.current);
|
|
213
|
+
hoverLeaveTimerRef.current = null;
|
|
214
|
+
}
|
|
215
|
+
setHovered(true);
|
|
216
|
+
}, []);
|
|
217
|
+
const hoverLeave = React.useCallback(() => {
|
|
218
|
+
if (hoverLeaveTimerRef.current)
|
|
219
|
+
clearTimeout(hoverLeaveTimerRef.current);
|
|
220
|
+
hoverLeaveTimerRef.current = setTimeout(() => setHovered(false), 120);
|
|
221
|
+
}, []);
|
|
182
222
|
const valueRef = React.useRef(value);
|
|
183
223
|
const cursorRef = React.useRef(cursor);
|
|
184
224
|
valueRef.current = value;
|
|
@@ -211,6 +251,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
211
251
|
React.useEffect(() => {
|
|
212
252
|
if (fillText && fillText !== lastFill.current) {
|
|
213
253
|
lastFill.current = fillText;
|
|
254
|
+
updateFoldBlock(null);
|
|
214
255
|
setInput(fillText);
|
|
215
256
|
onFillConsumed?.();
|
|
216
257
|
}
|
|
@@ -218,9 +259,9 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
218
259
|
// Double-tap Esc to clear (CC semantics).
|
|
219
260
|
const escPendingRef = React.useRef(false);
|
|
220
261
|
const escTimerRef = React.useRef(null);
|
|
221
|
-
/** True while a
|
|
262
|
+
/** True while a clipboard paste read is in flight (ignore repeat keys). */
|
|
222
263
|
const clipboardBusyRef = React.useRef(false);
|
|
223
|
-
/** True while the external editor owns the terminal (
|
|
264
|
+
/** True while the external editor owns the terminal (editor-key round-trip). */
|
|
224
265
|
const editorBusyRef = React.useRef(false);
|
|
225
266
|
/** Enter dedupe window: cmd pipelines can deliver one Enter as `\r`+`\n`. */
|
|
226
267
|
const lastEnterAtRef = React.useRef(0);
|
|
@@ -228,6 +269,8 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
228
269
|
return () => {
|
|
229
270
|
if (escTimerRef.current)
|
|
230
271
|
clearTimeout(escTimerRef.current);
|
|
272
|
+
if (hoverLeaveTimerRef.current)
|
|
273
|
+
clearTimeout(hoverLeaveTimerRef.current);
|
|
231
274
|
};
|
|
232
275
|
}, []);
|
|
233
276
|
const { columns, rows: terminalRows } = useTerminalSize();
|
|
@@ -290,28 +333,77 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
290
333
|
!helpOpen &&
|
|
291
334
|
!selectionActive &&
|
|
292
335
|
fileEscRef.current !== mention?.start;
|
|
336
|
+
/** Fold-block state + a synchronous mirror (setInput reads the ref).
|
|
337
|
+
* Creating a block also drags a caret that sits inside it out to the
|
|
338
|
+
* block's end (the block is atomic; typing continues after it). */
|
|
339
|
+
const foldBlockRef = React.useRef(null);
|
|
340
|
+
const updateFoldBlock = (block) => {
|
|
341
|
+
foldBlockRef.current = block;
|
|
342
|
+
setFoldBlock(block);
|
|
343
|
+
if (block) {
|
|
344
|
+
const c = cursorRef.current;
|
|
345
|
+
if (c > block.start && c < block.end) {
|
|
346
|
+
cursorRef.current = block.end;
|
|
347
|
+
setCursor(block.end);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
};
|
|
293
351
|
const setInput = (next, cursorOffset = next.length) => {
|
|
294
|
-
valueRef.current
|
|
352
|
+
const prev = valueRef.current;
|
|
353
|
+
const prevCursor = cursorRef.current;
|
|
354
|
+
const block = foldBlockRef.current;
|
|
295
355
|
// Normalize onto a grapheme boundary (also clamps into range): every
|
|
296
356
|
// caller passes a caret they believe is on a character edge — paste
|
|
297
357
|
// merges, history fills, and IME composition can still hand back an
|
|
298
358
|
// offset inside a surrogate pair or combining cluster.
|
|
299
|
-
|
|
359
|
+
let offset = normalizeCursorOffset(next, cursorOffset);
|
|
360
|
+
if (block) {
|
|
361
|
+
// Fold block is atomic: the caret never lands INSIDE [start, end).
|
|
362
|
+
if (offset > block.start && offset < block.end) {
|
|
363
|
+
offset = offset - block.start < block.end - offset ? block.start : block.end;
|
|
364
|
+
}
|
|
365
|
+
// Every real edit happens AT the caret, so a block that starts at or
|
|
366
|
+
// after the caret shifts with the value delta; one fully past the
|
|
367
|
+
// caret is untouched. Whole-value replacements (fill/history/editor)
|
|
368
|
+
// clear the block explicitly at their call sites.
|
|
369
|
+
const delta = next.length - prev.length;
|
|
370
|
+
let start = block.start;
|
|
371
|
+
let end = block.end;
|
|
372
|
+
if (prevCursor <= block.start) {
|
|
373
|
+
start += delta;
|
|
374
|
+
end += delta;
|
|
375
|
+
}
|
|
376
|
+
start = Math.max(0, Math.min(start, next.length));
|
|
377
|
+
end = Math.max(start, Math.min(end, next.length));
|
|
378
|
+
if (start >= end)
|
|
379
|
+
updateFoldBlock(null);
|
|
380
|
+
else if (start !== block.start || end !== block.end)
|
|
381
|
+
updateFoldBlock({ start, end });
|
|
382
|
+
}
|
|
383
|
+
// The synchronous mirrors are what batch-dispatched events (one stdin
|
|
384
|
+
// read → several keys, no render in between) read on their next turn.
|
|
385
|
+
valueRef.current = next;
|
|
386
|
+
cursorRef.current = offset;
|
|
300
387
|
setValue(next);
|
|
301
|
-
setCursor(
|
|
388
|
+
setCursor(offset);
|
|
302
389
|
};
|
|
303
390
|
/**
|
|
304
391
|
* Accept the selected file suggestion: replace ONLY the mention token at
|
|
305
392
|
* the caret (prefix/suffix text survives), quoting whitespace paths. A
|
|
306
393
|
* directory inserts `@dir/` without a trailing space so completion
|
|
307
|
-
* continues into it; a file completes the token with a space.
|
|
394
|
+
* continues into it; a file completes the token with a space. A typed
|
|
395
|
+
* `#L12-14` suffix is NOT part of the replacement — completion ends at
|
|
396
|
+
* `pathEnd` so the line range survives acceptance (issue #359).
|
|
308
397
|
*/
|
|
309
398
|
const acceptFile = (candidate) => {
|
|
310
399
|
if (!mention)
|
|
311
400
|
return;
|
|
312
401
|
const file = candidate.path;
|
|
313
402
|
const body = /\s/.test(file) ? `@"${file}"` : `@${file}`;
|
|
314
|
-
|
|
403
|
+
// A typed `#L12-14` suffix rides along AFTER the completed body (before
|
|
404
|
+
// the trailing space) — quoting a whitespace path must not detach it.
|
|
405
|
+
const suffix = mention.pathEnd === undefined ? '' : value.slice(mention.pathEnd, mention.end);
|
|
406
|
+
const insert = candidate.kind === 'directory' ? `${body}${suffix}` : `${body}${suffix} `;
|
|
315
407
|
const next = value.slice(0, mention.start) + insert + value.slice(mention.end);
|
|
316
408
|
setInput(next, mention.start + insert.length);
|
|
317
409
|
setFileSelected(0);
|
|
@@ -388,6 +480,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
388
480
|
return;
|
|
389
481
|
}
|
|
390
482
|
setInput(item.text);
|
|
483
|
+
updateFoldBlock(null);
|
|
391
484
|
setSelectedCommand(0);
|
|
392
485
|
setFileSelected(0);
|
|
393
486
|
channel.notify(t('input-retracted'), { timeoutMs: 2000 });
|
|
@@ -449,13 +542,17 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
449
542
|
return handled;
|
|
450
543
|
};
|
|
451
544
|
/** Clipboard reads are asynchronous; insert against the latest render so
|
|
452
|
-
* typing while PowerShell owns the clipboard never gets overwritten.
|
|
545
|
+
* typing while PowerShell owns the clipboard never gets overwritten.
|
|
546
|
+
* Returns the resulting value and the insertion offset so callers can
|
|
547
|
+
* apply the paste fold. */
|
|
453
548
|
const insertClipboardAtCaret = (text) => {
|
|
454
549
|
const current = valueRef.current;
|
|
455
550
|
const position = cursorRef.current;
|
|
456
|
-
|
|
551
|
+
const next = current.slice(0, position) + text + current.slice(position);
|
|
552
|
+
setInput(next, position + text.length);
|
|
457
553
|
setSelectedCommand(0);
|
|
458
554
|
setFileSelected(0);
|
|
555
|
+
return { next, at: position };
|
|
459
556
|
};
|
|
460
557
|
/** Line index of the cursor; -1 when the cursor is at the very end. */
|
|
461
558
|
const cursorLine = (text, cursorOffset) => {
|
|
@@ -481,6 +578,39 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
481
578
|
// the text/caret produced by the preceding event in that batch.
|
|
482
579
|
const value = valueRef.current;
|
|
483
580
|
const cursor = cursorRef.current;
|
|
581
|
+
// Fold block (CC-style): Esc expands it — it must NEVER clear text
|
|
582
|
+
// that LOOKS like one line; Backspace at the block's tail / Delete at
|
|
583
|
+
// its head deletes the WHOLE block in one key; ←/→ jump over the
|
|
584
|
+
// atomic block. Typing NEVER expands it — the caret lives outside the
|
|
585
|
+
// block and edits land in the visible text around it.
|
|
586
|
+
const block = foldBlockRef.current;
|
|
587
|
+
// Line-level moves/edits (↑/↓/Home/End/Ctrl+A/E/U/K/W) treat the
|
|
588
|
+
// block as an atomic row: boundaries that would land inside it are
|
|
589
|
+
// clamped to its edges.
|
|
590
|
+
const clampRowStart = (pos) => block && cursor >= block.end ? Math.max(pos, block.end) : pos;
|
|
591
|
+
const clampRowEnd = (pos) => block && cursor <= block.start ? Math.min(pos, block.start) : pos;
|
|
592
|
+
if (block) {
|
|
593
|
+
if (key.escape) {
|
|
594
|
+
updateFoldBlock(null);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if ((key.backspace && cursor === block.end) || (key.delete && cursor === block.start)) {
|
|
598
|
+
const next = value.slice(0, block.start) + value.slice(block.end);
|
|
599
|
+
updateFoldBlock(null);
|
|
600
|
+
setInput(next, block.start);
|
|
601
|
+
setSelectedCommand(0);
|
|
602
|
+
setFileSelected(0);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (key.leftArrow && cursor === block.end) {
|
|
606
|
+
setInput(value, block.start);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (key.rightArrow && cursor === block.start) {
|
|
610
|
+
setInput(value, block.end);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
484
614
|
// Grapheme boundaries of the current text: every caret move / delete
|
|
485
615
|
// below snaps onto one of these offsets (never mid-cluster).
|
|
486
616
|
const bounds = graphemeBoundaries(value);
|
|
@@ -497,13 +627,20 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
497
627
|
// verbatim at the caret. Paste content may contain newlines — that is
|
|
498
628
|
// NOT Enter — so this branch runs before the whole-line submit rule.
|
|
499
629
|
if (event?.isPasted && input.length > 0) {
|
|
500
|
-
|
|
630
|
+
const text = input.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
631
|
+
insertAtCaret(text);
|
|
632
|
+
// A big paste becomes a CC-style fold block right away (hover peeks
|
|
633
|
+
// at it); an existing block is replaced by the new paste's span.
|
|
634
|
+
if (isBigInput(text))
|
|
635
|
+
updateFoldBlock({ start: cursor, end: cursor + text.length });
|
|
501
636
|
return;
|
|
502
637
|
}
|
|
503
|
-
// Ctrl+V / Cmd+V
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
|
|
638
|
+
// Clipboard paste (default Ctrl+V / Cmd+V, plus the Alt+V alias for
|
|
639
|
+
// terminals that intercept Ctrl+V — combos are user-remappable via
|
|
640
|
+
// /settings): raw mode hands the key to the app, so the clipboard is
|
|
641
|
+
// read here — text, file paths when the file manager copied files, or
|
|
642
|
+
// an exported temp-file path when the clipboard holds a raw image.
|
|
643
|
+
if (actionMatches('paste', input, key)) {
|
|
507
644
|
if (clipboardBusyRef.current)
|
|
508
645
|
return;
|
|
509
646
|
// Match insertAtCaret's overlay/selection dismissal up front: the
|
|
@@ -547,7 +684,11 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
547
684
|
// Insert against the LIVE input state: the read above resolved
|
|
548
685
|
// asynchronously and the user may have typed while waiting.
|
|
549
686
|
const text = formatClipboardInsert(content);
|
|
550
|
-
insertClipboardAtCaret(text);
|
|
687
|
+
const { at } = insertClipboardAtCaret(text);
|
|
688
|
+
// Same fold as bracketed paste: big clipboard text becomes a
|
|
689
|
+
// fold block covering exactly the pasted span.
|
|
690
|
+
if (isBigInput(text))
|
|
691
|
+
updateFoldBlock({ start: at, end: at + text.length });
|
|
551
692
|
})
|
|
552
693
|
.catch(() => {
|
|
553
694
|
channel.notify(t('input-clipboard-read-failed'), { color: 'warning' });
|
|
@@ -559,27 +700,30 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
559
700
|
});
|
|
560
701
|
return;
|
|
561
702
|
}
|
|
562
|
-
// Help is modal for modified keys and every Enter variant.
|
|
563
|
-
// is the intentional exception: paste closes Help and
|
|
703
|
+
// Help is modal for modified keys and every Enter variant. The paste
|
|
704
|
+
// branch above is the intentional exception: paste closes Help and
|
|
705
|
+
// inserts visibly.
|
|
564
706
|
// Swallow here before editor/submit/interrupt branches can mutate hidden
|
|
565
707
|
// composer or working-turn state; plain typing still dismisses Help below.
|
|
566
708
|
if (helpOpen && !key.escape && (key.ctrl || key.meta || key.super || key.return || input.includes('\n') || input.includes('\r'))) {
|
|
567
709
|
event.stopImmediatePropagation();
|
|
568
710
|
return;
|
|
569
711
|
}
|
|
570
|
-
// Ctrl+G: edit the current draft in
|
|
571
|
-
// readline's edit-and-execute-command). The
|
|
572
|
-
// file, the terminal is handed to the editor
|
|
573
|
-
// and the saved text replaces the input when
|
|
574
|
-
// every failure to an outcome, but the
|
|
575
|
-
// guarantee: a rejected promise must
|
|
576
|
-
// busy flag must always clear or
|
|
577
|
-
|
|
712
|
+
// Ctrl+G (remappable via /settings): edit the current draft in
|
|
713
|
+
// $VISUAL/$EDITOR (issue #123, readline's edit-and-execute-command). The
|
|
714
|
+
// draft is written to a temp file, the terminal is handed to the editor
|
|
715
|
+
// (Ink's alt-screen handoff), and the saved text replaces the input when
|
|
716
|
+
// it differs. The util maps every failure to an outcome, but the
|
|
717
|
+
// catch/finally here is the hard guarantee: a rejected promise must
|
|
718
|
+
// never kill the process, and the busy flag must always clear or the
|
|
719
|
+
// editor key stays locked forever.
|
|
720
|
+
if (actionMatches('editor', input, key)) {
|
|
578
721
|
editorBusyRef.current = true;
|
|
579
722
|
void (async () => {
|
|
580
723
|
try {
|
|
581
724
|
const outcome = await editInExternalEditor(value);
|
|
582
725
|
if (outcome.kind === 'edited') {
|
|
726
|
+
updateFoldBlock(null);
|
|
583
727
|
setInput(outcome.text);
|
|
584
728
|
setSelectedCommand(0);
|
|
585
729
|
setFileSelected(0);
|
|
@@ -724,8 +868,10 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
724
868
|
}
|
|
725
869
|
if (key.tab && overlayOpen) {
|
|
726
870
|
const command = suggestions[selectedCommand];
|
|
727
|
-
if (command)
|
|
871
|
+
if (command) {
|
|
872
|
+
updateFoldBlock(null);
|
|
728
873
|
setInput(command.replacement);
|
|
874
|
+
}
|
|
729
875
|
return;
|
|
730
876
|
}
|
|
731
877
|
// Tab while the model is working = queue for AFTER the turn (followup),
|
|
@@ -779,6 +925,22 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
779
925
|
setFileSelected(index => index <= 0 ? fileMatches.length - 1 : index - 1);
|
|
780
926
|
return;
|
|
781
927
|
}
|
|
928
|
+
// With a fold block the caret in the tail walks the TAIL's lines
|
|
929
|
+
// (the block is one atomic row); the first tail line steps over the
|
|
930
|
+
// block to its head.
|
|
931
|
+
if (block && cursor >= block.end) {
|
|
932
|
+
const tailCursor = cursor - block.end;
|
|
933
|
+
const line = cursorLine(tail, tailCursor);
|
|
934
|
+
if (line > 0) {
|
|
935
|
+
const upToLineStart = tail.lastIndexOf('\n', tailCursor - 1);
|
|
936
|
+
const prevLineStart = upToLineStart === -1 ? 0 : tail.lastIndexOf('\n', upToLineStart - 1) + 1;
|
|
937
|
+
const prevLine = tail.slice(prevLineStart, upToLineStart);
|
|
938
|
+
setInput(value, block.end + prevLineStart + Math.min(cursorColumn(tail, tailCursor), prevLine.length));
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
setInput(value, block.start);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
782
944
|
const line = cursorLine(value, cursor);
|
|
783
945
|
if (line > 0) {
|
|
784
946
|
// Move to the previous line, clamping to its length.
|
|
@@ -802,6 +964,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
802
964
|
historyIndex.current = Math.max(0, historyIndex.current - 1);
|
|
803
965
|
}
|
|
804
966
|
const entry = history.current[historyIndex.current] ?? '';
|
|
967
|
+
updateFoldBlock(null);
|
|
805
968
|
setInput(entry);
|
|
806
969
|
return;
|
|
807
970
|
}
|
|
@@ -810,6 +973,36 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
810
973
|
setFileSelected(index => index >= fileMatches.length - 1 ? 0 : index + 1);
|
|
811
974
|
return;
|
|
812
975
|
}
|
|
976
|
+
// Mirror of the tail-side ↑ walk: with a fold block the caret walks
|
|
977
|
+
// the TAIL's lines; past its last line it falls through to the
|
|
978
|
+
// overlay/history handling below.
|
|
979
|
+
if (block && cursor >= block.end) {
|
|
980
|
+
const tailCursor = cursor - block.end;
|
|
981
|
+
const line = cursorLine(tail, tailCursor);
|
|
982
|
+
const lines = tail.split('\n');
|
|
983
|
+
if (line < lines.length - 1) {
|
|
984
|
+
const nextLineStart = tail.indexOf('\n', tailCursor) + 1;
|
|
985
|
+
const nextLineEnd = tail.indexOf('\n', nextLineStart);
|
|
986
|
+
const nextLine = tail.slice(nextLineStart, nextLineEnd === -1 ? tail.length : nextLineEnd);
|
|
987
|
+
setInput(value, block.end + nextLineStart + Math.min(cursorColumn(tail, tailCursor), nextLine.length));
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
else if (block && cursor <= block.start) {
|
|
992
|
+
// Head side: walk the HEAD's lines; its last line steps over the
|
|
993
|
+
// block to the tail (never into history for a multi-row input).
|
|
994
|
+
const line = cursorLine(head, cursor);
|
|
995
|
+
const lines = head.split('\n');
|
|
996
|
+
if (line < lines.length - 1) {
|
|
997
|
+
const nextLineStart = head.indexOf('\n', cursor) + 1;
|
|
998
|
+
const nextLineEnd = head.indexOf('\n', nextLineStart);
|
|
999
|
+
const nextLine = head.slice(nextLineStart, nextLineEnd === -1 ? head.length : nextLineEnd);
|
|
1000
|
+
setInput(value, nextLineStart + Math.min(cursorColumn(head, cursor), nextLine.length));
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
setInput(value, block.end);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
813
1006
|
const line = cursorLine(value, cursor);
|
|
814
1007
|
const lines = value.split('\n');
|
|
815
1008
|
if (line < lines.length - 1) {
|
|
@@ -827,6 +1020,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
827
1020
|
return;
|
|
828
1021
|
if (historyIndex.current >= history.current.length - 1) {
|
|
829
1022
|
historyIndex.current = -1;
|
|
1023
|
+
updateFoldBlock(null);
|
|
830
1024
|
setInput(historyDraft.current);
|
|
831
1025
|
}
|
|
832
1026
|
else {
|
|
@@ -871,43 +1065,44 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
871
1065
|
return;
|
|
872
1066
|
}
|
|
873
1067
|
if (key.home) {
|
|
874
|
-
// Start of the current line.
|
|
875
|
-
const lineStart = value.lastIndexOf('\n', cursor - 1) + 1;
|
|
1068
|
+
// Start of the current line (the block is one atomic row).
|
|
1069
|
+
const lineStart = clampRowStart(value.lastIndexOf('\n', cursor - 1) + 1);
|
|
876
1070
|
setInput(value, lineStart);
|
|
877
1071
|
return;
|
|
878
1072
|
}
|
|
879
1073
|
if (key.end) {
|
|
880
1074
|
// End of the current line.
|
|
881
1075
|
const nextLine = value.indexOf('\n', cursor);
|
|
882
|
-
setInput(value, nextLine === -1 ? value.length : nextLine);
|
|
1076
|
+
setInput(value, nextLine === -1 ? value.length : clampRowEnd(nextLine));
|
|
883
1077
|
return;
|
|
884
1078
|
}
|
|
885
1079
|
if (isMod(key) && input === 'a') {
|
|
886
|
-
const lineStart = value.lastIndexOf('\n', cursor - 1) + 1;
|
|
1080
|
+
const lineStart = clampRowStart(value.lastIndexOf('\n', cursor - 1) + 1);
|
|
887
1081
|
setInput(value, lineStart);
|
|
888
1082
|
return;
|
|
889
1083
|
}
|
|
890
1084
|
if (isMod(key) && input === 'e') {
|
|
891
1085
|
const nextLine = value.indexOf('\n', cursor);
|
|
892
|
-
setInput(value, nextLine === -1 ? value.length : nextLine);
|
|
1086
|
+
setInput(value, nextLine === -1 ? value.length : clampRowEnd(nextLine));
|
|
893
1087
|
return;
|
|
894
1088
|
}
|
|
895
1089
|
if (isMod(key) && input === 'u') {
|
|
896
|
-
// Delete to start of line.
|
|
897
|
-
const lineStart = value.lastIndexOf('\n', cursor - 1) + 1;
|
|
1090
|
+
// Delete to start of line (never into the block).
|
|
1091
|
+
const lineStart = clampRowStart(value.lastIndexOf('\n', cursor - 1) + 1);
|
|
898
1092
|
setInput(value.slice(0, lineStart) + value.slice(cursor), lineStart);
|
|
899
1093
|
return;
|
|
900
1094
|
}
|
|
901
1095
|
if (isMod(key) && input === 'k') {
|
|
902
|
-
// Delete to end of line.
|
|
1096
|
+
// Delete to end of line (never into the block).
|
|
903
1097
|
const nextLine = value.indexOf('\n', cursor);
|
|
904
|
-
const end = nextLine === -1 ? value.length : nextLine;
|
|
1098
|
+
const end = nextLine === -1 ? value.length : clampRowEnd(nextLine);
|
|
905
1099
|
setInput(value.slice(0, cursor) + value.slice(end), cursor);
|
|
906
1100
|
return;
|
|
907
1101
|
}
|
|
908
1102
|
if (isMod(key) && input === 'w') {
|
|
909
1103
|
// Delete the word before the cursor (CC/readline behavior): skip
|
|
910
|
-
// trailing whitespace, then the whitespace-delimited word.
|
|
1104
|
+
// trailing whitespace, then the whitespace-delimited word. The
|
|
1105
|
+
// deletion start never crosses into the block.
|
|
911
1106
|
const before = value.slice(0, cursor);
|
|
912
1107
|
let end = before.length;
|
|
913
1108
|
while (end > 0 && /\s/.test(before[end - 1]))
|
|
@@ -915,7 +1110,8 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
915
1110
|
let start = end;
|
|
916
1111
|
while (start > 0 && !/\s/.test(before[start - 1]))
|
|
917
1112
|
start--;
|
|
918
|
-
|
|
1113
|
+
const clipped = clampRowStart(start);
|
|
1114
|
+
setInput(value.slice(0, clipped) + value.slice(cursor), clipped);
|
|
919
1115
|
return;
|
|
920
1116
|
}
|
|
921
1117
|
if (key.escape) {
|
|
@@ -948,8 +1144,17 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
948
1144
|
return;
|
|
949
1145
|
}
|
|
950
1146
|
// A single Esc clears the current input (if any); the double-tap
|
|
951
|
-
// path below handles rewind/clear on an already-empty input.
|
|
1147
|
+
// path below handles rewind/clear on an already-empty input. A BIG
|
|
1148
|
+
// input folds into a block instead (Esc = the fold toggle; the
|
|
1149
|
+
// expand → fold → expand cycle is lossless, and clearing a big draft
|
|
1150
|
+
// stays reachable via the block-delete Backspace or Ctrl+C).
|
|
952
1151
|
if (value.length > 0) {
|
|
1152
|
+
if (isBigInput(value)) {
|
|
1153
|
+
updateFoldBlock({ start: 0, end: value.length });
|
|
1154
|
+
setSelectedCommand(0);
|
|
1155
|
+
setFileSelected(0);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
953
1158
|
setInput('', 0);
|
|
954
1159
|
setSelectedCommand(0);
|
|
955
1160
|
setFileSelected(0);
|
|
@@ -998,8 +1203,33 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
998
1203
|
// single column — a fixed floor of 10 would wrap far too early and park
|
|
999
1204
|
// the declared cursor past the value box's actual width.
|
|
1000
1205
|
const inputWidth = Math.max(1, columns - 3);
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1206
|
+
const block = foldBlock;
|
|
1207
|
+
// Fold-block model: the value renders as [head rows][chip row][tail rows]
|
|
1208
|
+
// — the block is ONE atomic visual row regardless of its text size; text
|
|
1209
|
+
// before/after it stays fully editable and never unfolds it.
|
|
1210
|
+
const foldText = block ? value.slice(block.start, block.end) : value;
|
|
1211
|
+
const head = block ? value.slice(0, block.start) : '';
|
|
1212
|
+
const tail = block ? value.slice(block.end) : '';
|
|
1213
|
+
const headRows = block ? wrapToWidth(head, inputWidth) : [];
|
|
1214
|
+
const tailRows = block ? wrapToWidth(tail, inputWidth) : [];
|
|
1215
|
+
const chipRow = block ? headRows.length : -1;
|
|
1216
|
+
const visualLines = block
|
|
1217
|
+
? [...headRows, '', ...tailRows]
|
|
1218
|
+
: wrapToWidth(value, inputWidth);
|
|
1219
|
+
const caretVisualLine = block
|
|
1220
|
+
? cursor <= block.start
|
|
1221
|
+
? wrapToWidth(value.slice(0, cursor), inputWidth).length - 1
|
|
1222
|
+
: headRows.length + 1 + wrapToWidth(value.slice(block.end, cursor), inputWidth).length - 1
|
|
1223
|
+
: wrapToWidth(value.slice(0, cursor), inputWidth).length - 1;
|
|
1224
|
+
// Fold stats describe the BLOCK (or the whole input when expanded and
|
|
1225
|
+
// the ▾ prefix offers a manual whole-input fold). The chip shows the
|
|
1226
|
+
// block's own line/char count + first-line preview — the preview text
|
|
1227
|
+
// around it is unaffected.
|
|
1228
|
+
const big = isBigInput(foldText);
|
|
1229
|
+
const stats = big
|
|
1230
|
+
? t('input-fold-stats', { lines: foldText.split('\n').length, chars: foldText.length })
|
|
1231
|
+
: '';
|
|
1232
|
+
const peekOpen = block !== null && hovered && !selectionActive;
|
|
1003
1233
|
const windowStart = Math.max(0, Math.min(caretVisualLine - MAX_VISIBLE_LINES + 1, visualLines.length - MAX_VISIBLE_LINES));
|
|
1004
1234
|
const visibleLines = visualLines.slice(windowStart, windowStart + MAX_VISIBLE_LINES);
|
|
1005
1235
|
// Caret position in the caret's visual row, in two units:
|
|
@@ -1009,35 +1239,75 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
1009
1239
|
// cursor mid-character and Windows Terminal would paint the IME
|
|
1010
1240
|
// preedit (pinyin) over the surrounding text).
|
|
1011
1241
|
const caretCharCol = () => {
|
|
1012
|
-
const before = value.slice(0, cursor);
|
|
1242
|
+
const before = block && cursor >= block.end ? value.slice(block.end, cursor) : value.slice(0, cursor);
|
|
1013
1243
|
const rows = wrapToWidth(before, inputWidth);
|
|
1014
1244
|
const last = rows[rows.length - 1] ?? '';
|
|
1015
1245
|
return last.length;
|
|
1016
1246
|
};
|
|
1017
1247
|
const caretVisualCol = () => {
|
|
1018
|
-
const before = value.slice(0, cursor);
|
|
1248
|
+
const before = block && cursor >= block.end ? value.slice(block.end, cursor) : value.slice(0, cursor);
|
|
1019
1249
|
const rows = wrapToWidth(before, inputWidth);
|
|
1020
1250
|
const last = rows[rows.length - 1] ?? '';
|
|
1021
1251
|
return stringWidth(last);
|
|
1022
1252
|
};
|
|
1253
|
+
// Folded chip content: block stats + first-line preview + hover hint,
|
|
1254
|
+
// all pre-truncated to the input width (the row is one line, always).
|
|
1255
|
+
const foldBadge = `▸ ${stats}`;
|
|
1256
|
+
const foldHint = t('input-fold-hover');
|
|
1257
|
+
const foldPreviewWidth = inputWidth - stringWidth(foldBadge) - stringWidth(` · ${foldHint}`) - 6;
|
|
1258
|
+
const foldPreview = foldPreviewWidth >= 8
|
|
1259
|
+
? truncateToWidth(foldText.split('\n')[0] ?? '', foldPreviewWidth)
|
|
1260
|
+
: '';
|
|
1261
|
+
// Expanded-state fold affordance: a `▾` prefix at the start of the FIRST
|
|
1262
|
+
// row (only while the window is at the top and no block exists); its
|
|
1263
|
+
// cells fold the whole input into a block again on click.
|
|
1264
|
+
const prefixLabel = `▾ ${stats} · `;
|
|
1265
|
+
const prefixCols = !block && big && windowStart === 0 ? stringWidth(prefixLabel) : 0;
|
|
1023
1266
|
const rendered = visibleLines.map((line, index) => {
|
|
1024
1267
|
const absoluteLine = windowStart + index;
|
|
1268
|
+
if (block && absoluteLine === chipRow) {
|
|
1269
|
+
// The fold block's atomic chip row: click expands it; hover pops the
|
|
1270
|
+
// peek card. The row is one line no matter how big the block is.
|
|
1271
|
+
return (_jsxs(Box, { flexDirection: "row", onClick: (event) => {
|
|
1272
|
+
event.stopImmediatePropagation();
|
|
1273
|
+
updateFoldBlock(null);
|
|
1274
|
+
}, onMouseEnter: hoverEnter, onMouseLeave: hoverLeave, children: [_jsx(Text, { dimColor: true, children: foldBadge }), foldPreview !== '' && _jsx(Text, { dimColor: true, children: " \u00B7 " }), foldPreview !== '' && _jsx(Text, { wrap: "truncate-end", children: foldPreview }), _jsx(Text, { dimColor: true, children: ` · ${foldHint}` })] }, `fold-${absoluteLine}`));
|
|
1275
|
+
}
|
|
1276
|
+
const withPrefix = absoluteLine === 0 && prefixCols > 0;
|
|
1277
|
+
const text = withPrefix ? truncateToWidth(line, inputWidth - prefixCols) : line;
|
|
1278
|
+
const prefix = withPrefix ? _jsx(Text, { dimColor: true, children: prefixLabel }) : null;
|
|
1025
1279
|
if (absoluteLine !== caretVisualLine) {
|
|
1026
|
-
return (
|
|
1280
|
+
return (_jsxs(Text, { wrap: "truncate-end", children: [prefix, text] }, absoluteLine));
|
|
1027
1281
|
}
|
|
1028
1282
|
// Caret row: invert the grapheme cluster at the caret column (solid
|
|
1029
1283
|
// block). `col` is a cluster boundary by construction (the cursor is
|
|
1030
1284
|
// normalized onto boundaries and wrapping only breaks between
|
|
1031
1285
|
// graphemes), so [col, next boundary) covers the WHOLE cluster — a
|
|
1032
1286
|
// surrogate pair or ZWJ emoji inverts as one glyph, never two broken
|
|
1033
|
-
// halves.
|
|
1034
|
-
|
|
1035
|
-
const
|
|
1036
|
-
const
|
|
1037
|
-
const
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1287
|
+
// halves. Clamped into the prefix-shortened row text so the block
|
|
1288
|
+
// caret never renders under the fold prefix.
|
|
1289
|
+
const col = Math.min(caretCharCol(), text.length);
|
|
1290
|
+
const clusterEnd = nextGraphemeBoundary(graphemeBoundaries(text), col);
|
|
1291
|
+
const before = text.slice(0, col);
|
|
1292
|
+
const at = clusterEnd > col ? text.slice(col, clusterEnd) : ' ';
|
|
1293
|
+
const after = text.slice(clusterEnd);
|
|
1294
|
+
return (_jsxs(Text, { wrap: "truncate-end", children: [prefix, before, _jsx(Text, { inverse: true, children: at }), after] }, absoluteLine));
|
|
1040
1295
|
});
|
|
1296
|
+
// Peek card content: the BLOCK's text wrapped to the card's inner width,
|
|
1297
|
+
// capped at PEEK_MAX_ROWS visual rows (a preview — click to expand and
|
|
1298
|
+
// edit the real input). The footer reports the clipped remainder.
|
|
1299
|
+
const PEEK_MAX_ROWS = 10;
|
|
1300
|
+
const peekVisualLines = [];
|
|
1301
|
+
for (const line of foldText.split('\n')) {
|
|
1302
|
+
for (const row of wrapToWidth(line, cardContentWidth(columns))) {
|
|
1303
|
+
if (peekVisualLines.length >= PEEK_MAX_ROWS)
|
|
1304
|
+
break;
|
|
1305
|
+
peekVisualLines.push(row);
|
|
1306
|
+
}
|
|
1307
|
+
if (peekVisualLines.length >= PEEK_MAX_ROWS)
|
|
1308
|
+
break;
|
|
1309
|
+
}
|
|
1310
|
+
const peekClipped = peekVisualLines.length >= PEEK_MAX_ROWS;
|
|
1041
1311
|
// Composer height shrink: clearing multi-line text (Enter/Esc/Ctrl+C/
|
|
1042
1312
|
// Backspace) collapses the input area within one commit, shifting the
|
|
1043
1313
|
// status line up and the whole chrome with it. The renderer's
|
|
@@ -1070,7 +1340,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
1070
1340
|
// Clamp the declared column to the wrap width: a grapheme wider than
|
|
1071
1341
|
// the last remaining column (emoji at width 1) can push the visual
|
|
1072
1342
|
// column past inputWidth, and the park must stay inside the value box.
|
|
1073
|
-
column: Math.min(caretVisualCol(), inputWidth),
|
|
1343
|
+
column: Math.min(caretVisualCol() + (caretVisualLine === 0 && prefixCols > 0 ? prefixCols : 0), inputWidth),
|
|
1074
1344
|
active: !selectionActive,
|
|
1075
1345
|
});
|
|
1076
1346
|
/**
|
|
@@ -1078,22 +1348,55 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
1078
1348
|
* row/column relative to the box) to a UTF-16 cursor offset via the same
|
|
1079
1349
|
* grapheme walk the renderer wraps with — exact under CJK widths, wrapped
|
|
1080
1350
|
* rows and multi-codepoint clusters. Clicks land on the boundary nearest
|
|
1081
|
-
* the clicked cell (mid-grapheme snaps to its start).
|
|
1351
|
+
* the clicked cell (mid-grapheme snaps to its start). With a fold block,
|
|
1352
|
+
* clicks map into the head/tail text (the chip row has its own expand
|
|
1353
|
+
* onClick); without one, the fold prefix's cells fold the whole input.
|
|
1082
1354
|
*/
|
|
1083
1355
|
const handleValueClick = React.useCallback((e) => {
|
|
1084
1356
|
const clickedVisual = windowStart + e.localRow;
|
|
1085
1357
|
const clamped = Math.max(0, Math.min(clickedVisual, visualLines.length - 1));
|
|
1086
|
-
|
|
1087
|
-
|
|
1358
|
+
if (block) {
|
|
1359
|
+
if (clamped === chipRow)
|
|
1360
|
+
return;
|
|
1361
|
+
if (clamped < chipRow) {
|
|
1362
|
+
setCursor(clickToCursorOffset(head, inputWidth, clamped, e.localCol));
|
|
1363
|
+
}
|
|
1364
|
+
else {
|
|
1365
|
+
setCursor(block.end + clickToCursorOffset(tail, inputWidth, clamped - chipRow - 1, e.localCol));
|
|
1366
|
+
}
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
if (clamped === 0 && prefixCols > 0 && e.localCol < prefixCols) {
|
|
1370
|
+
updateFoldBlock({ start: 0, end: value.length });
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
const col = clamped === 0 && prefixCols > 0 ? Math.max(0, e.localCol - prefixCols) : e.localCol;
|
|
1374
|
+
setCursor(clickToCursorOffset(value, clamped === 0 && prefixCols > 0 ? inputWidth - prefixCols : inputWidth, clamped, col));
|
|
1375
|
+
}, [windowStart, visualLines.length, value, inputWidth, prefixCols, block, chipRow, head, tail]);
|
|
1088
1376
|
// 浮层整体挂载条件:与内部面板可见条件精确同值。关闭时必须把整个
|
|
1089
1377
|
// absolute 浮层移除——渲染器的 absolute-removed 检测只看被移除节点自身
|
|
1090
1378
|
// 的 style.position,常驻浮层 + 移除普通子节点不会触发 blit 解毒,被
|
|
1091
1379
|
// 覆盖的转录行会留空(见 Chat.tsx dialogOverlayOpen 注释)。
|
|
1092
|
-
const floatersOpen = helpOpen || channel.pending.length > 0 || fileOverlayOpen || overlayOpen;
|
|
1380
|
+
const floatersOpen = helpOpen || channel.pending.length > 0 || fileOverlayOpen || overlayOpen || peekOpen;
|
|
1093
1381
|
// 补全卡片边框与输入框 idle 边框同色(plan 模式下整套面板一起变 sage 绿)。
|
|
1094
|
-
|
|
1382
|
+
// `/color` 会话强调色优先于主题 promptBorder(plan 模式仍整体走 sage 绿)。
|
|
1383
|
+
// `?? ''` 防御最小 mock channel(只声明用到的字段的回归脚本)。
|
|
1384
|
+
const sessionAccent = sessionColorHex(channel.sessionColor ?? '');
|
|
1385
|
+
const promptAccent = channel.mode.plan === true ? 'planMode' : (sessionAccent ?? 'promptBorder');
|
|
1386
|
+
// 顶边框右侧的会话名标签(CC 风格 chip):色随强调色;超宽截断,宽度
|
|
1387
|
+
// 随终端列数伸缩但不超过 28 显示单元。默认关闭——`/settings` 的
|
|
1388
|
+
// 「会话名标签」开关(dsh-tui.promptSessionLabel)开启后显示。
|
|
1389
|
+
const sessionTitle = channel.sessionTitle ?? '';
|
|
1390
|
+
const topRightLabel = channel.promptSessionLabel === true && sessionTitle !== ''
|
|
1391
|
+
? {
|
|
1392
|
+
text: truncateToWidth(sessionTitle, Math.max(8, Math.min(28, columns - 8))),
|
|
1393
|
+
color: channel.mode.plan === true ? 'planMode' : (sessionAccent ?? 'claude'),
|
|
1394
|
+
ink: 'inverseText',
|
|
1395
|
+
}
|
|
1396
|
+
: undefined;
|
|
1095
1397
|
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [floatersOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 6, 1), children: [helpOpen && (_jsx(Box, { marginBottom: 1, children: _jsx(HelpMenu, { commands: channel.commandList, viewportHeight: helpViewportHeight, viewportWidth: columns, scrollRef: helpScrollRef, onCommandPick: (name) => {
|
|
1096
1398
|
// 点击命令行 = 填入 /name 并关闭帮助(Tab 补全的鼠标等价)
|
|
1399
|
+
updateFoldBlock(null);
|
|
1097
1400
|
setInput(`/${name} `);
|
|
1098
1401
|
onToggleHelp();
|
|
1099
1402
|
} }) })), !helpOpen && channel.pending.length > 0 && (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, paddingBottom: 1, children: [channel.pending.some(item => item.placement === 'steer') && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["\u26A1 ", t('input-pending-steer-label')] }), channel.pending
|
|
@@ -1120,7 +1423,12 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
|
|
|
1120
1423
|
// 滚轮 = 移动选中行(与 ↑/↓ 同路径,窗口跟随)
|
|
1121
1424
|
onWheelStep: (step) => {
|
|
1122
1425
|
setSelectedCommand(i => Math.max(0, Math.min(suggestions.length - 1, i + step)));
|
|
1123
|
-
} }))
|
|
1426
|
+
} })), peekOpen && (_jsx(Box, { onMouseEnter: hoverEnter, onMouseLeave: hoverLeave, children: _jsx(SuggestionCard, { title: stats, columns: columns, accent: promptAccent, footer: peekClipped
|
|
1427
|
+
? t('input-fold-peek-footer', { lines: foldText.split('\n').length })
|
|
1428
|
+
: undefined, rows: peekVisualLines.map((row, index) => (_jsx(Text, { wrap: "truncate-end", children: row }, index))), onRowPick: () => {
|
|
1429
|
+
updateFoldBlock(null);
|
|
1430
|
+
setHovered(false);
|
|
1431
|
+
} }) }))] })), lastNotification && (_jsx(Box, { position: "absolute", marginTop: -1, height: 1, width: "100%", paddingLeft: 2, paddingRight: 1, flexDirection: "column", justifyContent: "flex-end", overflow: "hidden", children: _jsx(Box, { justifyContent: "flex-end", children: _jsx(Text, { color: lastNotification.color, dimColor: !lastNotification.color, wrap: "truncate", children: lastNotification.text }) }) })), _jsx(EffortInputBorder, { effort: channel.reasoningEffort, levels: channel.effortLevels, columns: columns, onLight: isLightThemeActive(themeName), idleColor: promptAccent, topRightLabel: topRightLabel, children: _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", width: "100%", children: [_jsx(EffortChargeGlyph, { effort: channel.reasoningEffort, levels: channel.effortLevels, working: channel.working }), _jsx(Box, { ref: valueBoxRef, flexGrow: 1, flexShrink: 1, onClick: handleValueClick, children: value.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { inverse: true, children: " " }), _jsx(EffortTierBadge, { effort: channel.reasoningEffort, levels: channel.effortLevels, onLight: isLightThemeActive(themeName), columns: columns, leadingColumns: 3 })] })) : (_jsx(Box, { flexDirection: "column", children: rendered })) })] }) })] }));
|
|
1124
1432
|
}
|
|
1125
1433
|
/**
|
|
1126
1434
|
* Hard-wrap text into visual rows of at most `width` columns (CJK-aware via
|