@travisennis/acai 0.0.9 → 0.0.11
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 +51 -760
- package/bin/acai +52 -0
- package/dist/agent/index.d.ts +12 -2
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +380 -199
- package/dist/agent/sub-agent.d.ts +23 -0
- package/dist/agent/sub-agent.d.ts.map +1 -0
- package/dist/agent/sub-agent.js +109 -0
- package/dist/cli/index.d.ts +26 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/{cli.js → cli/index.js} +84 -77
- package/dist/{stdin.d.ts → cli/stdin.d.ts} +2 -1
- package/dist/cli/stdin.d.ts.map +1 -0
- package/dist/{stdin.js → cli/stdin.js} +11 -0
- package/dist/commands/copy/index.js +2 -2
- package/dist/commands/copy/utils.d.ts.map +1 -1
- package/dist/commands/copy/utils.js +15 -13
- package/dist/commands/generate-rules/index.d.ts +1 -1
- package/dist/commands/generate-rules/index.d.ts.map +1 -1
- package/dist/commands/generate-rules/index.js +16 -101
- package/dist/commands/generate-rules/service.d.ts +22 -0
- package/dist/commands/generate-rules/service.d.ts.map +1 -0
- package/dist/commands/generate-rules/service.js +103 -0
- package/dist/commands/handoff/index.js +2 -2
- package/dist/commands/health/index.js +1 -1
- package/dist/commands/health/utils.d.ts +3 -2
- package/dist/commands/health/utils.d.ts.map +1 -1
- package/dist/commands/health/utils.js +6 -0
- package/dist/commands/history/index.d.ts +1 -1
- package/dist/commands/history/index.d.ts.map +1 -1
- package/dist/commands/history/index.js +17 -18
- package/dist/commands/history/types.d.ts +38 -0
- package/dist/commands/history/types.d.ts.map +1 -1
- package/dist/commands/history/utils.d.ts.map +1 -1
- package/dist/commands/history/utils.js +63 -58
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +3 -8
- package/dist/commands/init-project/index.d.ts.map +1 -1
- package/dist/commands/init-project/index.js +3 -3
- package/dist/commands/init-project/utils.d.ts +2 -1
- package/dist/commands/init-project/utils.d.ts.map +1 -1
- package/dist/commands/init-project/utils.js +10 -2
- package/dist/commands/list-tools/index.d.ts.map +1 -1
- package/dist/commands/list-tools/index.js +7 -31
- package/dist/commands/manager.d.ts +2 -2
- package/dist/commands/manager.d.ts.map +1 -1
- package/dist/commands/manager.js +55 -33
- package/dist/commands/model/index.d.ts.map +1 -1
- package/dist/commands/model/index.js +20 -151
- package/dist/commands/model/model-panel.d.ts +4 -0
- package/dist/commands/model/model-panel.d.ts.map +1 -0
- package/dist/commands/model/model-panel.js +144 -0
- package/dist/commands/paste/index.d.ts.map +1 -1
- package/dist/commands/paste/index.js +59 -62
- package/dist/commands/paste/utils.d.ts.map +1 -1
- package/dist/commands/paste/utils.js +88 -58
- package/dist/commands/pickup/index.d.ts.map +1 -1
- package/dist/commands/pickup/index.js +6 -3
- package/dist/commands/pickup/utils.js +3 -3
- package/dist/commands/resources/index.d.ts.map +1 -1
- package/dist/commands/resources/index.js +33 -50
- package/dist/commands/review/index.d.ts.map +1 -1
- package/dist/commands/review/index.js +3 -117
- package/dist/commands/review/review-panel.d.ts +3 -0
- package/dist/commands/review/review-panel.d.ts.map +1 -0
- package/dist/commands/review/review-panel.js +186 -0
- package/dist/commands/review/utils.d.ts +15 -1
- package/dist/commands/review/utils.d.ts.map +1 -1
- package/dist/commands/review/utils.js +127 -68
- package/dist/commands/session/index.d.ts +1 -1
- package/dist/commands/session/index.d.ts.map +1 -1
- package/dist/commands/session/index.js +124 -135
- package/dist/commands/shell/index.d.ts.map +1 -1
- package/dist/commands/shell/index.js +16 -1
- package/dist/commands/types.d.ts +2 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/{config.d.ts → config/index.d.ts} +20 -9
- package/dist/config/index.d.ts.map +1 -0
- package/dist/{config.js → config/index.js} +43 -42
- package/dist/execution/index.d.ts.map +1 -1
- package/dist/execution/index.js +75 -55
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +148 -141
- package/dist/middleware/cache.d.ts.map +1 -1
- package/dist/middleware/cache.js +18 -36
- package/dist/models/ai-config.d.ts +1 -0
- package/dist/models/ai-config.d.ts.map +1 -1
- package/dist/models/ai-config.js +4 -3
- package/dist/models/anthropic-provider.d.ts +2 -5
- package/dist/models/anthropic-provider.d.ts.map +1 -1
- package/dist/models/anthropic-provider.js +3 -70
- package/dist/models/deepseek-provider.d.ts +1 -0
- package/dist/models/deepseek-provider.d.ts.map +1 -1
- package/dist/models/google-provider.d.ts +2 -3
- package/dist/models/google-provider.d.ts.map +1 -1
- package/dist/models/google-provider.js +0 -26
- package/dist/models/groq-provider.d.ts +1 -0
- package/dist/models/groq-provider.d.ts.map +1 -1
- package/dist/models/manager.d.ts +13 -2
- package/dist/models/manager.d.ts.map +1 -1
- package/dist/models/manager.js +20 -8
- package/dist/models/openai-provider.d.ts +2 -5
- package/dist/models/openai-provider.d.ts.map +1 -1
- package/dist/models/openai-provider.js +0 -52
- package/dist/models/opencode-go-provider.d.ts +25 -0
- package/dist/models/opencode-go-provider.d.ts.map +1 -0
- package/dist/models/opencode-go-provider.js +78 -0
- package/dist/models/opencode-zen-provider.d.ts +7 -3
- package/dist/models/opencode-zen-provider.d.ts.map +1 -1
- package/dist/models/opencode-zen-provider.js +49 -10
- package/dist/models/openrouter-provider.d.ts +27 -31
- package/dist/models/openrouter-provider.d.ts.map +1 -1
- package/dist/models/openrouter-provider.js +121 -180
- package/dist/models/providers.d.ts +3 -3
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +6 -0
- package/dist/models/xai-provider.d.ts +4 -3
- package/dist/models/xai-provider.d.ts.map +1 -1
- package/dist/models/xai-provider.js +18 -18
- package/dist/modes/manager.d.ts +24 -0
- package/dist/modes/manager.d.ts.map +1 -0
- package/dist/modes/manager.js +77 -0
- package/dist/modes/prompts.d.ts +2 -0
- package/dist/modes/prompts.d.ts.map +1 -0
- package/dist/modes/prompts.js +142 -0
- package/dist/prompts/mentions.d.ts +11 -0
- package/dist/prompts/mentions.d.ts.map +1 -0
- package/dist/{mentions.js → prompts/mentions.js} +55 -85
- package/dist/{prompts.d.ts → prompts/system-prompt.d.ts} +7 -2
- package/dist/prompts/system-prompt.d.ts.map +1 -0
- package/dist/{prompts.js → prompts/system-prompt.js} +31 -16
- package/dist/repl/index.d.ts +174 -0
- package/dist/repl/index.d.ts.map +1 -0
- package/dist/{repl-new.js → repl/index.js} +397 -76
- package/dist/repl/project-status.d.ts +1 -0
- package/dist/repl/project-status.d.ts.map +1 -1
- package/dist/repl/project-status.js +4 -1
- package/dist/sessions/manager.d.ts +92 -0
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +262 -9
- package/dist/sessions/summary.d.ts +4 -0
- package/dist/sessions/summary.d.ts.map +1 -0
- package/dist/sessions/summary.js +30 -0
- package/dist/skills/index.d.ts +29 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +294 -0
- package/dist/subagents/index.d.ts +16 -0
- package/dist/subagents/index.d.ts.map +1 -0
- package/dist/subagents/index.js +231 -0
- package/dist/terminal/control.d.ts +1 -1
- package/dist/terminal/control.d.ts.map +1 -1
- package/dist/terminal/control.js +3 -3
- package/dist/terminal/east-asian-width.d.ts.map +1 -1
- package/dist/terminal/east-asian-width.js +404 -351
- package/dist/terminal/keys.d.ts +17 -0
- package/dist/terminal/keys.d.ts.map +1 -1
- package/dist/terminal/keys.js +37 -0
- package/dist/terminal/select-prompt.d.ts.map +1 -1
- package/dist/terminal/select-prompt.js +24 -12
- package/dist/terminal/string-width.d.ts.map +1 -1
- package/dist/terminal/string-width.js +25 -27
- package/dist/terminal/style.d.ts.map +1 -1
- package/dist/terminal/style.js +4 -7
- package/dist/terminal/supports-color.d.ts.map +1 -1
- package/dist/terminal/supports-color.js +41 -27
- package/dist/terminal/table/cell.d.ts +12 -0
- package/dist/terminal/table/cell.d.ts.map +1 -1
- package/dist/terminal/table/cell.js +40 -25
- package/dist/terminal/table/layout-manager.d.ts.map +1 -1
- package/dist/terminal/table/layout-manager.js +100 -68
- package/dist/terminal/table/utils.d.ts +1 -1
- package/dist/terminal/table/utils.d.ts.map +1 -1
- package/dist/terminal/table/utils.js +17 -10
- package/dist/terminal/wrap-ansi.d.ts.map +1 -1
- package/dist/terminal/wrap-ansi.js +174 -105
- package/dist/tokens/tracker.d.ts +1 -0
- package/dist/tokens/tracker.d.ts.map +1 -1
- package/dist/tokens/tracker.js +3 -0
- package/dist/tools/agent.d.ts +27 -0
- package/dist/tools/agent.d.ts.map +1 -0
- package/dist/tools/agent.js +81 -0
- package/dist/tools/apply-patch.d.ts +62 -0
- package/dist/tools/apply-patch.d.ts.map +1 -0
- package/dist/tools/apply-patch.js +377 -0
- package/dist/tools/bash.d.ts +4 -3
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +349 -141
- package/dist/tools/directory-tree.d.ts +3 -3
- package/dist/tools/directory-tree.d.ts.map +1 -1
- package/dist/tools/directory-tree.js +8 -5
- package/dist/tools/dynamic-tool-loader.d.ts +3 -6
- package/dist/tools/dynamic-tool-loader.d.ts.map +1 -1
- package/dist/tools/dynamic-tool-loader.js +20 -4
- package/dist/tools/edit-file.d.ts +7 -7
- package/dist/tools/edit-file.d.ts.map +1 -1
- package/dist/tools/edit-file.js +292 -85
- package/dist/tools/glob.d.ts +6 -6
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +110 -63
- package/dist/tools/grep.d.ts +15 -12
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +315 -193
- package/dist/tools/index.d.ts +114 -9
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +39 -24
- package/dist/tools/ls.d.ts +2 -2
- package/dist/tools/ls.d.ts.map +1 -1
- package/dist/tools/ls.js +7 -5
- package/dist/tools/read-file.d.ts +4 -6
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +84 -39
- package/dist/tools/save-file.d.ts +3 -3
- package/dist/tools/save-file.d.ts.map +1 -1
- package/dist/tools/save-file.js +36 -31
- package/dist/tools/skill.d.ts +23 -0
- package/dist/tools/skill.d.ts.map +1 -0
- package/dist/tools/skill.js +65 -0
- package/dist/tools/think.d.ts.map +1 -1
- package/dist/tools/think.js +2 -9
- package/dist/tools/utils.d.ts +2 -0
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tools/utils.js +12 -0
- package/dist/tools/web-fetch.d.ts +50 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +446 -0
- package/dist/tools/web-search.d.ts +44 -0
- package/dist/tools/web-search.d.ts.map +1 -0
- package/dist/tools/web-search.js +226 -0
- package/dist/tui/autocomplete/attachment-provider.d.ts +3 -6
- package/dist/tui/autocomplete/attachment-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/attachment-provider.js +25 -78
- package/dist/tui/autocomplete/base-provider.d.ts +1 -0
- package/dist/tui/autocomplete/base-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/combined-provider.d.ts +1 -4
- package/dist/tui/autocomplete/combined-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/combined-provider.js +3 -17
- package/dist/tui/autocomplete/command-provider.d.ts +1 -0
- package/dist/tui/autocomplete/command-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/command-provider.js +3 -0
- package/dist/tui/autocomplete/file-search-provider.d.ts +2 -1
- package/dist/tui/autocomplete/file-search-provider.d.ts.map +1 -1
- package/dist/tui/autocomplete/file-search-provider.js +37 -17
- package/dist/tui/autocomplete/skill-provider.d.ts +17 -0
- package/dist/tui/autocomplete/skill-provider.d.ts.map +1 -0
- package/dist/tui/autocomplete/skill-provider.js +49 -0
- package/dist/tui/autocomplete/utils.d.ts +2 -1
- package/dist/tui/autocomplete/utils.d.ts.map +1 -1
- package/dist/tui/autocomplete/utils.js +25 -23
- package/dist/tui/autocomplete.d.ts +2 -2
- package/dist/tui/autocomplete.d.ts.map +1 -1
- package/dist/tui/autocomplete.js +3 -5
- package/dist/tui/components/assistant-message.d.ts.map +1 -1
- package/dist/tui/components/assistant-message.js +0 -4
- package/dist/tui/components/editor.d.ts +18 -3
- package/dist/tui/components/editor.d.ts.map +1 -1
- package/dist/tui/components/editor.js +211 -237
- package/dist/tui/components/footer.d.ts +6 -4
- package/dist/tui/components/footer.d.ts.map +1 -1
- package/dist/tui/components/footer.js +49 -25
- package/dist/tui/components/markdown.d.ts +10 -7
- package/dist/tui/components/markdown.d.ts.map +1 -1
- package/dist/tui/components/markdown.js +57 -39
- package/dist/tui/components/modal.d.ts.map +1 -1
- package/dist/tui/components/modal.js +35 -33
- package/dist/tui/components/notification.d.ts +13 -2
- package/dist/tui/components/notification.d.ts.map +1 -1
- package/dist/tui/components/notification.js +36 -2
- package/dist/tui/components/progress-bar.js +1 -1
- package/dist/tui/components/select-list.d.ts +1 -0
- package/dist/tui/components/select-list.d.ts.map +1 -1
- package/dist/tui/components/select-list.js +14 -11
- package/dist/tui/components/text.d.ts +16 -0
- package/dist/tui/components/text.d.ts.map +1 -1
- package/dist/tui/components/text.js +72 -57
- package/dist/tui/components/thinking-block.d.ts +9 -0
- package/dist/tui/components/thinking-block.d.ts.map +1 -1
- package/dist/tui/components/thinking-block.js +43 -11
- package/dist/tui/components/tool-execution.d.ts +5 -1
- package/dist/tui/components/tool-execution.d.ts.map +1 -1
- package/dist/tui/components/tool-execution.js +19 -10
- package/dist/tui/components/user-message.d.ts.map +1 -1
- package/dist/tui/components/user-message.js +0 -3
- package/dist/tui/components/welcome.d.ts +2 -1
- package/dist/tui/components/welcome.d.ts.map +1 -1
- package/dist/tui/components/welcome.js +2 -2
- package/dist/tui/editor-launcher.d.ts +3 -2
- package/dist/tui/editor-launcher.d.ts.map +1 -1
- package/dist/tui/index.d.ts +0 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/terminal.d.ts.map +1 -1
- package/dist/tui/terminal.js +10 -2
- package/dist/tui/tui.d.ts +43 -0
- package/dist/tui/tui.d.ts.map +1 -1
- package/dist/tui/tui.js +166 -41
- package/dist/tui/utils.d.ts +1 -5
- package/dist/tui/utils.d.ts.map +1 -1
- package/dist/tui/utils.js +271 -44
- package/dist/utils/bash/parse.d.ts +19 -0
- package/dist/utils/bash/parse.d.ts.map +1 -0
- package/dist/utils/bash/parse.js +223 -0
- package/dist/utils/bash/quote.d.ts +6 -0
- package/dist/utils/bash/quote.d.ts.map +1 -0
- package/dist/utils/bash/quote.js +23 -0
- package/dist/utils/bash.d.ts.map +1 -1
- package/dist/utils/bash.js +211 -126
- package/dist/utils/command-protection.d.ts +28 -0
- package/dist/utils/command-protection.d.ts.map +1 -0
- package/dist/utils/command-protection.js +324 -0
- package/dist/utils/dedent.d.ts.map +1 -0
- package/dist/utils/env-expand.d.ts +2 -0
- package/dist/utils/env-expand.d.ts.map +1 -0
- package/dist/utils/env-expand.js +8 -0
- package/dist/utils/filesystem/path-display.d.ts +11 -0
- package/dist/utils/filesystem/path-display.d.ts.map +1 -0
- package/dist/utils/filesystem/path-display.js +32 -0
- package/dist/utils/filesystem/security.d.ts +2 -2
- package/dist/utils/filesystem/security.d.ts.map +1 -1
- package/dist/utils/filesystem/security.js +28 -30
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/{formatting.js → utils/formatting.js} +1 -1
- package/dist/utils/git.d.ts +4 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +30 -0
- package/dist/utils/glob.d.ts +1 -1
- package/dist/utils/glob.d.ts.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/{logger.js → utils/logger.js} +1 -1
- package/dist/utils/parsing.d.ts.map +1 -0
- package/dist/utils/process.d.ts.map +1 -1
- package/dist/utils/process.js +90 -37
- package/dist/utils/templates.d.ts +2 -0
- package/dist/utils/templates.d.ts.map +1 -0
- package/dist/utils/templates.js +24 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/{version.js → utils/version.js} +1 -1
- package/package.json +35 -26
- package/dist/cli.d.ts +0 -23
- package/dist/cli.d.ts.map +0 -1
- package/dist/commands/add-directory/types.d.ts +0 -6
- package/dist/commands/add-directory/types.d.ts.map +0 -1
- package/dist/commands/add-directory/types.js +0 -1
- package/dist/commands/copy/types.d.ts +0 -3
- package/dist/commands/copy/types.d.ts.map +0 -1
- package/dist/commands/copy/types.js +0 -1
- package/dist/commands/exit/index.d.ts +0 -10
- package/dist/commands/exit/index.d.ts.map +0 -1
- package/dist/commands/exit/index.js +0 -21
- package/dist/commands/exit/types.d.ts +0 -8
- package/dist/commands/exit/types.d.ts.map +0 -1
- package/dist/commands/exit/types.js +0 -1
- package/dist/commands/exit/utils.d.ts +0 -2
- package/dist/commands/exit/utils.d.ts.map +0 -1
- package/dist/commands/exit/utils.js +0 -13
- package/dist/commands/prompt/index.d.ts +0 -5
- package/dist/commands/prompt/index.d.ts.map +0 -1
- package/dist/commands/prompt/index.js +0 -122
- package/dist/commands/prompt/types.d.ts +0 -15
- package/dist/commands/prompt/types.d.ts.map +0 -1
- package/dist/commands/prompt/types.js +0 -1
- package/dist/commands/prompt/utils.d.ts +0 -12
- package/dist/commands/prompt/utils.d.ts.map +0 -1
- package/dist/commands/prompt/utils.js +0 -107
- package/dist/commands/reset/index.d.ts +0 -3
- package/dist/commands/reset/index.d.ts.map +0 -1
- package/dist/commands/reset/index.js +0 -25
- package/dist/commands/reset/types.d.ts +0 -1
- package/dist/commands/reset/types.d.ts.map +0 -1
- package/dist/commands/reset/types.js +0 -3
- package/dist/commands/review/types.d.ts +0 -12
- package/dist/commands/review/types.d.ts.map +0 -1
- package/dist/commands/review/types.js +0 -1
- package/dist/commands/save/index.d.ts +0 -3
- package/dist/commands/save/index.d.ts.map +0 -1
- package/dist/commands/save/index.js +0 -19
- package/dist/config.d.ts.map +0 -1
- package/dist/dedent.d.ts.map +0 -1
- package/dist/formatting.d.ts.map +0 -1
- package/dist/logger.d.ts.map +0 -1
- package/dist/mentions.d.ts +0 -14
- package/dist/mentions.d.ts.map +0 -1
- package/dist/parsing.d.ts.map +0 -1
- package/dist/prompts.d.ts.map +0 -1
- package/dist/repl-new.d.ts +0 -65
- package/dist/repl-new.d.ts.map +0 -1
- package/dist/skills.d.ts +0 -16
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js +0 -233
- package/dist/stdin.d.ts.map +0 -1
- package/dist/tui/autocomplete/path-provider.d.ts +0 -21
- package/dist/tui/autocomplete/path-provider.d.ts.map +0 -1
- package/dist/tui/autocomplete/path-provider.js +0 -164
- package/dist/utils/iterables.d.ts +0 -2
- package/dist/utils/iterables.d.ts.map +0 -1
- package/dist/utils/iterables.js +0 -6
- package/dist/version.d.ts.map +0 -1
- /package/dist/{dedent.d.ts → utils/dedent.d.ts} +0 -0
- /package/dist/{dedent.js → utils/dedent.js} +0 -0
- /package/dist/{formatting.d.ts → utils/formatting.d.ts} +0 -0
- /package/dist/{logger.d.ts → utils/logger.d.ts} +0 -0
- /package/dist/{parsing.d.ts → utils/parsing.d.ts} +0 -0
- /package/dist/{parsing.js → utils/parsing.js} +0 -0
- /package/dist/{version.d.ts → utils/version.d.ts} +0 -0
package/dist/tui/tui.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Minimal TUI implementation with differential rendering
|
|
3
3
|
*/
|
|
4
|
-
import { getTerminalSize, isCtrlC, isCtrlZ, isEscape, } from "../terminal/control.js";
|
|
4
|
+
import { getTerminalSize, isCtrlC, isCtrlD, isCtrlM, isCtrlN, isCtrlO, isCtrlR, isCtrlZ, isEscape, } from "../terminal/control.js";
|
|
5
|
+
import { isShiftTab } from "../terminal/keys.js";
|
|
5
6
|
import style from "../terminal/style.js";
|
|
6
7
|
import { visibleWidth } from "./utils.js";
|
|
7
8
|
export { visibleWidth };
|
|
@@ -19,6 +20,15 @@ export class Container {
|
|
|
19
20
|
this.children.splice(index, 1);
|
|
20
21
|
}
|
|
21
22
|
}
|
|
23
|
+
insertChildBefore(beforeComponent, newComponent) {
|
|
24
|
+
const index = this.children.indexOf(beforeComponent);
|
|
25
|
+
if (index !== -1) {
|
|
26
|
+
this.children.splice(index, 0, newComponent);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
this.children.push(newComponent);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
22
32
|
clear() {
|
|
23
33
|
this.children = [];
|
|
24
34
|
}
|
|
@@ -28,6 +38,14 @@ export class Container {
|
|
|
28
38
|
}
|
|
29
39
|
/**
|
|
30
40
|
* TUI - Main class for managing terminal UI with differential rendering
|
|
41
|
+
*
|
|
42
|
+
* The TUI splits its children into two regions:
|
|
43
|
+
* - Scrollable content: children added before setFixedFooterStart()
|
|
44
|
+
* - Fixed footer: children added after setFixedFooterStart()
|
|
45
|
+
*
|
|
46
|
+
* The scrollable content region supports virtual scrolling via trackpad/mouse
|
|
47
|
+
* wheel. The fixed footer (editor, footer bar, notifications) is always
|
|
48
|
+
* pinned to the bottom of the terminal.
|
|
31
49
|
*/
|
|
32
50
|
// biome-ignore lint/style/useNamingConvention: override
|
|
33
51
|
export class TUI extends Container {
|
|
@@ -37,14 +55,41 @@ export class TUI extends Container {
|
|
|
37
55
|
isRendering = false;
|
|
38
56
|
renderAgain = false;
|
|
39
57
|
activeModal = null;
|
|
58
|
+
scrollOffset = 0;
|
|
59
|
+
lastScrollableHeight = 0;
|
|
60
|
+
fixedFooterIndex = -1;
|
|
61
|
+
isUserScrolledUp = false;
|
|
40
62
|
onCtrlC;
|
|
63
|
+
onCtrlD;
|
|
64
|
+
onReconstructSession;
|
|
65
|
+
onCtrlN;
|
|
66
|
+
onCtrlO;
|
|
67
|
+
/** Callback invoked when Ctrl+M is pressed - opens model selector. */
|
|
68
|
+
onCtrlM;
|
|
69
|
+
onCtrlR;
|
|
70
|
+
onShiftTab;
|
|
41
71
|
constructor(terminal) {
|
|
42
72
|
super();
|
|
43
73
|
this.terminal = terminal;
|
|
44
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Mark the boundary between scrollable content and fixed footer.
|
|
77
|
+
* All children added after this call will be rendered as fixed
|
|
78
|
+
* footer content pinned to the bottom of the terminal.
|
|
79
|
+
*/
|
|
80
|
+
setFixedFooterStart() {
|
|
81
|
+
this.fixedFooterIndex = this.children.length;
|
|
82
|
+
}
|
|
45
83
|
setFocus(component) {
|
|
46
84
|
this.focusedComponent = component;
|
|
47
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Get the underlying terminal instance for external mode operations.
|
|
88
|
+
* Used primarily for spawning external editors.
|
|
89
|
+
*/
|
|
90
|
+
getTerminal() {
|
|
91
|
+
return this.terminal;
|
|
92
|
+
}
|
|
48
93
|
start() {
|
|
49
94
|
this.terminal.start((data) => this.handleInput(data), () => this.requestRender());
|
|
50
95
|
this.terminal.onResume(() => this.requestRender());
|
|
@@ -69,7 +114,15 @@ export class TUI extends Container {
|
|
|
69
114
|
});
|
|
70
115
|
}
|
|
71
116
|
inBracketedPaste = false;
|
|
117
|
+
focusedComponentWantsNavigation() {
|
|
118
|
+
return this.focusedComponent?.wantsNavigationKeys?.() ?? false;
|
|
119
|
+
}
|
|
72
120
|
handleInput(data) {
|
|
121
|
+
// Handle mouse tracking events (SGR format: \x1b[<button;x;yM)
|
|
122
|
+
if (data.startsWith("\x1b[<")) {
|
|
123
|
+
this.handleMouseEvent(data);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
73
126
|
if (data.includes("\x1b[200~")) {
|
|
74
127
|
this.inBracketedPaste = true;
|
|
75
128
|
}
|
|
@@ -94,6 +147,36 @@ export class TUI extends Container {
|
|
|
94
147
|
process.exit(0);
|
|
95
148
|
}
|
|
96
149
|
}
|
|
150
|
+
// Handle Ctrl+D - exit only if editor is empty (handled by Repl)
|
|
151
|
+
if (isCtrlD(data)) {
|
|
152
|
+
if (this.onCtrlD) {
|
|
153
|
+
this.onCtrlD();
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const keyBindings = [
|
|
158
|
+
{ check: isCtrlO, handler: this.onCtrlO },
|
|
159
|
+
{ check: isCtrlR, handler: this.onCtrlR },
|
|
160
|
+
{ check: isCtrlN, handler: this.onCtrlN },
|
|
161
|
+
{ check: isCtrlM, handler: this.onCtrlM },
|
|
162
|
+
];
|
|
163
|
+
for (const binding of keyBindings) {
|
|
164
|
+
if (binding.check(data)) {
|
|
165
|
+
binding.handler?.();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Handle Shift+Tab - cycle mode only when no modal is active,
|
|
170
|
+
// the focused component won't handle navigation (e.g., model selector),
|
|
171
|
+
// and the editor isn't showing autocomplete
|
|
172
|
+
if (isShiftTab(data) && !this.inBracketedPaste) {
|
|
173
|
+
if (!this.activeModal && !this.focusedComponentWantsNavigation()) {
|
|
174
|
+
if (this.onShiftTab) {
|
|
175
|
+
this.onShiftTab();
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
97
180
|
// Handle Escape key to close modal if one is active
|
|
98
181
|
if (isEscape(data) && this.activeModal) {
|
|
99
182
|
this.hideModal();
|
|
@@ -106,9 +189,69 @@ export class TUI extends Container {
|
|
|
106
189
|
}
|
|
107
190
|
else if (this.focusedComponent?.handleInput) {
|
|
108
191
|
this.focusedComponent.handleInput(data);
|
|
192
|
+
this.scrollToBottom();
|
|
193
|
+
this.requestRender();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
handleMouseEvent(data) {
|
|
197
|
+
// SGR mouse format: \x1b[<button;x;yM (press) or \x1b[<button;x;ym (release)
|
|
198
|
+
// Extract button code between "<" and first ";"
|
|
199
|
+
const start = data.indexOf("<");
|
|
200
|
+
const semi = data.indexOf(";", start);
|
|
201
|
+
if (start === -1 || semi === -1)
|
|
202
|
+
return;
|
|
203
|
+
const button = Number.parseInt(data.slice(start + 1, semi), 10);
|
|
204
|
+
const scrollLines = 3;
|
|
205
|
+
const { rows } = getTerminalSize();
|
|
206
|
+
const fixedHeight = this.getFixedFooterHeight(this.terminal.columns);
|
|
207
|
+
const scrollableViewport = rows - fixedHeight;
|
|
208
|
+
// Button 64 = scroll up, button 65 = scroll down
|
|
209
|
+
if (button === 64) {
|
|
210
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - scrollLines);
|
|
211
|
+
const maxOffset = Math.max(0, this.lastScrollableHeight - scrollableViewport);
|
|
212
|
+
this.isUserScrolledUp = this.scrollOffset < maxOffset;
|
|
213
|
+
this.requestRender();
|
|
214
|
+
}
|
|
215
|
+
else if (button === 65) {
|
|
216
|
+
const maxOffset = Math.max(0, this.lastScrollableHeight - scrollableViewport);
|
|
217
|
+
this.scrollOffset = Math.min(maxOffset, this.scrollOffset + scrollLines);
|
|
218
|
+
this.isUserScrolledUp = this.scrollOffset < maxOffset;
|
|
109
219
|
this.requestRender();
|
|
110
220
|
}
|
|
111
221
|
}
|
|
222
|
+
scrollToBottom() {
|
|
223
|
+
const { rows } = getTerminalSize();
|
|
224
|
+
const fixedHeight = this.getFixedFooterHeight(this.terminal.columns);
|
|
225
|
+
const scrollableViewport = rows - fixedHeight;
|
|
226
|
+
this.scrollOffset = Math.max(0, this.lastScrollableHeight - scrollableViewport);
|
|
227
|
+
this.isUserScrolledUp = false;
|
|
228
|
+
}
|
|
229
|
+
getFixedFooterHeight(width) {
|
|
230
|
+
if (this.fixedFooterIndex < 0)
|
|
231
|
+
return 0;
|
|
232
|
+
let height = 0;
|
|
233
|
+
for (let i = this.fixedFooterIndex; i < this.children.length; i++) {
|
|
234
|
+
height += this.children[i].render(width).length;
|
|
235
|
+
}
|
|
236
|
+
return height;
|
|
237
|
+
}
|
|
238
|
+
renderScrollableContent(width) {
|
|
239
|
+
const end = this.fixedFooterIndex >= 0 ? this.fixedFooterIndex : this.children.length;
|
|
240
|
+
const lines = [];
|
|
241
|
+
for (let i = 0; i < end; i++) {
|
|
242
|
+
lines.push(...this.children[i].render(width));
|
|
243
|
+
}
|
|
244
|
+
return lines;
|
|
245
|
+
}
|
|
246
|
+
renderFixedFooter(width) {
|
|
247
|
+
if (this.fixedFooterIndex < 0)
|
|
248
|
+
return [];
|
|
249
|
+
const lines = [];
|
|
250
|
+
for (let i = this.fixedFooterIndex; i < this.children.length; i++) {
|
|
251
|
+
lines.push(...this.children[i].render(width));
|
|
252
|
+
}
|
|
253
|
+
return lines;
|
|
254
|
+
}
|
|
112
255
|
doRender() {
|
|
113
256
|
if (this.isRendering) {
|
|
114
257
|
this.renderAgain = true;
|
|
@@ -117,13 +260,32 @@ export class TUI extends Container {
|
|
|
117
260
|
this.isRendering = true;
|
|
118
261
|
try {
|
|
119
262
|
const width = this.terminal.columns;
|
|
120
|
-
|
|
121
|
-
|
|
263
|
+
const { rows } = getTerminalSize();
|
|
264
|
+
// Render fixed footer and scrollable content separately
|
|
265
|
+
const fixedLines = this.renderFixedFooter(width);
|
|
266
|
+
const scrollableLines = this.renderScrollableContent(width);
|
|
267
|
+
this.lastScrollableHeight = scrollableLines.length;
|
|
268
|
+
const fixedHeight = fixedLines.length;
|
|
269
|
+
const scrollableViewport = rows - fixedHeight;
|
|
270
|
+
// Auto-scroll to bottom when new content arrives,
|
|
271
|
+
// unless the user has explicitly scrolled up
|
|
272
|
+
if (!this.isUserScrolledUp) {
|
|
273
|
+
this.scrollOffset = Math.max(0, scrollableLines.length - scrollableViewport);
|
|
274
|
+
}
|
|
275
|
+
// Clamp scroll offset
|
|
276
|
+
const maxOffset = Math.max(0, scrollableLines.length - scrollableViewport);
|
|
277
|
+
if (this.scrollOffset > maxOffset) {
|
|
278
|
+
this.scrollOffset = maxOffset;
|
|
279
|
+
}
|
|
280
|
+
// Apply scroll offset to get visible scrollable lines
|
|
281
|
+
const visibleScrollable = scrollableLines.slice(this.scrollOffset, this.scrollOffset + scrollableViewport);
|
|
282
|
+
// Combine visible scrollable content with fixed footer
|
|
283
|
+
const visibleLines = [...visibleScrollable, ...fixedLines];
|
|
122
284
|
// Build output buffer using array join (more efficient than string concat)
|
|
123
285
|
const bufferParts = [
|
|
124
286
|
"\x1b[?2026h", // Begin synchronized output
|
|
125
287
|
"\x1b[3J\x1b[2J\x1b[H", // Clear scrollback, screen, and home
|
|
126
|
-
|
|
288
|
+
visibleLines.join("\r\n"),
|
|
127
289
|
];
|
|
128
290
|
// Render modal on top if active
|
|
129
291
|
if (this.activeModal) {
|
|
@@ -154,43 +316,6 @@ export class TUI extends Container {
|
|
|
154
316
|
}
|
|
155
317
|
}
|
|
156
318
|
}
|
|
157
|
-
// private positionCursor(
|
|
158
|
-
// componentCursorPos: [number, number] | null,
|
|
159
|
-
// ): void {
|
|
160
|
-
// if (!componentCursorPos) {
|
|
161
|
-
// // No cursor position from component, hide cursor
|
|
162
|
-
// this.terminal.hideCursor();
|
|
163
|
-
// return;
|
|
164
|
-
// }
|
|
165
|
-
// const [cursorRow, cursorCol] = componentCursorPos;
|
|
166
|
-
//
|
|
167
|
-
// // Calculate absolute cursor position in the terminal
|
|
168
|
-
// // We need to find which line in newLines corresponds to the component's cursor row
|
|
169
|
-
// // and then position the cursor at that line and column
|
|
170
|
-
//
|
|
171
|
-
// // Find the line offset for the focused component
|
|
172
|
-
// let componentStartLine = 0;
|
|
173
|
-
// if (this.focusedComponent) {
|
|
174
|
-
// // Find the line where this component starts by summing heights of previous components
|
|
175
|
-
// for (const child of this.children) {
|
|
176
|
-
// if (child === this.focusedComponent) {
|
|
177
|
-
// break;
|
|
178
|
-
// }
|
|
179
|
-
// // Use the already-rendered lines to calculate height, not re-render
|
|
180
|
-
// const childLines = child.render(this.terminal.columns);
|
|
181
|
-
// componentStartLine += childLines.length;
|
|
182
|
-
// }
|
|
183
|
-
// }
|
|
184
|
-
//
|
|
185
|
-
// const absoluteRow = componentStartLine + cursorRow;
|
|
186
|
-
// const absoluteCol = cursorCol;
|
|
187
|
-
//
|
|
188
|
-
// // Position cursor using absolute positioning
|
|
189
|
-
// // Move to home position first, then move down to row, then right to column
|
|
190
|
-
// // Note: terminal rows/columns are 1-indexed, so we add 1
|
|
191
|
-
// this.terminal.write(`\x1b[H\x1b[${absoluteRow + 1}B\x1b[${absoluteCol + 1}G`);
|
|
192
|
-
// this.terminal.showCursor();
|
|
193
|
-
// }
|
|
194
319
|
/**
|
|
195
320
|
* Show a modal dialog
|
|
196
321
|
*/
|
package/dist/tui/utils.d.ts
CHANGED
|
@@ -7,11 +7,7 @@
|
|
|
7
7
|
* - Tabs (replaced with 3 spaces for consistent width)
|
|
8
8
|
*/
|
|
9
9
|
export declare function visibleWidth(str: string): number;
|
|
10
|
-
|
|
11
|
-
* Truncate text to fit within a maximum visible width, preserving ANSI codes.
|
|
12
|
-
* Adds ellipsis if truncation occurs.
|
|
13
|
-
*/
|
|
14
|
-
export declare function truncateToWidth(text: string, maxWidth: number, ellipsis?: string): string;
|
|
10
|
+
export declare function truncateToWidth(text: string, maxWidth: number, ellipsis?: string, pad?: boolean): string;
|
|
15
11
|
/**
|
|
16
12
|
* Apply background color to a line, padding to full width.
|
|
17
13
|
*
|
package/dist/tui/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../source/tui/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../source/tui/utils.ts"],"names":[],"mappings":"AAgBA;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIhD;AAwOD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,QAAQ,SAAQ,EAChB,GAAG,UAAQ,GACV,MAAM,CAuER;AAwHD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAC7B,MAAM,CASR"}
|
package/dist/tui/utils.js
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { getSegmenter } from "../terminal/segmenter.js";
|
|
2
2
|
import stringWidth from "../terminal/string-width.js";
|
|
3
|
+
/**
|
|
4
|
+
* Check if a string contains only printable ASCII characters (0x20-0x7e).
|
|
5
|
+
*/
|
|
6
|
+
function isPrintableAscii(str) {
|
|
7
|
+
for (let i = 0; i < str.length; i++) {
|
|
8
|
+
const code = str.charCodeAt(i);
|
|
9
|
+
if (code < 0x20 || code > 0x7e) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
3
15
|
/**
|
|
4
16
|
* Calculate the visible width of a string in terminal columns.
|
|
5
17
|
* This correctly handles:
|
|
@@ -13,6 +25,12 @@ export function visibleWidth(str) {
|
|
|
13
25
|
const normalized = str.replace(/\t/g, " ");
|
|
14
26
|
return stringWidth(normalized);
|
|
15
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the width of a single grapheme cluster.
|
|
30
|
+
*/
|
|
31
|
+
function graphemeWidth(grapheme) {
|
|
32
|
+
return stringWidth(grapheme);
|
|
33
|
+
}
|
|
16
34
|
/**
|
|
17
35
|
* Extract ANSI escape sequences from a string at the given position.
|
|
18
36
|
*/
|
|
@@ -32,64 +50,273 @@ function extractAnsiCode(str, pos) {
|
|
|
32
50
|
}
|
|
33
51
|
return null;
|
|
34
52
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
function createInitialState() {
|
|
54
|
+
return {
|
|
55
|
+
result: "",
|
|
56
|
+
pendingAnsi: "",
|
|
57
|
+
visibleSoFar: 0,
|
|
58
|
+
keptWidth: 0,
|
|
59
|
+
keepContiguousPrefix: true,
|
|
60
|
+
overflowed: false,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function processAnsiCode(state, text, pos) {
|
|
64
|
+
const ansi = extractAnsiCode(text, pos);
|
|
65
|
+
if (ansi) {
|
|
66
|
+
state.pendingAnsi += ansi.code;
|
|
67
|
+
return ansi.length;
|
|
68
|
+
}
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
function processTab(state, targetWidth, maxWidth) {
|
|
72
|
+
if (state.keepContiguousPrefix && state.keptWidth + 3 <= targetWidth) {
|
|
73
|
+
if (state.pendingAnsi) {
|
|
74
|
+
state.result += state.pendingAnsi;
|
|
75
|
+
state.pendingAnsi = "";
|
|
76
|
+
}
|
|
77
|
+
state.result += "\t";
|
|
78
|
+
state.keptWidth += 3;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
state.keepContiguousPrefix = false;
|
|
82
|
+
state.pendingAnsi = "";
|
|
83
|
+
}
|
|
84
|
+
state.visibleSoFar += 3;
|
|
85
|
+
if (state.visibleSoFar > maxWidth) {
|
|
86
|
+
state.overflowed = true;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function processGraphemeSegment(state, segment, targetWidth, maxWidth) {
|
|
92
|
+
const width = graphemeWidth(segment);
|
|
93
|
+
if (state.keepContiguousPrefix && state.keptWidth + width <= targetWidth) {
|
|
94
|
+
if (state.pendingAnsi) {
|
|
95
|
+
state.result += state.pendingAnsi;
|
|
96
|
+
state.pendingAnsi = "";
|
|
97
|
+
}
|
|
98
|
+
state.result += segment;
|
|
99
|
+
state.keptWidth += width;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
state.keepContiguousPrefix = false;
|
|
103
|
+
state.pendingAnsi = "";
|
|
104
|
+
}
|
|
105
|
+
state.visibleSoFar += width;
|
|
106
|
+
if (state.visibleSoFar > maxWidth) {
|
|
107
|
+
state.overflowed = true;
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
function truncateSimple(text, targetWidth, maxWidth) {
|
|
113
|
+
const state = createInitialState();
|
|
114
|
+
for (const { segment } of getSegmenter().segment(text)) {
|
|
115
|
+
if (processGraphemeSegment(state, segment, targetWidth, maxWidth)) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
result: state.result,
|
|
121
|
+
keptWidth: state.keptWidth,
|
|
122
|
+
overflowed: state.overflowed,
|
|
123
|
+
visibleSoFar: state.visibleSoFar,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function truncateComplex(text, targetWidth, maxWidth) {
|
|
127
|
+
const state = createInitialState();
|
|
128
|
+
let i = 0;
|
|
129
|
+
while (i < text.length && !state.overflowed) {
|
|
130
|
+
const ansiSkip = processAnsiCode(state, text, i);
|
|
131
|
+
if (ansiSkip > 0) {
|
|
132
|
+
i += ansiSkip;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (text[i] === "\t") {
|
|
136
|
+
if (processTab(state, targetWidth, maxWidth)) {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
i++;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Find end of current text segment (until tab or ANSI)
|
|
143
|
+
let end = i;
|
|
144
|
+
while (end < text.length && text[end] !== "\t") {
|
|
145
|
+
const nextAnsi = extractAnsiCode(text, end);
|
|
146
|
+
if (nextAnsi) {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
end++;
|
|
150
|
+
}
|
|
151
|
+
// Process graphemes in this segment
|
|
152
|
+
for (const { segment } of getSegmenter().segment(text.slice(i, end))) {
|
|
153
|
+
if (processGraphemeSegment(state, segment, targetWidth, maxWidth)) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
i = end;
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
result: state.result,
|
|
161
|
+
keptWidth: state.keptWidth,
|
|
162
|
+
overflowed: state.overflowed,
|
|
163
|
+
exhaustedInput: i >= text.length,
|
|
164
|
+
visibleSoFar: state.visibleSoFar,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function handleOversizedEllipsis(text, maxWidth, ellipsis, pad) {
|
|
168
|
+
const textWidth = visibleWidth(text);
|
|
169
|
+
if (textWidth <= maxWidth) {
|
|
170
|
+
return pad ? text + " ".repeat(maxWidth - textWidth) : text;
|
|
171
|
+
}
|
|
172
|
+
const clippedEllipsis = truncateFragmentToWidth(ellipsis, maxWidth);
|
|
173
|
+
if (clippedEllipsis.width === 0) {
|
|
174
|
+
return pad ? " ".repeat(maxWidth) : "";
|
|
175
|
+
}
|
|
176
|
+
return finalizeTruncatedResult("", 0, clippedEllipsis.text, clippedEllipsis.width, maxWidth, pad);
|
|
177
|
+
}
|
|
178
|
+
export function truncateToWidth(text, maxWidth, ellipsis = "...", pad = false) {
|
|
179
|
+
if (maxWidth <= 0) {
|
|
180
|
+
return "";
|
|
181
|
+
}
|
|
182
|
+
if (text.length === 0) {
|
|
183
|
+
return pad ? " ".repeat(maxWidth) : "";
|
|
43
184
|
}
|
|
44
185
|
const ellipsisWidth = visibleWidth(ellipsis);
|
|
186
|
+
// Handle case where ellipsis is wider than maxWidth
|
|
187
|
+
if (ellipsisWidth >= maxWidth) {
|
|
188
|
+
const result = handleOversizedEllipsis(text, maxWidth, ellipsis, pad);
|
|
189
|
+
if (result !== null) {
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Fast path: pure ASCII printable
|
|
194
|
+
if (isPrintableAscii(text)) {
|
|
195
|
+
if (text.length <= maxWidth) {
|
|
196
|
+
return pad ? text + " ".repeat(maxWidth - text.length) : text;
|
|
197
|
+
}
|
|
198
|
+
const targetWidth = maxWidth - ellipsisWidth;
|
|
199
|
+
return finalizeTruncatedResult(text.slice(0, targetWidth), targetWidth, ellipsis, ellipsisWidth, maxWidth, pad);
|
|
200
|
+
}
|
|
45
201
|
const targetWidth = maxWidth - ellipsisWidth;
|
|
46
|
-
|
|
47
|
-
|
|
202
|
+
const hasAnsi = text.includes("\x1b");
|
|
203
|
+
const hasTabs = text.includes("\t");
|
|
204
|
+
let truncatedResult;
|
|
205
|
+
let exhaustedInput;
|
|
206
|
+
if (!hasAnsi && !hasTabs) {
|
|
207
|
+
truncatedResult = truncateSimple(text, targetWidth, maxWidth);
|
|
208
|
+
exhaustedInput = !truncatedResult.overflowed;
|
|
48
209
|
}
|
|
49
|
-
|
|
210
|
+
else {
|
|
211
|
+
const complexResult = truncateComplex(text, targetWidth, maxWidth);
|
|
212
|
+
truncatedResult = complexResult;
|
|
213
|
+
exhaustedInput = complexResult.exhaustedInput;
|
|
214
|
+
}
|
|
215
|
+
// Text fits completely
|
|
216
|
+
if (!truncatedResult.overflowed && exhaustedInput) {
|
|
217
|
+
return pad
|
|
218
|
+
? text + " ".repeat(Math.max(0, maxWidth - truncatedResult.visibleSoFar))
|
|
219
|
+
: text;
|
|
220
|
+
}
|
|
221
|
+
return finalizeTruncatedResult(truncatedResult.result, truncatedResult.keptWidth, ellipsis, ellipsisWidth, maxWidth, pad);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Truncate a single fragment to a maximum width.
|
|
225
|
+
*/
|
|
226
|
+
function truncateFragmentToWidth(text, maxWidth) {
|
|
227
|
+
if (maxWidth <= 0 || text.length === 0) {
|
|
228
|
+
return { text: "", width: 0 };
|
|
229
|
+
}
|
|
230
|
+
if (isPrintableAscii(text)) {
|
|
231
|
+
const clipped = text.slice(0, maxWidth);
|
|
232
|
+
return { text: clipped, width: clipped.length };
|
|
233
|
+
}
|
|
234
|
+
const hasAnsi = text.includes("\x1b");
|
|
235
|
+
const hasTabs = text.includes("\t");
|
|
236
|
+
if (!hasAnsi && !hasTabs) {
|
|
237
|
+
let result = "";
|
|
238
|
+
let width = 0;
|
|
239
|
+
for (const { segment } of getSegmenter().segment(text)) {
|
|
240
|
+
const w = graphemeWidth(segment);
|
|
241
|
+
if (width + w > maxWidth) {
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
result += segment;
|
|
245
|
+
width += w;
|
|
246
|
+
}
|
|
247
|
+
return { text: result, width };
|
|
248
|
+
}
|
|
249
|
+
let result = "";
|
|
250
|
+
let width = 0;
|
|
50
251
|
let i = 0;
|
|
51
|
-
|
|
252
|
+
let pendingAnsi = "";
|
|
52
253
|
while (i < text.length) {
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
i +=
|
|
254
|
+
const ansi = extractAnsiCode(text, i);
|
|
255
|
+
if (ansi) {
|
|
256
|
+
pendingAnsi += ansi.code;
|
|
257
|
+
i += ansi.length;
|
|
258
|
+
continue;
|
|
57
259
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
260
|
+
if (text[i] === "\t") {
|
|
261
|
+
if (width + 3 > maxWidth) {
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
if (pendingAnsi) {
|
|
265
|
+
result += pendingAnsi;
|
|
266
|
+
pendingAnsi = "";
|
|
267
|
+
}
|
|
268
|
+
result += "\t";
|
|
269
|
+
width += 3;
|
|
270
|
+
i++;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
let end = i;
|
|
274
|
+
while (end < text.length && text[end] !== "\t") {
|
|
275
|
+
const nextAnsi = extractAnsiCode(text, end);
|
|
276
|
+
if (nextAnsi) {
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
end++;
|
|
280
|
+
}
|
|
281
|
+
for (const { segment } of getSegmenter().segment(text.slice(i, end))) {
|
|
282
|
+
const w = graphemeWidth(segment);
|
|
283
|
+
if (width + w > maxWidth) {
|
|
284
|
+
return { text: result, width };
|
|
66
285
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
segments.push({ type: "grapheme", value: seg.segment });
|
|
286
|
+
if (pendingAnsi) {
|
|
287
|
+
result += pendingAnsi;
|
|
288
|
+
pendingAnsi = "";
|
|
71
289
|
}
|
|
72
|
-
|
|
290
|
+
result += segment;
|
|
291
|
+
width += w;
|
|
73
292
|
}
|
|
293
|
+
i = end;
|
|
74
294
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
295
|
+
return { text: result, width };
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Finalize a truncated result with proper ANSI reset codes.
|
|
299
|
+
* Only adds reset codes if the prefix contains ANSI sequences.
|
|
300
|
+
*/
|
|
301
|
+
function finalizeTruncatedResult(prefix, prefixWidth, ellipsis, ellipsisWidth, maxWidth, pad) {
|
|
302
|
+
const hasAnsi = prefix.includes("\x1b");
|
|
303
|
+
const visibleWidth = prefixWidth + ellipsisWidth;
|
|
304
|
+
let result;
|
|
305
|
+
if (hasAnsi) {
|
|
306
|
+
const reset = "\x1b[0m";
|
|
307
|
+
if (ellipsis.length > 0) {
|
|
308
|
+
result = `${prefix}${reset}${ellipsis}${reset}`;
|
|
82
309
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (currentWidth + graphemeWidth > targetWidth) {
|
|
86
|
-
break;
|
|
310
|
+
else {
|
|
311
|
+
result = `${prefix}${reset}`;
|
|
87
312
|
}
|
|
88
|
-
result += grapheme;
|
|
89
|
-
currentWidth += graphemeWidth;
|
|
90
313
|
}
|
|
91
|
-
|
|
92
|
-
|
|
314
|
+
else {
|
|
315
|
+
result = ellipsis.length > 0 ? `${prefix}${ellipsis}` : prefix;
|
|
316
|
+
}
|
|
317
|
+
return pad
|
|
318
|
+
? result + " ".repeat(Math.max(0, maxWidth - visibleWidth))
|
|
319
|
+
: result;
|
|
93
320
|
}
|
|
94
321
|
/**
|
|
95
322
|
* Apply background color to a line, padding to full width.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface ControlOperator {
|
|
2
|
+
op: "||" | "&&" | ";;" | "|&" | "<(" | "<<<" | ">>" | ">&" | "&" | ";" | "(" | ")" | "|" | "<" | ">";
|
|
3
|
+
}
|
|
4
|
+
interface GlobOperator {
|
|
5
|
+
op: "glob";
|
|
6
|
+
pattern: string;
|
|
7
|
+
}
|
|
8
|
+
interface CommentOperator {
|
|
9
|
+
comment: string;
|
|
10
|
+
}
|
|
11
|
+
type ParseEntry = string | ControlOperator | GlobOperator | CommentOperator;
|
|
12
|
+
interface ParseOptions {
|
|
13
|
+
escape?: string;
|
|
14
|
+
}
|
|
15
|
+
type EnvFunction = (key: string) => unknown;
|
|
16
|
+
export declare function parse(s: string, env?: Record<string, string | undefined> | EnvFunction, opts?: ParseOptions): ParseEntry[];
|
|
17
|
+
export declare function parse<T extends object | string>(s: string, env: (key: string) => T | undefined, opts?: ParseOptions): (ParseEntry | T)[];
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../source/utils/bash/parse.ts"],"names":[],"mappings":"AA+BA,UAAU,eAAe;IACvB,EAAE,EACE,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,IAAI,GACJ,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,GACH,GAAG,CAAC;CACT;AAED,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,UAAU,GAAG,MAAM,GAAG,eAAe,GAAG,YAAY,GAAG,eAAe,CAAC;AAE5E,UAAU,YAAY;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;AA8L5C,wBAAgB,KAAK,CACnB,CAAC,EAAE,MAAM,EACT,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,WAAW,EACtD,IAAI,CAAC,EAAE,YAAY,GAClB,UAAU,EAAE,CAAC;AAChB,wBAAgB,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC7C,CAAC,EAAE,MAAM,EACT,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,EACnC,IAAI,CAAC,EAAE,YAAY,GAClB,CAAC,UAAU,GAAG,CAAC,CAAC,EAAE,CAAC"}
|