@travisennis/acai 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -729
- 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 +378 -168
- 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 -76
- package/dist/cli/stdin.d.ts +9 -0
- package/dist/cli/stdin.d.ts.map +1 -0
- package/dist/cli/stdin.js +37 -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 -100
- package/dist/commands/generate-rules/service.d.ts +21 -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.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.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 +57 -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 +9 -0
- 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 +134 -112
- package/dist/commands/session/types.d.ts +7 -0
- package/dist/commands/session/types.d.ts.map +1 -1
- package/dist/commands/share/html-renderer.d.ts +25 -0
- package/dist/commands/share/html-renderer.d.ts.map +1 -0
- package/dist/commands/share/html-renderer.js +384 -0
- package/dist/commands/share/index.d.ts +3 -0
- package/dist/commands/share/index.d.ts.map +1 -0
- package/dist/commands/share/index.js +122 -0
- 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 +170 -127
- 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 +5 -5
- package/dist/models/openai-provider.d.ts.map +1 -1
- package/dist/models/openai-provider.js +27 -40
- package/dist/models/opencode-zen-provider.d.ts +8 -3
- package/dist/models/opencode-zen-provider.d.ts.map +1 -1
- package/dist/models/opencode-zen-provider.js +68 -11
- package/dist/models/openrouter-provider.d.ts +24 -30
- package/dist/models/openrouter-provider.d.ts.map +1 -1
- package/dist/models/openrouter-provider.js +92 -177
- package/dist/models/providers.d.ts +1 -1
- package/dist/models/providers.d.ts.map +1 -1
- 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 +23 -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 +143 -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} +21 -80
- package/dist/prompts/system-prompt.d.ts +26 -0
- package/dist/prompts/system-prompt.d.ts.map +1 -0
- package/dist/{prompts.js → prompts/system-prompt.js} +50 -22
- 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} +399 -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 +93 -1
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +264 -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.d.ts → skills/index.d.ts} +14 -2
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +294 -0
- package/dist/subagents/index.d.ts +15 -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 +30 -9
- 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.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 +172 -103
- 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/bash.d.ts +4 -3
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +343 -121
- package/dist/tools/code-search.d.ts +41 -0
- package/dist/tools/code-search.d.ts.map +1 -0
- package/dist/tools/code-search.js +195 -0
- 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 +2 -5
- 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 +164 -66
- package/dist/tools/glob.d.ts +6 -6
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +95 -55
- package/dist/tools/grep.d.ts +15 -12
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +300 -192
- package/dist/tools/index.d.ts +143 -5
- 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 +3 -3
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +74 -34
- 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 +11 -11
- 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 +62 -0
- package/dist/tools/web-fetch.d.ts.map +1 -0
- package/dist/tools/web-fetch.js +429 -0
- package/dist/tools/web-search.d.ts +62 -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 +36 -16
- 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.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 +21 -2
- package/dist/tui/components/editor.d.ts.map +1 -1
- package/dist/tui/components/editor.js +228 -236
- 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 +8 -5
- 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 +37 -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.js +2 -2
- package/dist/tui/editor-launcher.d.ts +13 -0
- package/dist/tui/editor-launcher.d.ts.map +1 -0
- package/dist/tui/editor-launcher.js +39 -0
- package/dist/tui/index.d.ts +3 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +1 -0
- package/dist/tui/terminal.d.ts +27 -0
- package/dist/tui/terminal.d.ts.map +1 -1
- package/dist/tui/terminal.js +144 -15
- package/dist/tui/tui.d.ts +43 -0
- package/dist/tui/tui.d.ts.map +1 -1
- package/dist/tui/tui.js +172 -41
- 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 +32 -31
- 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 +34 -25
- package/dist/cli.d.ts +0 -23
- package/dist/cli.d.ts.map +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 -126
- 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/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 +0 -10
- package/dist/prompts.d.ts.map +0 -1
- package/dist/repl-new.d.ts +0 -62
- package/dist/repl-new.d.ts.map +0 -1
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js +0 -233
- 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/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/terminal.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import tty from "node:tty";
|
|
1
3
|
/**
|
|
2
4
|
* Real terminal using process.stdin/stdout
|
|
5
|
+
*
|
|
6
|
+
* When useTty is true (for piped stdin scenarios), input is read from /dev/tty
|
|
7
|
+
* instead of process.stdin, allowing interactive input after piped content.
|
|
3
8
|
*/
|
|
4
9
|
export class ProcessTerminal {
|
|
5
10
|
wasRaw = false;
|
|
@@ -7,19 +12,47 @@ export class ProcessTerminal {
|
|
|
7
12
|
boundInputListener;
|
|
8
13
|
boundResizeListener;
|
|
9
14
|
stopped = false;
|
|
15
|
+
inExternalMode = false;
|
|
16
|
+
resumeCallback;
|
|
17
|
+
inputStream;
|
|
18
|
+
ttyFd;
|
|
19
|
+
useTty;
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.useTty = options.useTty ?? false;
|
|
22
|
+
this.inputStream = process.stdin;
|
|
23
|
+
}
|
|
24
|
+
handleSigCont = () => {
|
|
25
|
+
if (this.inExternalMode) {
|
|
26
|
+
this.exitExternalMode();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
10
29
|
start(onInput, onResize) {
|
|
11
|
-
if (
|
|
30
|
+
if (this.useTty) {
|
|
31
|
+
try {
|
|
32
|
+
this.ttyFd = fs.openSync("/dev/tty", "r");
|
|
33
|
+
this.inputStream = new tty.ReadStream(this.ttyFd);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error("Cannot open /dev/tty for interactive input");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
this.inputStream = process.stdin;
|
|
41
|
+
}
|
|
42
|
+
if (!this.inputStream.isTTY || !process.stdout.isTTY) {
|
|
12
43
|
throw new Error("Terminal requires TTY environment");
|
|
13
44
|
}
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
process.stdin.setRawMode(true);
|
|
45
|
+
this.wasRaw = this.inputStream.isRaw ?? false;
|
|
46
|
+
if (this.inputStream.setRawMode) {
|
|
47
|
+
this.inputStream.setRawMode(true);
|
|
18
48
|
}
|
|
19
|
-
|
|
20
|
-
|
|
49
|
+
this.inputStream.setEncoding("utf8");
|
|
50
|
+
this.inputStream.resume();
|
|
21
51
|
// Enable bracketed paste mode - terminal will wrap pastes in \x1b[200~ ... \x1b[201~
|
|
22
52
|
process.stdout.write("\x1b[?2004h");
|
|
53
|
+
// Enable mouse tracking (SGR mode) so trackpad scroll sends mouse events
|
|
54
|
+
// instead of being translated into arrow key sequences
|
|
55
|
+
process.stdout.write("\x1b[?1000h\x1b[?1006h");
|
|
23
56
|
// Create bound listeners so we can properly remove them later
|
|
24
57
|
this.boundInputListener = (data) => {
|
|
25
58
|
onInput(typeof data === "string" ? data : data.toString("utf8"));
|
|
@@ -27,13 +60,66 @@ export class ProcessTerminal {
|
|
|
27
60
|
this.boundResizeListener = () => {
|
|
28
61
|
onResize();
|
|
29
62
|
};
|
|
30
|
-
|
|
31
|
-
process.stdout.on("resize", this.boundResizeListener);
|
|
63
|
+
this.attachListeners();
|
|
32
64
|
this.sigintHandler = () => {
|
|
33
|
-
// Let the custom editor.onCtrlC handler in
|
|
65
|
+
// Let the custom editor.onCtrlC handler in Repl handle Ctrl+C
|
|
34
66
|
// This prevents a race condition where stop() gets called without the exit message
|
|
35
67
|
};
|
|
36
68
|
process.on("SIGINT", this.sigintHandler);
|
|
69
|
+
process.on("SIGCONT", this.handleSigCont);
|
|
70
|
+
process.on("SIGTERM", () => this.stop());
|
|
71
|
+
process.on("SIGHUP", () => this.stop());
|
|
72
|
+
this.setupCrashCleanup();
|
|
73
|
+
}
|
|
74
|
+
attachListeners() {
|
|
75
|
+
if (this.boundInputListener) {
|
|
76
|
+
this.inputStream.on("data", this.boundInputListener);
|
|
77
|
+
}
|
|
78
|
+
if (this.boundResizeListener) {
|
|
79
|
+
process.stdout.on("resize", this.boundResizeListener);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
detachListeners() {
|
|
83
|
+
if (this.boundInputListener) {
|
|
84
|
+
this.inputStream.removeListener("data", this.boundInputListener);
|
|
85
|
+
}
|
|
86
|
+
if (this.boundResizeListener) {
|
|
87
|
+
process.stdout.removeListener("resize", this.boundResizeListener);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
setupCrashCleanup() {
|
|
91
|
+
process.on("exit", () => {
|
|
92
|
+
process.stdout.write("\x1b[?25h");
|
|
93
|
+
process.stdout.write("\x1b[?2004l");
|
|
94
|
+
process.stdout.write("\x1b[?1000l\x1b[?1006l");
|
|
95
|
+
process.stdout.write("\x1b[0m");
|
|
96
|
+
if (this.inputStream.setRawMode) {
|
|
97
|
+
this.inputStream.setRawMode(this.wasRaw);
|
|
98
|
+
}
|
|
99
|
+
this.closeTtyFd();
|
|
100
|
+
});
|
|
101
|
+
process.on("uncaughtException", (error) => {
|
|
102
|
+
process.stdout.write("\x1b[?25h");
|
|
103
|
+
process.stdout.write("\x1b[?2004l");
|
|
104
|
+
process.stdout.write("\x1b[?1000l\x1b[?1006l");
|
|
105
|
+
process.stdout.write("\x1b[0m");
|
|
106
|
+
if (this.inputStream.setRawMode) {
|
|
107
|
+
this.inputStream.setRawMode(this.wasRaw);
|
|
108
|
+
}
|
|
109
|
+
this.closeTtyFd();
|
|
110
|
+
throw error;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
closeTtyFd() {
|
|
114
|
+
if (this.ttyFd !== undefined) {
|
|
115
|
+
try {
|
|
116
|
+
fs.closeSync(this.ttyFd);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Ignore close errors
|
|
120
|
+
}
|
|
121
|
+
this.ttyFd = undefined;
|
|
122
|
+
}
|
|
37
123
|
}
|
|
38
124
|
stop() {
|
|
39
125
|
if (this.stopped)
|
|
@@ -43,22 +129,24 @@ export class ProcessTerminal {
|
|
|
43
129
|
process.off("SIGINT", this.sigintHandler);
|
|
44
130
|
this.sigintHandler = undefined;
|
|
45
131
|
}
|
|
46
|
-
// Disable bracketed paste mode
|
|
132
|
+
// Disable bracketed paste mode and mouse tracking
|
|
47
133
|
process.stdout.write("\x1b[?2004l");
|
|
134
|
+
process.stdout.write("\x1b[?1000l\x1b[?1006l");
|
|
48
135
|
// Remove event handlers using the exact references that were added
|
|
49
136
|
if (this.boundInputListener) {
|
|
50
|
-
|
|
137
|
+
this.inputStream.removeListener("data", this.boundInputListener);
|
|
51
138
|
this.boundInputListener = undefined;
|
|
52
139
|
}
|
|
53
140
|
if (this.boundResizeListener) {
|
|
54
141
|
process.stdout.removeListener("resize", this.boundResizeListener);
|
|
55
142
|
this.boundResizeListener = undefined;
|
|
56
143
|
}
|
|
57
|
-
|
|
144
|
+
this.inputStream.pause();
|
|
58
145
|
// Restore raw mode state
|
|
59
|
-
if (
|
|
60
|
-
|
|
146
|
+
if (this.inputStream.setRawMode) {
|
|
147
|
+
this.inputStream.setRawMode(this.wasRaw);
|
|
61
148
|
}
|
|
149
|
+
this.closeTtyFd();
|
|
62
150
|
}
|
|
63
151
|
write(data) {
|
|
64
152
|
process.stdout.write(data);
|
|
@@ -95,4 +183,45 @@ export class ProcessTerminal {
|
|
|
95
183
|
clearScreen() {
|
|
96
184
|
process.stdout.write("\x1b[2J\x1b[H"); // Clear screen and move to home (1,1)
|
|
97
185
|
}
|
|
186
|
+
enterExternalMode() {
|
|
187
|
+
if (this.inExternalMode || this.stopped)
|
|
188
|
+
return;
|
|
189
|
+
this.inExternalMode = true;
|
|
190
|
+
this.detachListeners();
|
|
191
|
+
this.inputStream.pause();
|
|
192
|
+
if (this.inputStream.setRawMode) {
|
|
193
|
+
this.inputStream.setRawMode(false);
|
|
194
|
+
}
|
|
195
|
+
process.stdout.write("\x1b[0m");
|
|
196
|
+
process.stdout.write("\x1b[?25h");
|
|
197
|
+
process.stdout.write("\x1b[?2004l");
|
|
198
|
+
process.stdout.write("\x1b[?1000l\x1b[?1006l");
|
|
199
|
+
}
|
|
200
|
+
exitExternalMode() {
|
|
201
|
+
if (!this.inExternalMode || this.stopped)
|
|
202
|
+
return;
|
|
203
|
+
this.inExternalMode = false;
|
|
204
|
+
if (this.inputStream.setRawMode) {
|
|
205
|
+
this.inputStream.setRawMode(true);
|
|
206
|
+
}
|
|
207
|
+
this.inputStream.setEncoding("utf8");
|
|
208
|
+
this.inputStream.resume();
|
|
209
|
+
process.stdout.write("\x1b[?2004h");
|
|
210
|
+
process.stdout.write("\x1b[?1000h\x1b[?1006h");
|
|
211
|
+
process.stdout.write("\x1b[?25l");
|
|
212
|
+
this.attachListeners();
|
|
213
|
+
if (this.resumeCallback) {
|
|
214
|
+
this.resumeCallback();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
background() {
|
|
218
|
+
this.enterExternalMode();
|
|
219
|
+
process.kill(process.pid, "SIGSTOP");
|
|
220
|
+
}
|
|
221
|
+
isInExternalMode() {
|
|
222
|
+
return this.inExternalMode;
|
|
223
|
+
}
|
|
224
|
+
onResume(callback) {
|
|
225
|
+
this.resumeCallback = callback;
|
|
226
|
+
}
|
|
98
227
|
}
|
package/dist/tui/tui.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export interface Component {
|
|
|
24
24
|
* and col is 0-indexed column position
|
|
25
25
|
*/
|
|
26
26
|
getCursorPosition?(): [number, number] | null;
|
|
27
|
+
/**
|
|
28
|
+
* Optional method to indicate the component wants to handle Tab/Shift+Tab
|
|
29
|
+
* navigation keys itself, preventing the TUI from intercepting them.
|
|
30
|
+
*/
|
|
31
|
+
wantsNavigationKeys?(): boolean;
|
|
27
32
|
}
|
|
28
33
|
export { visibleWidth };
|
|
29
34
|
/**
|
|
@@ -38,6 +43,14 @@ export declare class Container implements Component {
|
|
|
38
43
|
}
|
|
39
44
|
/**
|
|
40
45
|
* TUI - Main class for managing terminal UI with differential rendering
|
|
46
|
+
*
|
|
47
|
+
* The TUI splits its children into two regions:
|
|
48
|
+
* - Scrollable content: children added before setFixedFooterStart()
|
|
49
|
+
* - Fixed footer: children added after setFixedFooterStart()
|
|
50
|
+
*
|
|
51
|
+
* The scrollable content region supports virtual scrolling via trackpad/mouse
|
|
52
|
+
* wheel. The fixed footer (editor, footer bar, notifications) is always
|
|
53
|
+
* pinned to the bottom of the terminal.
|
|
41
54
|
*/
|
|
42
55
|
export declare class TUI extends Container {
|
|
43
56
|
private terminal;
|
|
@@ -46,13 +59,43 @@ export declare class TUI extends Container {
|
|
|
46
59
|
private isRendering;
|
|
47
60
|
private renderAgain;
|
|
48
61
|
private activeModal;
|
|
62
|
+
private scrollOffset;
|
|
63
|
+
private lastScrollableHeight;
|
|
64
|
+
private fixedFooterIndex;
|
|
65
|
+
private isUserScrolledUp;
|
|
49
66
|
onCtrlC?: () => void;
|
|
67
|
+
onCtrlD?: () => void;
|
|
68
|
+
onReconstructSession?: () => void;
|
|
69
|
+
onCtrlN?: () => void;
|
|
70
|
+
onCtrlO?: () => void;
|
|
71
|
+
/** Callback invoked when Ctrl+M is pressed - opens model selector. */
|
|
72
|
+
onCtrlM?: () => void;
|
|
73
|
+
onCtrlR?: () => void;
|
|
74
|
+
onShiftTab?: () => void;
|
|
50
75
|
constructor(terminal: Terminal);
|
|
76
|
+
/**
|
|
77
|
+
* Mark the boundary between scrollable content and fixed footer.
|
|
78
|
+
* All children added after this call will be rendered as fixed
|
|
79
|
+
* footer content pinned to the bottom of the terminal.
|
|
80
|
+
*/
|
|
81
|
+
setFixedFooterStart(): void;
|
|
51
82
|
setFocus(component: Component | null): void;
|
|
83
|
+
/**
|
|
84
|
+
* Get the underlying terminal instance for external mode operations.
|
|
85
|
+
* Used primarily for spawning external editors.
|
|
86
|
+
*/
|
|
87
|
+
getTerminal(): Terminal;
|
|
52
88
|
start(): void;
|
|
53
89
|
stop(): void;
|
|
54
90
|
requestRender(): void;
|
|
91
|
+
private inBracketedPaste;
|
|
92
|
+
private focusedComponentWantsNavigation;
|
|
55
93
|
private handleInput;
|
|
94
|
+
private handleMouseEvent;
|
|
95
|
+
scrollToBottom(): void;
|
|
96
|
+
private getFixedFooterHeight;
|
|
97
|
+
private renderScrollableContent;
|
|
98
|
+
private renderFixedFooter;
|
|
56
99
|
private doRender;
|
|
57
100
|
/**
|
|
58
101
|
* Show a modal dialog
|
package/dist/tui/tui.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../../source/tui/tui.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../../source/tui/tui.ts"],"names":[],"mappings":"AAAA;;GAEG;AAeH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEhC;;OAEG;IACH,WAAW,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEjC;;;;OAIG;IACH,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAE9C;;;OAGG;IACH,mBAAmB,CAAC,IAAI,OAAO,CAAC;CACjC;AAED,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,qBAAa,SAAU,YAAW,SAAS;IACzC,QAAQ,EAAE,SAAS,EAAE,CAAM;IAE3B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAIpC,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAOvC,KAAK,IAAI,IAAI;IAIb,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;CAGhC;AAED;;;;;;;;;;GAUG;AAGH,qBAAa,GAAI,SAAQ,SAAS;IAChC,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,oBAAoB,CAAK;IACjC,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,gBAAgB,CAAS;IAE1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,sEAAsE;IAC/D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;gBAEnB,QAAQ,EAAE,QAAQ;IAK9B;;;;OAIG;IACH,mBAAmB,IAAI,IAAI;IAI3B,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI;IAI3C;;;OAGG;IACH,WAAW,IAAI,QAAQ;IAIvB,KAAK,IAAI,IAAI;IAUb,IAAI,IAAI,IAAI;IAKZ,aAAa,IAAI,IAAI;IAarB,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,+BAA+B;IAIvC,OAAO,CAAC,WAAW;IAyFnB,OAAO,CAAC,gBAAgB;IAiCxB,cAAc,IAAI,IAAI;IAWtB,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,uBAAuB;IAU/B,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,QAAQ;IAoFhB;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAK7B;;OAEG;IACH,SAAS,IAAI,IAAI;IAKjB;;OAEG;IACH,aAAa,IAAI,OAAO;CAGzB"}
|
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, 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 };
|
|
@@ -28,6 +29,14 @@ export class Container {
|
|
|
28
29
|
}
|
|
29
30
|
/**
|
|
30
31
|
* TUI - Main class for managing terminal UI with differential rendering
|
|
32
|
+
*
|
|
33
|
+
* The TUI splits its children into two regions:
|
|
34
|
+
* - Scrollable content: children added before setFixedFooterStart()
|
|
35
|
+
* - Fixed footer: children added after setFixedFooterStart()
|
|
36
|
+
*
|
|
37
|
+
* The scrollable content region supports virtual scrolling via trackpad/mouse
|
|
38
|
+
* wheel. The fixed footer (editor, footer bar, notifications) is always
|
|
39
|
+
* pinned to the bottom of the terminal.
|
|
31
40
|
*/
|
|
32
41
|
// biome-ignore lint/style/useNamingConvention: override
|
|
33
42
|
export class TUI extends Container {
|
|
@@ -37,16 +46,44 @@ export class TUI extends Container {
|
|
|
37
46
|
isRendering = false;
|
|
38
47
|
renderAgain = false;
|
|
39
48
|
activeModal = null;
|
|
49
|
+
scrollOffset = 0;
|
|
50
|
+
lastScrollableHeight = 0;
|
|
51
|
+
fixedFooterIndex = -1;
|
|
52
|
+
isUserScrolledUp = false;
|
|
40
53
|
onCtrlC;
|
|
54
|
+
onCtrlD;
|
|
55
|
+
onReconstructSession;
|
|
56
|
+
onCtrlN;
|
|
57
|
+
onCtrlO;
|
|
58
|
+
/** Callback invoked when Ctrl+M is pressed - opens model selector. */
|
|
59
|
+
onCtrlM;
|
|
60
|
+
onCtrlR;
|
|
61
|
+
onShiftTab;
|
|
41
62
|
constructor(terminal) {
|
|
42
63
|
super();
|
|
43
64
|
this.terminal = terminal;
|
|
44
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Mark the boundary between scrollable content and fixed footer.
|
|
68
|
+
* All children added after this call will be rendered as fixed
|
|
69
|
+
* footer content pinned to the bottom of the terminal.
|
|
70
|
+
*/
|
|
71
|
+
setFixedFooterStart() {
|
|
72
|
+
this.fixedFooterIndex = this.children.length;
|
|
73
|
+
}
|
|
45
74
|
setFocus(component) {
|
|
46
75
|
this.focusedComponent = component;
|
|
47
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Get the underlying terminal instance for external mode operations.
|
|
79
|
+
* Used primarily for spawning external editors.
|
|
80
|
+
*/
|
|
81
|
+
getTerminal() {
|
|
82
|
+
return this.terminal;
|
|
83
|
+
}
|
|
48
84
|
start() {
|
|
49
85
|
this.terminal.start((data) => this.handleInput(data), () => this.requestRender());
|
|
86
|
+
this.terminal.onResume(() => this.requestRender());
|
|
50
87
|
this.terminal.hideCursor();
|
|
51
88
|
this.requestRender();
|
|
52
89
|
}
|
|
@@ -67,7 +104,29 @@ export class TUI extends Container {
|
|
|
67
104
|
this.doRender();
|
|
68
105
|
});
|
|
69
106
|
}
|
|
107
|
+
inBracketedPaste = false;
|
|
108
|
+
focusedComponentWantsNavigation() {
|
|
109
|
+
return this.focusedComponent?.wantsNavigationKeys?.() ?? false;
|
|
110
|
+
}
|
|
70
111
|
handleInput(data) {
|
|
112
|
+
// Handle mouse tracking events (SGR format: \x1b[<button;x;yM)
|
|
113
|
+
if (data.startsWith("\x1b[<")) {
|
|
114
|
+
this.handleMouseEvent(data);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (data.includes("\x1b[200~")) {
|
|
118
|
+
this.inBracketedPaste = true;
|
|
119
|
+
}
|
|
120
|
+
if (data.includes("\x1b[201~")) {
|
|
121
|
+
this.inBracketedPaste = false;
|
|
122
|
+
}
|
|
123
|
+
// Handle Ctrl+Z - background the process (POSIX only)
|
|
124
|
+
if (isCtrlZ(data) &&
|
|
125
|
+
!this.inBracketedPaste &&
|
|
126
|
+
process.platform !== "win32") {
|
|
127
|
+
this.terminal.background();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
71
130
|
// Handle Ctrl+C globally - exit the application
|
|
72
131
|
if (isCtrlC(data)) {
|
|
73
132
|
console.info("\nCtrl+C pressed - exiting...");
|
|
@@ -79,6 +138,36 @@ export class TUI extends Container {
|
|
|
79
138
|
process.exit(0);
|
|
80
139
|
}
|
|
81
140
|
}
|
|
141
|
+
// Handle Ctrl+D - exit only if editor is empty (handled by Repl)
|
|
142
|
+
if (isCtrlD(data)) {
|
|
143
|
+
if (this.onCtrlD) {
|
|
144
|
+
this.onCtrlD();
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const keyBindings = [
|
|
149
|
+
{ check: isCtrlO, handler: this.onCtrlO },
|
|
150
|
+
{ check: isCtrlR, handler: this.onCtrlR },
|
|
151
|
+
{ check: isCtrlN, handler: this.onCtrlN },
|
|
152
|
+
{ check: isCtrlM, handler: this.onCtrlM },
|
|
153
|
+
];
|
|
154
|
+
for (const binding of keyBindings) {
|
|
155
|
+
if (binding.check(data)) {
|
|
156
|
+
binding.handler?.();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Handle Shift+Tab - cycle mode only when no modal is active,
|
|
161
|
+
// the focused component won't handle navigation (e.g., model selector),
|
|
162
|
+
// and the editor isn't showing autocomplete
|
|
163
|
+
if (isShiftTab(data) && !this.inBracketedPaste) {
|
|
164
|
+
if (!this.activeModal && !this.focusedComponentWantsNavigation()) {
|
|
165
|
+
if (this.onShiftTab) {
|
|
166
|
+
this.onShiftTab();
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
82
171
|
// Handle Escape key to close modal if one is active
|
|
83
172
|
if (isEscape(data) && this.activeModal) {
|
|
84
173
|
this.hideModal();
|
|
@@ -91,9 +180,69 @@ export class TUI extends Container {
|
|
|
91
180
|
}
|
|
92
181
|
else if (this.focusedComponent?.handleInput) {
|
|
93
182
|
this.focusedComponent.handleInput(data);
|
|
183
|
+
this.scrollToBottom();
|
|
184
|
+
this.requestRender();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
handleMouseEvent(data) {
|
|
188
|
+
// SGR mouse format: \x1b[<button;x;yM (press) or \x1b[<button;x;ym (release)
|
|
189
|
+
// Extract button code between "<" and first ";"
|
|
190
|
+
const start = data.indexOf("<");
|
|
191
|
+
const semi = data.indexOf(";", start);
|
|
192
|
+
if (start === -1 || semi === -1)
|
|
193
|
+
return;
|
|
194
|
+
const button = Number.parseInt(data.slice(start + 1, semi), 10);
|
|
195
|
+
const scrollLines = 3;
|
|
196
|
+
const { rows } = getTerminalSize();
|
|
197
|
+
const fixedHeight = this.getFixedFooterHeight(this.terminal.columns);
|
|
198
|
+
const scrollableViewport = rows - fixedHeight;
|
|
199
|
+
// Button 64 = scroll up, button 65 = scroll down
|
|
200
|
+
if (button === 64) {
|
|
201
|
+
this.scrollOffset = Math.max(0, this.scrollOffset - scrollLines);
|
|
202
|
+
const maxOffset = Math.max(0, this.lastScrollableHeight - scrollableViewport);
|
|
203
|
+
this.isUserScrolledUp = this.scrollOffset < maxOffset;
|
|
204
|
+
this.requestRender();
|
|
205
|
+
}
|
|
206
|
+
else if (button === 65) {
|
|
207
|
+
const maxOffset = Math.max(0, this.lastScrollableHeight - scrollableViewport);
|
|
208
|
+
this.scrollOffset = Math.min(maxOffset, this.scrollOffset + scrollLines);
|
|
209
|
+
this.isUserScrolledUp = this.scrollOffset < maxOffset;
|
|
94
210
|
this.requestRender();
|
|
95
211
|
}
|
|
96
212
|
}
|
|
213
|
+
scrollToBottom() {
|
|
214
|
+
const { rows } = getTerminalSize();
|
|
215
|
+
const fixedHeight = this.getFixedFooterHeight(this.terminal.columns);
|
|
216
|
+
const scrollableViewport = rows - fixedHeight;
|
|
217
|
+
this.scrollOffset = Math.max(0, this.lastScrollableHeight - scrollableViewport);
|
|
218
|
+
this.isUserScrolledUp = false;
|
|
219
|
+
}
|
|
220
|
+
getFixedFooterHeight(width) {
|
|
221
|
+
if (this.fixedFooterIndex < 0)
|
|
222
|
+
return 0;
|
|
223
|
+
let height = 0;
|
|
224
|
+
for (let i = this.fixedFooterIndex; i < this.children.length; i++) {
|
|
225
|
+
height += this.children[i].render(width).length;
|
|
226
|
+
}
|
|
227
|
+
return height;
|
|
228
|
+
}
|
|
229
|
+
renderScrollableContent(width) {
|
|
230
|
+
const end = this.fixedFooterIndex >= 0 ? this.fixedFooterIndex : this.children.length;
|
|
231
|
+
const lines = [];
|
|
232
|
+
for (let i = 0; i < end; i++) {
|
|
233
|
+
lines.push(...this.children[i].render(width));
|
|
234
|
+
}
|
|
235
|
+
return lines;
|
|
236
|
+
}
|
|
237
|
+
renderFixedFooter(width) {
|
|
238
|
+
if (this.fixedFooterIndex < 0)
|
|
239
|
+
return [];
|
|
240
|
+
const lines = [];
|
|
241
|
+
for (let i = this.fixedFooterIndex; i < this.children.length; i++) {
|
|
242
|
+
lines.push(...this.children[i].render(width));
|
|
243
|
+
}
|
|
244
|
+
return lines;
|
|
245
|
+
}
|
|
97
246
|
doRender() {
|
|
98
247
|
if (this.isRendering) {
|
|
99
248
|
this.renderAgain = true;
|
|
@@ -102,13 +251,32 @@ export class TUI extends Container {
|
|
|
102
251
|
this.isRendering = true;
|
|
103
252
|
try {
|
|
104
253
|
const width = this.terminal.columns;
|
|
105
|
-
|
|
106
|
-
|
|
254
|
+
const { rows } = getTerminalSize();
|
|
255
|
+
// Render fixed footer and scrollable content separately
|
|
256
|
+
const fixedLines = this.renderFixedFooter(width);
|
|
257
|
+
const scrollableLines = this.renderScrollableContent(width);
|
|
258
|
+
this.lastScrollableHeight = scrollableLines.length;
|
|
259
|
+
const fixedHeight = fixedLines.length;
|
|
260
|
+
const scrollableViewport = rows - fixedHeight;
|
|
261
|
+
// Auto-scroll to bottom when new content arrives,
|
|
262
|
+
// unless the user has explicitly scrolled up
|
|
263
|
+
if (!this.isUserScrolledUp) {
|
|
264
|
+
this.scrollOffset = Math.max(0, scrollableLines.length - scrollableViewport);
|
|
265
|
+
}
|
|
266
|
+
// Clamp scroll offset
|
|
267
|
+
const maxOffset = Math.max(0, scrollableLines.length - scrollableViewport);
|
|
268
|
+
if (this.scrollOffset > maxOffset) {
|
|
269
|
+
this.scrollOffset = maxOffset;
|
|
270
|
+
}
|
|
271
|
+
// Apply scroll offset to get visible scrollable lines
|
|
272
|
+
const visibleScrollable = scrollableLines.slice(this.scrollOffset, this.scrollOffset + scrollableViewport);
|
|
273
|
+
// Combine visible scrollable content with fixed footer
|
|
274
|
+
const visibleLines = [...visibleScrollable, ...fixedLines];
|
|
107
275
|
// Build output buffer using array join (more efficient than string concat)
|
|
108
276
|
const bufferParts = [
|
|
109
277
|
"\x1b[?2026h", // Begin synchronized output
|
|
110
278
|
"\x1b[3J\x1b[2J\x1b[H", // Clear scrollback, screen, and home
|
|
111
|
-
|
|
279
|
+
visibleLines.join("\r\n"),
|
|
112
280
|
];
|
|
113
281
|
// Render modal on top if active
|
|
114
282
|
if (this.activeModal) {
|
|
@@ -139,43 +307,6 @@ export class TUI extends Container {
|
|
|
139
307
|
}
|
|
140
308
|
}
|
|
141
309
|
}
|
|
142
|
-
// private positionCursor(
|
|
143
|
-
// componentCursorPos: [number, number] | null,
|
|
144
|
-
// ): void {
|
|
145
|
-
// if (!componentCursorPos) {
|
|
146
|
-
// // No cursor position from component, hide cursor
|
|
147
|
-
// this.terminal.hideCursor();
|
|
148
|
-
// return;
|
|
149
|
-
// }
|
|
150
|
-
// const [cursorRow, cursorCol] = componentCursorPos;
|
|
151
|
-
//
|
|
152
|
-
// // Calculate absolute cursor position in the terminal
|
|
153
|
-
// // We need to find which line in newLines corresponds to the component's cursor row
|
|
154
|
-
// // and then position the cursor at that line and column
|
|
155
|
-
//
|
|
156
|
-
// // Find the line offset for the focused component
|
|
157
|
-
// let componentStartLine = 0;
|
|
158
|
-
// if (this.focusedComponent) {
|
|
159
|
-
// // Find the line where this component starts by summing heights of previous components
|
|
160
|
-
// for (const child of this.children) {
|
|
161
|
-
// if (child === this.focusedComponent) {
|
|
162
|
-
// break;
|
|
163
|
-
// }
|
|
164
|
-
// // Use the already-rendered lines to calculate height, not re-render
|
|
165
|
-
// const childLines = child.render(this.terminal.columns);
|
|
166
|
-
// componentStartLine += childLines.length;
|
|
167
|
-
// }
|
|
168
|
-
// }
|
|
169
|
-
//
|
|
170
|
-
// const absoluteRow = componentStartLine + cursorRow;
|
|
171
|
-
// const absoluteCol = cursorCol;
|
|
172
|
-
//
|
|
173
|
-
// // Position cursor using absolute positioning
|
|
174
|
-
// // Move to home position first, then move down to row, then right to column
|
|
175
|
-
// // Note: terminal rows/columns are 1-indexed, so we add 1
|
|
176
|
-
// this.terminal.write(`\x1b[H\x1b[${absoluteRow + 1}B\x1b[${absoluteCol + 1}G`);
|
|
177
|
-
// this.terminal.showCursor();
|
|
178
|
-
// }
|
|
179
310
|
/**
|
|
180
311
|
* Show a modal dialog
|
|
181
312
|
*/
|
|
@@ -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"}
|