@linxiraos/pi-tui 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +188 -120
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +22 -26
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +35 -43
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +461 -11
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/agent-hub.ts +2 -2
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/plugin-settings.ts +8 -2
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -6
- package/src/prompt/composer.ts +196 -96
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +256 -423
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +137 -60
- package/src/status-line/component.ts +23 -10
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +11 -1
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +160 -46
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +140 -36
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
package/src/tmux.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export { wrapTmuxPassthrough } from "./multiplexers/tmux";
|
|
2
2
|
/** Pass a control sequence through tmux, leaving direct-terminal output unchanged. */
|
|
3
3
|
export declare function wrapTmuxPassthroughIfNeeded(payload: string, env?: NodeJS.ProcessEnv): string;
|
|
4
|
+
/**
|
|
5
|
+
* Emulator name candidates from a `#{client_termtype}<TAB>#{client_termname}`
|
|
6
|
+
* line, best first: the terminal-type reply (tmux 3.4+, XTVERSION), then the
|
|
7
|
+
* emulator the client's `TERM` names when it ships its own terminfo entry. Both
|
|
8
|
+
* are kept because the reply can name an unrecognized program (a nested tmux
|
|
9
|
+
* answers `tmux 3.4`) while `TERM` still identifies the emulator.
|
|
10
|
+
*/
|
|
11
|
+
export declare function tmuxClientProgramsFromReport(report: string): string[];
|
|
4
12
|
/**
|
|
5
13
|
* Whether the terminal hosting this tmux session draws TSP programs natively:
|
|
6
14
|
* it sets the session user option `@tern-tsp` while it watches the session.
|
|
@@ -12,10 +20,37 @@ export declare function tmuxSessionSpeaksTsp(env?: NodeJS.ProcessEnv): boolean;
|
|
|
12
20
|
*/
|
|
13
21
|
export declare function setTmuxPaneApcTitles(on: boolean, env?: NodeJS.ProcessEnv): void;
|
|
14
22
|
/**
|
|
15
|
-
* Resolve the terminal emulator
|
|
23
|
+
* Resolve the terminal emulator names recorded for this tmux client, best first.
|
|
16
24
|
*
|
|
17
25
|
* tmux overwrites pane identity variables with its own values, but retains the
|
|
18
|
-
* attached client's terminal-type reply in `#{client_termtype}
|
|
19
|
-
*
|
|
26
|
+
* attached client's terminal-type reply in `#{client_termtype}` and its `TERM`
|
|
27
|
+
* in `#{client_termname}` (see {@link tmuxClientProgramsFromReport}). The local
|
|
28
|
+
* IPC query runs once per process and degrades to `[]` when unavailable.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveTmuxClientTerminalNames(env?: NodeJS.ProcessEnv): readonly string[];
|
|
31
|
+
/**
|
|
32
|
+
* Whether tmux draws SIXEL to the client attached to this session: its
|
|
33
|
+
* effective terminal description for that client carries `Sxl`.
|
|
34
|
+
*
|
|
35
|
+
* A tmux built with SIXEL answers every pane's DA1 with attribute 4 and draws a
|
|
36
|
+
* `SIXEL IMAGE (WxH)` text placeholder for clients without SIXEL, so the pane's
|
|
37
|
+
* DA1 says nothing about the outer terminal. tmux itself renders SIXEL only
|
|
38
|
+
* when the client's terminal has `Sxl`, whether from terminfo,
|
|
39
|
+
* `terminal-overrides`, or the `sixel` feature (DA1 or `terminal-features`),
|
|
40
|
+
* and `show-messages -T` reports that merged description. The TUI consults
|
|
41
|
+
* this before trusting the pane's DA1 attribute. Queried once per tmux
|
|
42
|
+
* session; `false` outside tmux, with no attached client, or when the query
|
|
43
|
+
* fails. Nested tmux usually reports no `Sxl`: the inner client's terminal is
|
|
44
|
+
* the outer tmux, whose DA1 starts with level 1, which tmux does not parse
|
|
45
|
+
* for features.
|
|
46
|
+
*/
|
|
47
|
+
export declare function tmuxClientRendersSixel(env?: NodeJS.ProcessEnv): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Whether tmux forwards this pane's `\x1bPtmux;…\x1b\\` passthrough envelopes
|
|
50
|
+
* to the attached client. tmux 3.3+ defaults `allow-passthrough` to `off` and
|
|
51
|
+
* then silently drops every wrapped sequence, so Kitty graphics sent through
|
|
52
|
+
* the envelope never reach a Kitty-capable outer terminal. Resolves the
|
|
53
|
+
* effective pane value (pane → window → global). Queried once per pane;
|
|
54
|
+
* `false` outside tmux or when the query fails.
|
|
20
55
|
*/
|
|
21
|
-
export declare function
|
|
56
|
+
export declare function tmuxPaneAllowsPassthrough(env?: NodeJS.ProcessEnv): boolean;
|
package/src/tmux.ts
CHANGED
|
@@ -10,24 +10,55 @@ export function wrapTmuxPassthroughIfNeeded(payload: string, env: NodeJS.Process
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const CLIENT_TERMTYPE_NAME = /^([A-Za-z][A-Za-z0-9._+-]*)(?=\s|\(|$)/u;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// One `tmux display-message` round trip over the server socket, which answers in
|
|
14
|
+
// single-digit milliseconds when healthy. 500ms kept losing the race on a loaded
|
|
15
|
+
// machine (fork/exec of the tmux client alone can exceed it on a saturated box),
|
|
16
|
+
// and a lost race is indistinguishable from "not tmux": the process then falls
|
|
17
|
+
// back to the true-color profile for the whole session. 2s bounds the startup
|
|
18
|
+
// stall only when tmux is genuinely wedged.
|
|
19
|
+
const CLIENT_TERMINAL_TIMEOUT_MS = 2_000;
|
|
20
|
+
/** Client `TERM` values that name their emulator; tmux records this on every version. */
|
|
21
|
+
const CLIENT_TERMNAME_PROGRAMS: ReadonlyArray<readonly [RegExp, string]> = [
|
|
22
|
+
[/^xterm-kitty$/u, "kitty"],
|
|
23
|
+
[/^xterm-ghostty$/u, "ghostty"],
|
|
24
|
+
[/^wezterm$/u, "wezterm"],
|
|
25
|
+
];
|
|
26
|
+
let cachedClientTerminalNames: readonly string[] | undefined;
|
|
15
27
|
|
|
16
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Emulator name candidates from a `#{client_termtype}<TAB>#{client_termname}`
|
|
30
|
+
* line, best first: the terminal-type reply (tmux 3.4+, XTVERSION), then the
|
|
31
|
+
* emulator the client's `TERM` names when it ships its own terminfo entry. Both
|
|
32
|
+
* are kept because the reply can name an unrecognized program (a nested tmux
|
|
33
|
+
* answers `tmux 3.4`) while `TERM` still identifies the emulator.
|
|
34
|
+
*/
|
|
35
|
+
export function tmuxClientProgramsFromReport(report: string): string[] {
|
|
36
|
+
// Split before trimming: an empty terminal type leaves the line starting with the tab.
|
|
37
|
+
const [termtype = "", termname = ""] = report.split("\t");
|
|
38
|
+
const programs: string[] = [];
|
|
39
|
+
const program = CLIENT_TERMTYPE_NAME.exec(termtype.trim())?.[1];
|
|
40
|
+
if (program) programs.push(program);
|
|
41
|
+
const term = termname.trim().toLowerCase();
|
|
42
|
+
const termProgram = CLIENT_TERMNAME_PROGRAMS.find(([pattern]) => pattern.test(term))?.[1];
|
|
43
|
+
if (termProgram) programs.push(termProgram);
|
|
44
|
+
return programs;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function queryTmuxClientTerminalNames(env: NodeJS.ProcessEnv): readonly string[] {
|
|
17
48
|
const tmux = $which("tmux", { PATH: env.PATH });
|
|
18
|
-
if (!tmux) return
|
|
49
|
+
if (!tmux) return [];
|
|
19
50
|
try {
|
|
20
|
-
const result = Bun.spawnSync([tmux, "display-message", "-p", "#{client_termtype}"], {
|
|
51
|
+
const result = Bun.spawnSync([tmux, "display-message", "-p", "#{client_termtype}\t#{client_termname}"], {
|
|
21
52
|
env,
|
|
22
53
|
stdout: "pipe",
|
|
23
54
|
stderr: "ignore",
|
|
24
|
-
timeout:
|
|
55
|
+
timeout: CLIENT_TERMINAL_TIMEOUT_MS,
|
|
25
56
|
killSignal: "SIGKILL",
|
|
26
57
|
});
|
|
27
|
-
if (result.exitCode !== 0) return
|
|
28
|
-
return
|
|
58
|
+
if (result.exitCode !== 0) return [];
|
|
59
|
+
return tmuxClientProgramsFromReport(result.stdout.toString());
|
|
29
60
|
} catch {
|
|
30
|
-
return
|
|
61
|
+
return [];
|
|
31
62
|
}
|
|
32
63
|
}
|
|
33
64
|
|
|
@@ -75,14 +106,77 @@ export function setTmuxPaneApcTitles(on: boolean, env: NodeJS.ProcessEnv = Bun.e
|
|
|
75
106
|
}
|
|
76
107
|
|
|
77
108
|
/**
|
|
78
|
-
* Resolve the terminal emulator
|
|
109
|
+
* Resolve the terminal emulator names recorded for this tmux client, best first.
|
|
79
110
|
*
|
|
80
111
|
* tmux overwrites pane identity variables with its own values, but retains the
|
|
81
|
-
* attached client's terminal-type reply in `#{client_termtype}
|
|
82
|
-
*
|
|
112
|
+
* attached client's terminal-type reply in `#{client_termtype}` and its `TERM`
|
|
113
|
+
* in `#{client_termname}` (see {@link tmuxClientProgramsFromReport}). The local
|
|
114
|
+
* IPC query runs once per process and degrades to `[]` when unavailable.
|
|
83
115
|
*/
|
|
84
|
-
export function
|
|
85
|
-
if (!tmuxMultiplexer.isInside(env) || isBunTestRuntime()) return
|
|
86
|
-
|
|
87
|
-
return
|
|
116
|
+
export function resolveTmuxClientTerminalNames(env: NodeJS.ProcessEnv = Bun.env): readonly string[] {
|
|
117
|
+
if (!tmuxMultiplexer.isInside(env) || isBunTestRuntime()) return [];
|
|
118
|
+
cachedClientTerminalNames ??= queryTmuxClientTerminalNames(env);
|
|
119
|
+
return cachedClientTerminalNames;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const SIXEL_CAPABILITY = /^\s*\d+: Sxl: \(flag\) true$/mu;
|
|
123
|
+
let cachedClientSixel: { tmux: string; renders: boolean } | undefined;
|
|
124
|
+
|
|
125
|
+
function queryTmuxClientRendersSixel(env: NodeJS.ProcessEnv): boolean {
|
|
126
|
+
const client = runTmux(["display-message", "-p", "#{client_name}"], env)?.trim();
|
|
127
|
+
if (!client) return false;
|
|
128
|
+
return SIXEL_CAPABILITY.test(runTmux(["show-messages", "-T", "-t", client], env) ?? "");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Whether tmux draws SIXEL to the client attached to this session: its
|
|
133
|
+
* effective terminal description for that client carries `Sxl`.
|
|
134
|
+
*
|
|
135
|
+
* A tmux built with SIXEL answers every pane's DA1 with attribute 4 and draws a
|
|
136
|
+
* `SIXEL IMAGE (WxH)` text placeholder for clients without SIXEL, so the pane's
|
|
137
|
+
* DA1 says nothing about the outer terminal. tmux itself renders SIXEL only
|
|
138
|
+
* when the client's terminal has `Sxl`, whether from terminfo,
|
|
139
|
+
* `terminal-overrides`, or the `sixel` feature (DA1 or `terminal-features`),
|
|
140
|
+
* and `show-messages -T` reports that merged description. The TUI consults
|
|
141
|
+
* this before trusting the pane's DA1 attribute. Queried once per tmux
|
|
142
|
+
* session; `false` outside tmux, with no attached client, or when the query
|
|
143
|
+
* fails. Nested tmux usually reports no `Sxl`: the inner client's terminal is
|
|
144
|
+
* the outer tmux, whose DA1 starts with level 1, which tmux does not parse
|
|
145
|
+
* for features.
|
|
146
|
+
*/
|
|
147
|
+
export function tmuxClientRendersSixel(env: NodeJS.ProcessEnv = Bun.env): boolean {
|
|
148
|
+
const tmux = env.TMUX;
|
|
149
|
+
if (!tmux) return false;
|
|
150
|
+
if (cachedClientSixel?.tmux !== tmux) cachedClientSixel = { tmux, renders: queryTmuxClientRendersSixel(env) };
|
|
151
|
+
return cachedClientSixel.renders;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let cachedPanePassthrough: { key: string; allowed: boolean } | undefined;
|
|
155
|
+
|
|
156
|
+
function queryTmuxPaneAllowsPassthrough(env: NodeJS.ProcessEnv): boolean {
|
|
157
|
+
const pane = env.TMUX_PANE;
|
|
158
|
+
const target = pane ? ["-t", pane] : [];
|
|
159
|
+
const value = runTmux(["show-options", "-Apqv", ...target, "allow-passthrough"], env)?.trim();
|
|
160
|
+
if (value === undefined) return false;
|
|
161
|
+
// tmux before 3.3 has no `allow-passthrough` option (`-q` prints nothing)
|
|
162
|
+
// and always forwards DCS passthrough.
|
|
163
|
+
return value === "" || value === "on" || value === "all";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Whether tmux forwards this pane's `\x1bPtmux;…\x1b\\` passthrough envelopes
|
|
168
|
+
* to the attached client. tmux 3.3+ defaults `allow-passthrough` to `off` and
|
|
169
|
+
* then silently drops every wrapped sequence, so Kitty graphics sent through
|
|
170
|
+
* the envelope never reach a Kitty-capable outer terminal. Resolves the
|
|
171
|
+
* effective pane value (pane → window → global). Queried once per pane;
|
|
172
|
+
* `false` outside tmux or when the query fails.
|
|
173
|
+
*/
|
|
174
|
+
export function tmuxPaneAllowsPassthrough(env: NodeJS.ProcessEnv = Bun.env): boolean {
|
|
175
|
+
const tmux = env.TMUX;
|
|
176
|
+
if (!tmux) return false;
|
|
177
|
+
const key = `${tmux}\0${env.TMUX_PANE ?? ""}`;
|
|
178
|
+
if (cachedPanePassthrough?.key !== key) {
|
|
179
|
+
cachedPanePassthrough = { key, allowed: queryTmuxPaneAllowsPassthrough(env) };
|
|
180
|
+
}
|
|
181
|
+
return cachedPanePassthrough.allowed;
|
|
88
182
|
}
|
package/src/tools/ask.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { getMarkdownTheme, type Theme } from "../theme/theme";
|
|
|
23
23
|
|
|
24
24
|
import { outputBlockContentWidth, renderStatusLine } from "../render";
|
|
25
25
|
import { framedToolCard } from "../render/tool-card";
|
|
26
|
+
import { wrapPrefixedLine } from "../render/hanging-wrap";
|
|
26
27
|
|
|
27
28
|
import { formatErrorMessage, formatMeta, formatTitle, sanitizeCarriageReturns } from "../render/render-utils";
|
|
28
29
|
import { tuiText, tuiTextFmt } from "../i18n";
|
|
@@ -203,20 +204,27 @@ export function optionMarker(uiTheme: Theme, multi: boolean | undefined, selecte
|
|
|
203
204
|
return selected ? uiTheme.radio.selected : uiTheme.radio.unselected;
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
function wrapOptionLabel(marker: string, label: string, width: number): string[] {
|
|
208
|
+
const prefix = ` ${marker} `;
|
|
209
|
+
return wrapPrefixedLine(prefix, " ".repeat(visibleWidth(prefix)), label, width);
|
|
210
|
+
}
|
|
211
|
+
|
|
206
212
|
/** Render the offered options for a question form as flat marker bullets (no tree guides). */
|
|
207
213
|
function renderQuestionOptionLines(
|
|
208
214
|
uiTheme: Theme,
|
|
209
215
|
mdTheme: MarkdownTheme,
|
|
210
216
|
options: AskRenderOption[],
|
|
211
217
|
multi: boolean | undefined,
|
|
218
|
+
width: number,
|
|
212
219
|
): string[] {
|
|
213
220
|
const out: string[] = [];
|
|
214
221
|
for (const opt of options) {
|
|
215
222
|
const optLabel = renderInlineMarkdown(opt.label, mdTheme, t => uiTheme.fg("muted", t));
|
|
216
|
-
out.push(
|
|
223
|
+
out.push(...wrapOptionLabel(uiTheme.fg("dim", optionMarker(uiTheme, multi, false)), optLabel, width));
|
|
217
224
|
if (opt.description?.trim()) {
|
|
218
225
|
const description = renderInlineMarkdown(opt.description.trim(), mdTheme, t => uiTheme.fg("dim", t));
|
|
219
|
-
|
|
226
|
+
const prefix = ` ${uiTheme.fg("dim", "↳")} `;
|
|
227
|
+
out.push(...wrapPrefixedLine(prefix, " ".repeat(visibleWidth(prefix)), description, width));
|
|
220
228
|
}
|
|
221
229
|
}
|
|
222
230
|
return out;
|
|
@@ -261,6 +269,7 @@ function renderAnswerOptionLines(
|
|
|
261
269
|
customInput: string | undefined,
|
|
262
270
|
note: string | undefined,
|
|
263
271
|
width: number,
|
|
272
|
+
contentWidth: number,
|
|
264
273
|
selectedIndices?: ReadonlySet<number>,
|
|
265
274
|
): string[] {
|
|
266
275
|
const selected = new Set(selectedOptions ?? []);
|
|
@@ -283,7 +292,7 @@ function renderAnswerOptionLines(
|
|
|
283
292
|
const labelStyled = renderInlineMarkdown(label, mdTheme, t =>
|
|
284
293
|
isSelected ? uiTheme.fg("toolOutput", t) : uiTheme.fg("muted", t),
|
|
285
294
|
);
|
|
286
|
-
out.push(
|
|
295
|
+
out.push(...wrapOptionLabel(markerStyled, labelStyled, contentWidth));
|
|
287
296
|
}
|
|
288
297
|
if (customInput !== undefined) out.push(...renderCustomInputLines(uiTheme, customInput));
|
|
289
298
|
if (note !== undefined) out.push(...renderNoteLines(uiTheme, note, width));
|
|
@@ -461,7 +470,7 @@ export const askToolRenderer = {
|
|
|
461
470
|
? tuiText("askQuestionOne", "1 question")
|
|
462
471
|
: tuiTextFmt("askQuestionManyFmt", "%d questions", questions.length),
|
|
463
472
|
)}`;
|
|
464
|
-
return framedToolCard(uiTheme, ({ width }) => {
|
|
473
|
+
return framedToolCard(uiTheme, ({ width, contentWidth }) => {
|
|
465
474
|
const sections = questions.map(q => {
|
|
466
475
|
const meta: string[] = [];
|
|
467
476
|
if (q.multi) meta.push(tuiText("askMultiMeta", "multi"));
|
|
@@ -470,7 +479,7 @@ export const askToolRenderer = {
|
|
|
470
479
|
// md() returns a shared cached array (module-level Markdown LRU) — copy before appending.
|
|
471
480
|
const mdLines = md(q.question, width);
|
|
472
481
|
const lines = q.options?.length
|
|
473
|
-
? [...mdLines, ...renderQuestionOptionLines(uiTheme, mdTheme, q.options, q.multi)]
|
|
482
|
+
? [...mdLines, ...renderQuestionOptionLines(uiTheme, mdTheme, q.options, q.multi, contentWidth)]
|
|
474
483
|
: mdLines;
|
|
475
484
|
return { label: `${uiTheme.fg("dim", `[${q.id}]`)}${metaStr}`, content: lines };
|
|
476
485
|
});
|
|
@@ -496,11 +505,11 @@ export const askToolRenderer = {
|
|
|
496
505
|
if (questionOptions?.length) meta.push(`options:${questionOptions.length}`);
|
|
497
506
|
const header = `${label}${formatMeta(meta, uiTheme)}`;
|
|
498
507
|
const multi = args.multi;
|
|
499
|
-
return framedToolCard(uiTheme, ({ width }) => {
|
|
508
|
+
return framedToolCard(uiTheme, ({ width, contentWidth }) => {
|
|
500
509
|
// md() returns a shared cached array (module-level Markdown LRU) — copy before appending.
|
|
501
510
|
const mdLines = md(question, width);
|
|
502
511
|
const bodyLines = questionOptions?.length
|
|
503
|
-
? [...mdLines, ...renderQuestionOptionLines(uiTheme, mdTheme, questionOptions, multi)]
|
|
512
|
+
? [...mdLines, ...renderQuestionOptionLines(uiTheme, mdTheme, questionOptions, multi, contentWidth)]
|
|
504
513
|
: mdLines;
|
|
505
514
|
return {
|
|
506
515
|
header,
|
|
@@ -570,7 +579,7 @@ export const askToolRenderer = {
|
|
|
570
579
|
},
|
|
571
580
|
uiTheme,
|
|
572
581
|
);
|
|
573
|
-
return framedToolCard(uiTheme, ({ width }) => {
|
|
582
|
+
return framedToolCard(uiTheme, ({ width, contentWidth }) => {
|
|
574
583
|
const rawResults = rawDetails.results ?? [];
|
|
575
584
|
const sections = results.map((r, index) => {
|
|
576
585
|
// Sanitizing preserves order and length, so raw indices align with `r`.
|
|
@@ -587,6 +596,7 @@ export const askToolRenderer = {
|
|
|
587
596
|
r.customInput,
|
|
588
597
|
r.note,
|
|
589
598
|
width,
|
|
599
|
+
contentWidth,
|
|
590
600
|
selectedIndicesFor(raw?.options, raw?.selectedOptions),
|
|
591
601
|
),
|
|
592
602
|
];
|
|
@@ -625,7 +635,7 @@ export const askToolRenderer = {
|
|
|
625
635
|
const dCustom = details.customInput;
|
|
626
636
|
const dNote = details.note;
|
|
627
637
|
const dTimedOut = details.timedOut;
|
|
628
|
-
return framedToolCard(uiTheme, ({ width }) => {
|
|
638
|
+
return framedToolCard(uiTheme, ({ width, contentWidth }) => {
|
|
629
639
|
// md() returns a shared cached array (module-level Markdown LRU) — copy before appending.
|
|
630
640
|
const bodyLines = [
|
|
631
641
|
...md(question, width),
|
|
@@ -638,6 +648,7 @@ export const askToolRenderer = {
|
|
|
638
648
|
dCustom,
|
|
639
649
|
dNote,
|
|
640
650
|
width,
|
|
651
|
+
contentWidth,
|
|
641
652
|
selectedIndicesFor(rawDetails.options, rawDetails.selectedOptions),
|
|
642
653
|
),
|
|
643
654
|
];
|
package/src/tools/ast-edit.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Component } from "../tui";
|
|
2
2
|
import type { Theme } from "../theme/theme";
|
|
3
3
|
import type { OutputMeta } from "./output-meta";
|
|
4
|
-
import type { NativeToolView, RenderResultOptions } from "./renderer";
|
|
4
|
+
import type { NativeToolView, RenderResultContextOptions, RenderResultOptions } from "./renderer";
|
|
5
5
|
/** Display metadata returned by ast-edit. */
|
|
6
6
|
export interface AstEditToolDetails {
|
|
7
7
|
totalReplacements: number;
|
|
@@ -47,7 +47,7 @@ export declare const astEditToolRenderer: {
|
|
|
47
47
|
}>;
|
|
48
48
|
details?: AstEditToolDetails;
|
|
49
49
|
isError?: boolean;
|
|
50
|
-
}, options:
|
|
50
|
+
}, options: RenderResultContextOptions, uiTheme: Theme, args?: AstEditRenderArgs): Component;
|
|
51
51
|
describeCall(args: AstEditRenderArgs): NativeToolView;
|
|
52
52
|
describeResult(result: {
|
|
53
53
|
content: Array<{
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord, sanitizeText } from "@linxiraos/pi-utils";
|
|
1
2
|
import type { Component } from "../tui";
|
|
2
3
|
import { Text } from "../components/text";
|
|
3
4
|
import { replaceTabs } from "../utils";
|
|
@@ -14,11 +15,16 @@ import {
|
|
|
14
15
|
} from "../render/render-utils";
|
|
15
16
|
import { classifyGroupedLines, groupLineIndicesByBlank } from "./grouped-file-output";
|
|
16
17
|
import type { OutputMeta } from "./output-meta";
|
|
17
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
NativeToolHead,
|
|
20
|
+
NativeToolView,
|
|
21
|
+
RenderResultContextOptions,
|
|
22
|
+
RenderResultOptions,
|
|
23
|
+
ToolRenderer,
|
|
24
|
+
} from "./renderer";
|
|
18
25
|
import { code, compact, diff, node, span, text } from "../native/describe";
|
|
19
26
|
import type { NativeChild, NativeNode } from "../native/node";
|
|
20
27
|
import { diffStatsMeta, displayPath, errorText, fileDiffSection, noteText, resultText, statsText } from "./native-view";
|
|
21
|
-
import { getLanguageFromPath } from "../lang-from-path";
|
|
22
28
|
|
|
23
29
|
/** Display metadata returned by ast-edit. */
|
|
24
30
|
export interface AstEditToolDetails {
|
|
@@ -148,9 +154,9 @@ function parseAstEditChanges(lines: readonly string[]): AstEditFileDiff[] {
|
|
|
148
154
|
return files;
|
|
149
155
|
}
|
|
150
156
|
|
|
151
|
-
/** A file's proposed rewrite as a `diff`:
|
|
157
|
+
/** A file's proposed rewrite as a `diff`: Tern highlights it by the file's grammar and draws no path header. */
|
|
152
158
|
function astEditDiff(file: AstEditFileDiff): NativeNode {
|
|
153
|
-
return diff(file.lines.join("\n"), {
|
|
159
|
+
return diff(file.lines.join("\n"), { path: file.path });
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
/** The single rewrite pattern (`target:"pattern"`), or the rewrite count, for the head. */
|
|
@@ -190,7 +196,7 @@ export const astEditToolRenderer = {
|
|
|
190
196
|
|
|
191
197
|
renderResult(
|
|
192
198
|
result: { content: Array<{ type: string; text?: string }>; details?: AstEditToolDetails; isError?: boolean },
|
|
193
|
-
options:
|
|
199
|
+
options: RenderResultContextOptions,
|
|
194
200
|
uiTheme: Theme,
|
|
195
201
|
args?: AstEditRenderArgs,
|
|
196
202
|
): Component {
|
|
@@ -245,6 +251,19 @@ export const astEditToolRenderer = {
|
|
|
245
251
|
// Resolve hyperlinks over the whole output so nested directory headers
|
|
246
252
|
// reconstruct across the blank-line groups the tree list collapses by.
|
|
247
253
|
const contexts = classifyGroupedLines(allLines, details?.cwd ?? details?.searchPath, details?.searchPath);
|
|
254
|
+
// The display lines are sanitized; reconstruct link targets from the original
|
|
255
|
+
// grouped headers only when they still align with those display lines.
|
|
256
|
+
const rawDetails = options.renderContext?.rawDetails;
|
|
257
|
+
const rawContent =
|
|
258
|
+
isRecord(rawDetails) && typeof rawDetails.displayContent === "string" ? rawDetails.displayContent : undefined;
|
|
259
|
+
const rawLines = rawContent?.split("\n");
|
|
260
|
+
const rawCwd = isRecord(rawDetails) && typeof rawDetails.cwd === "string" ? rawDetails.cwd : undefined;
|
|
261
|
+
const rawSearchPath =
|
|
262
|
+
isRecord(rawDetails) && typeof rawDetails.searchPath === "string" ? rawDetails.searchPath : undefined;
|
|
263
|
+
const rawContexts =
|
|
264
|
+
rawContent !== undefined && rawLines?.length === allLines.length && sanitizeText(rawContent) === textContent
|
|
265
|
+
? classifyGroupedLines(rawLines, rawCwd ?? rawSearchPath, rawSearchPath)
|
|
266
|
+
: undefined;
|
|
248
267
|
// Style lazily: collapsed bodies show only the first groups.
|
|
249
268
|
const styledLines: (string | undefined)[] = Array.from({ length: allLines.length }, () => undefined);
|
|
250
269
|
const styleLine = (index: number): string => {
|
|
@@ -255,12 +274,16 @@ export const astEditToolRenderer = {
|
|
|
255
274
|
// second vertical bar inside the frame border.
|
|
256
275
|
const display = replaceTabs(allLines[index]!.replace("│", " "));
|
|
257
276
|
let styled: string;
|
|
277
|
+
const linkPath =
|
|
278
|
+
ctx.headerPath && rawContexts?.[index]?.kind === ctx.kind && rawContexts[index]?.depth === ctx.depth
|
|
279
|
+
? (rawContexts[index]?.headerPath ?? ctx.headerPath)
|
|
280
|
+
: ctx.headerPath;
|
|
258
281
|
if (ctx.kind === "dir") {
|
|
259
282
|
const accent = uiTheme.fg("accent", display);
|
|
260
|
-
styled =
|
|
283
|
+
styled = linkPath ? fileHyperlink(linkPath, accent) : accent;
|
|
261
284
|
} else if (ctx.kind === "file") {
|
|
262
285
|
const tinted = uiTheme.fg(ctx.depth === 1 ? "accent" : "dim", display);
|
|
263
|
-
styled =
|
|
286
|
+
styled = linkPath ? fileHyperlink(linkPath, tinted) : tinted;
|
|
264
287
|
} else if (display.startsWith("+")) {
|
|
265
288
|
styled = uiTheme.fg("toolDiffAdded", display);
|
|
266
289
|
} else if (display.startsWith("-")) {
|
package/src/tools/ast-grep.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Component } from "../tui";
|
|
2
2
|
import type { Theme } from "../theme/theme";
|
|
3
3
|
import type { OutputMeta } from "./output-meta";
|
|
4
|
-
import type {
|
|
4
|
+
import type { NativeToolView, RenderResultContextOptions, RenderResultOptions } from "./renderer";
|
|
5
5
|
/** Display metadata returned by ast-grep. */
|
|
6
6
|
export interface AstGrepToolDetails {
|
|
7
7
|
matchCount: number;
|
|
@@ -46,7 +46,7 @@ export declare const astGrepToolRenderer: {
|
|
|
46
46
|
}>;
|
|
47
47
|
details?: AstGrepToolDetails;
|
|
48
48
|
isError?: boolean;
|
|
49
|
-
}, options:
|
|
49
|
+
}, options: RenderResultContextOptions, uiTheme: Theme, args?: AstGrepRenderArgs): Component;
|
|
50
50
|
describeCall(args: AstGrepRenderArgs): NativeToolView;
|
|
51
51
|
describeResult(result: {
|
|
52
52
|
content: Array<{
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord, sanitizeText } from "@linxiraos/pi-utils";
|
|
1
2
|
import type { Component } from "../tui";
|
|
2
3
|
import { Text } from "../components/text";
|
|
3
4
|
import type { Theme } from "../theme/theme";
|
|
@@ -13,13 +14,11 @@ import {
|
|
|
13
14
|
toPathList,
|
|
14
15
|
} from "../render/render-utils";
|
|
15
16
|
import { classifyGroupedLines, describeGroupedOutput, groupLineIndicesByBlank } from "./grouped-file-output";
|
|
16
|
-
import { getLanguageFromPath } from "../lang-from-path";
|
|
17
17
|
import { code, compact, node, span } from "../native/describe";
|
|
18
18
|
import type { NativeChild } from "../native/node";
|
|
19
19
|
import { errorText, noteText, resultText, toolHead } from "./native-view";
|
|
20
20
|
import type { OutputMeta } from "./output-meta";
|
|
21
|
-
import {
|
|
22
|
-
import type { RenderResultOptions, ToolRenderer, NativeToolView } from "./renderer";
|
|
21
|
+
import type { NativeToolView, RenderResultContextOptions, RenderResultOptions, ToolRenderer } from "./renderer";
|
|
23
22
|
|
|
24
23
|
/** Display metadata returned by ast-grep. */
|
|
25
24
|
export interface AstGrepToolDetails {
|
|
@@ -61,8 +60,7 @@ export const astGrepToolRenderer = {
|
|
|
61
60
|
renderCall(args: AstGrepRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
62
61
|
const meta: string[] = [];
|
|
63
62
|
const scopePaths = toPathList(args.path ?? args.paths);
|
|
64
|
-
if (scopePaths.length)
|
|
65
|
-
meta.push(tuiTextFmt("gpInLabelFmt", `in ${scopePaths.join(", ")}`, scopePaths.join(", ")));
|
|
63
|
+
if (scopePaths.length) meta.push(`in ${scopePaths.join(", ")}`);
|
|
66
64
|
if (args.skip !== undefined && args.skip > 0) meta.push(`skip:${args.skip}`);
|
|
67
65
|
|
|
68
66
|
const description = args.pat ?? "?";
|
|
@@ -72,15 +70,14 @@ export const astGrepToolRenderer = {
|
|
|
72
70
|
|
|
73
71
|
renderResult(
|
|
74
72
|
result: { content: Array<{ type: string; text?: string }>; details?: AstGrepToolDetails; isError?: boolean },
|
|
75
|
-
options:
|
|
73
|
+
options: RenderResultContextOptions,
|
|
76
74
|
uiTheme: Theme,
|
|
77
75
|
args?: AstGrepRenderArgs,
|
|
78
76
|
): Component {
|
|
79
77
|
const details = result.details;
|
|
80
78
|
|
|
81
79
|
if (result.isError) {
|
|
82
|
-
const errorText =
|
|
83
|
-
result.content?.find(c => c.type === "text")?.text || tuiText("glUnknownError", "Unknown error");
|
|
80
|
+
const errorText = result.content?.find(c => c.type === "text")?.text || "Unknown error";
|
|
84
81
|
return new Text(formatErrorMessage(errorText, uiTheme), 0, 0);
|
|
85
82
|
}
|
|
86
83
|
|
|
@@ -91,33 +88,23 @@ export const astGrepToolRenderer = {
|
|
|
91
88
|
|
|
92
89
|
if (matchCount === 0) {
|
|
93
90
|
const description = args?.pat;
|
|
94
|
-
const meta = [
|
|
95
|
-
if (details?.scopePath) meta.push(
|
|
96
|
-
if (filesSearched > 0) meta.push(
|
|
91
|
+
const meta = ["0 matches"];
|
|
92
|
+
if (details?.scopePath) meta.push(`in ${details.scopePath}`);
|
|
93
|
+
if (filesSearched > 0) meta.push(`searched ${filesSearched}`);
|
|
97
94
|
const header = renderStatusLine({ icon: "warning", title: "AST Grep", description, meta }, uiTheme);
|
|
98
|
-
const lines = [header, formatEmptyMessage(
|
|
95
|
+
const lines = [header, formatEmptyMessage("No matches found", uiTheme)];
|
|
99
96
|
if (details?.parseErrors?.length) {
|
|
100
|
-
lines.push(
|
|
101
|
-
uiTheme.fg(
|
|
102
|
-
"warning",
|
|
103
|
-
tuiText("agrpMisScoped", "Query may be mis-scoped; narrow `path` before concluding absence"),
|
|
104
|
-
),
|
|
105
|
-
);
|
|
97
|
+
lines.push(uiTheme.fg("warning", "Query may be mis-scoped; narrow `path` before concluding absence"));
|
|
106
98
|
appendParseErrorsBulletList(lines, details.parseErrors, uiTheme, details.parseErrorsTotal);
|
|
107
99
|
}
|
|
108
100
|
return new Text(lines.join("\n"), 0, 0);
|
|
109
101
|
}
|
|
110
102
|
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
fileCount === 1 ? tuiText("agrpFileOne", "1 file") : tuiTextFmt("agrpFileManyFmt", "%d files", fileCount);
|
|
117
|
-
const meta = [matchMeta, fileMeta];
|
|
118
|
-
if (details?.scopePath) meta.push(tuiTextFmt("gpInLabelFmt", `in ${details.scopePath}`, details.scopePath));
|
|
119
|
-
meta.push(tuiTextFmt("agrpSearchedFmt", `searched ${filesSearched}`, filesSearched));
|
|
120
|
-
if (limitReached) meta.push(uiTheme.fg("warning", tuiText("tLimitReached", "limit reached")));
|
|
103
|
+
const summaryParts = [formatCount("match", matchCount), formatCount("file", fileCount)];
|
|
104
|
+
const meta = [...summaryParts];
|
|
105
|
+
if (details?.scopePath) meta.push(`in ${details.scopePath}`);
|
|
106
|
+
meta.push(`searched ${filesSearched}`);
|
|
107
|
+
if (limitReached) meta.push(uiTheme.fg("warning", "limit reached"));
|
|
121
108
|
const description = args?.pat;
|
|
122
109
|
const header = renderStatusLine(
|
|
123
110
|
{
|
|
@@ -136,6 +123,20 @@ export const astGrepToolRenderer = {
|
|
|
136
123
|
// Resolve hyperlinks over the whole output so nested directory headers
|
|
137
124
|
// reconstruct across the blank-line groups the tree list collapses by.
|
|
138
125
|
const contexts = classifyGroupedLines(allLines, details?.cwd ?? details?.searchPath, details?.searchPath);
|
|
126
|
+
// The displayed lines are sanitized, but OSC 8 destinations must retain the
|
|
127
|
+
// original path bytes. Only substitute targets for headers already linked
|
|
128
|
+
// by the sanitized output; never let raw text create additional links.
|
|
129
|
+
const rawDetails = options.renderContext?.rawDetails;
|
|
130
|
+
const rawContent =
|
|
131
|
+
isRecord(rawDetails) && typeof rawDetails.displayContent === "string" ? rawDetails.displayContent : undefined;
|
|
132
|
+
const rawLines = rawContent?.split("\n");
|
|
133
|
+
const rawCwd = isRecord(rawDetails) && typeof rawDetails.cwd === "string" ? rawDetails.cwd : undefined;
|
|
134
|
+
const rawSearchPath =
|
|
135
|
+
isRecord(rawDetails) && typeof rawDetails.searchPath === "string" ? rawDetails.searchPath : undefined;
|
|
136
|
+
const rawContexts =
|
|
137
|
+
rawContent !== undefined && rawLines?.length === allLines.length && sanitizeText(rawContent) === textContent
|
|
138
|
+
? classifyGroupedLines(rawLines, rawCwd ?? rawSearchPath, rawSearchPath)
|
|
139
|
+
: undefined;
|
|
139
140
|
// Style lazily: the collapsed tree list only renders the first few groups,
|
|
140
141
|
// so a large result never styles/hyperlinks rows nobody sees.
|
|
141
142
|
const styledLines: (string | undefined)[] = Array.from({ length: allLines.length }, () => undefined);
|
|
@@ -144,13 +145,18 @@ export const astGrepToolRenderer = {
|
|
|
144
145
|
if (cached !== undefined) return cached;
|
|
145
146
|
const line = allLines[index]!;
|
|
146
147
|
const ctx = contexts[index]!;
|
|
148
|
+
const rawContext = rawContexts?.[index];
|
|
149
|
+
const target =
|
|
150
|
+
ctx.headerPath && rawContext?.kind === ctx.kind && rawContext.depth === ctx.depth
|
|
151
|
+
? (rawContext.headerPath ?? ctx.headerPath)
|
|
152
|
+
: ctx.headerPath;
|
|
147
153
|
let styled: string;
|
|
148
154
|
if (ctx.kind === "dir") {
|
|
149
155
|
const accent = uiTheme.fg("accent", line);
|
|
150
|
-
styled =
|
|
156
|
+
styled = target ? fileHyperlink(target, accent) : accent;
|
|
151
157
|
} else if (ctx.kind === "file") {
|
|
152
158
|
const tinted = uiTheme.fg(ctx.depth === 1 ? "accent" : "dim", line);
|
|
153
|
-
styled =
|
|
159
|
+
styled = target ? fileHyperlink(target, tinted) : tinted;
|
|
154
160
|
} else {
|
|
155
161
|
styled = uiTheme.fg(line.startsWith(" meta:") ? "dim" : "toolOutput", line);
|
|
156
162
|
}
|
|
@@ -174,9 +180,7 @@ export const astGrepToolRenderer = {
|
|
|
174
180
|
|
|
175
181
|
const extraLines: string[] = [];
|
|
176
182
|
if (limitReached) {
|
|
177
|
-
extraLines.push(
|
|
178
|
-
uiTheme.fg("warning", tuiText("agrpLimitNarrowPath", "limit reached; narrow path or increase limit")),
|
|
179
|
-
);
|
|
183
|
+
extraLines.push(uiTheme.fg("warning", "limit reached; narrow path or use skip"));
|
|
180
184
|
}
|
|
181
185
|
if (details?.parseErrors?.length) {
|
|
182
186
|
extraLines.push(
|
|
@@ -274,8 +278,8 @@ export const astGrepToolRenderer = {
|
|
|
274
278
|
return {
|
|
275
279
|
head,
|
|
276
280
|
body: compact<NativeChild>([
|
|
277
|
-
...describeGroupedOutput(kept
|
|
278
|
-
limitReached && noteText("limit reached; narrow path or
|
|
281
|
+
...describeGroupedOutput(kept),
|
|
282
|
+
limitReached && noteText("limit reached; narrow path or use skip", "warning"),
|
|
279
283
|
parseNote,
|
|
280
284
|
]),
|
|
281
285
|
preview: { lines: COLLAPSED_MATCH_LIMIT },
|
package/src/tools/bash.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface BashToolDetails {
|
|
|
18
18
|
requestedTimeoutSeconds?: number;
|
|
19
19
|
timeoutDisabled?: boolean;
|
|
20
20
|
wallTimeMs?: number;
|
|
21
|
+
/** False when no wall-time footer was emitted; absent on older results. */
|
|
22
|
+
wallTimeNoticeEmitted?: boolean;
|
|
21
23
|
/** Exit code of a command that ran to completion but failed (non-zero). */
|
|
22
24
|
exitCode?: number;
|
|
23
25
|
/** True when the command was killed by its timeout deadline (not a failure). */
|
|
@@ -31,12 +33,15 @@ export interface BashToolDetails {
|
|
|
31
33
|
timedOut: boolean;
|
|
32
34
|
pid?: number;
|
|
33
35
|
};
|
|
36
|
+
/** Background job behind the call; `cancelled`: killed (`proc://<id>/kill`) before it finished. */
|
|
34
37
|
async?: {
|
|
35
|
-
state: "running" | "completed" | "failed";
|
|
38
|
+
state: "running" | "completed" | "failed" | "cancelled";
|
|
36
39
|
jobId: string;
|
|
37
40
|
type: "bash";
|
|
38
41
|
};
|
|
39
42
|
}
|
|
43
|
+
/** Minimum duration for a model-facing wall-time notice; shorter runs keep timing only in details. */
|
|
44
|
+
export declare const WALL_TIME_NOTICE_MIN_MS = 1000;
|
|
40
45
|
/** Formats the model-facing command duration notice. */
|
|
41
46
|
export declare function formatWallTimeNotice(wallTimeMs: number): string;
|
|
42
47
|
/** Formats the model-facing command exit status notice. */
|