@runminton/pi-tidy-tools 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -11
- package/README.zh-CN.md +51 -11
- package/package.json +1 -1
- package/tidy-tools.ts +188 -34
package/README.md
CHANGED
|
@@ -2,31 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
A tidy tool-rendering extension for [Pi](https://github.com/earendil-works/pi-mono) (the pi-coding-agent TUI). It overrides the rendering of `bash`, `edit` and `write`: compact summaries by default, full official rendering when you need it.
|
|
4
4
|
|
|
5
|
+
> Display layer only. Tool execution is fully delegated to Pi's official implementations — parameters, behavior and results are untouched; the extension only decides how many rows to draw and in which colors.
|
|
6
|
+
|
|
5
7
|
[中文版](README.zh-CN.md)
|
|
6
8
|
|
|
7
9
|
## Default view
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
All three tools start **Collapsed**:
|
|
10
12
|
|
|
11
|
-
| Tool |
|
|
13
|
+
| Tool | Collapsed summary | Expanded |
|
|
12
14
|
|---|---|---|
|
|
13
|
-
| `bash` |
|
|
14
|
-
| `edit` |
|
|
15
|
-
| `write` |
|
|
15
|
+
| `bash` | `$ command` plus one output line, with an inline `... (N more lines)` suffix when truncated | complete output |
|
|
16
|
+
| `edit` | `edit path (N blocks)` + `applied +a -b` | Pi's official renderer: path, block count, live diff preview, native layout |
|
|
17
|
+
| `write` | `write path (N lines)` + `done` | Pi's official renderer: path, line count, syntax-highlighted content |
|
|
18
|
+
|
|
19
|
+
Collapsed summaries are single-line per slot: one command line plus one output line (the count suffix is inline at the end of the output line).
|
|
20
|
+
|
|
21
|
+
## Modes
|
|
22
|
+
|
|
23
|
+
| Mode | Behavior |
|
|
24
|
+
|---|---|
|
|
25
|
+
| compact (default) | all three tools collapsed |
|
|
26
|
+
| markdown (`/tidy-markdown` toggles) | `edit`/`write` on Markdown files (`.md`, `.mdx`, `.markdown`) expand to full content; everything else stays collapsed |
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
Switching modes clears per-tool manual toggles and re-renders existing rows. The mode is not persisted across restarts.
|
|
18
29
|
|
|
19
30
|
## Shortcuts
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
On Mac press `Command+Option+letter` (verified; maps to the registered `Ctrl+Alt+letter`. Other platforms press `Ctrl+Alt+letter`):
|
|
33
|
+
|
|
34
|
+
| Shortcut (Mac) | Tool | Toggles between |
|
|
22
35
|
|---|---|---|
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
36
|
+
| `Command+Option+B` | `bash` | collapsed summary ↔ **one-key full output** |
|
|
37
|
+
| `Command+Option+E` | `edit` | official rendering ↔ collapsed summary |
|
|
38
|
+
| `Command+Option+W` | `write` | official rendering ↔ collapsed summary |
|
|
26
39
|
|
|
27
40
|
A notification shows the current state on each toggle; press again to switch back.
|
|
28
41
|
|
|
29
|
-
`bash`
|
|
42
|
+
`bash` and `write` expand to the **complete output** in one keypress — they skip Pi's built-in previews (bash's 5-line tail preview, write's 10-line preview). `edit` expands to Pi's **official rendering** (it has no preview state). The global `Ctrl+O` tool expansion does not affect these three tools.
|
|
43
|
+
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
Use these commands when terminal shortcuts conflict or are not delivered:
|
|
47
|
+
|
|
48
|
+
| Command | Tool |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `/tidy-bash` | Toggle `bash` output |
|
|
51
|
+
| `/tidy-edit` | Toggle `edit` output |
|
|
52
|
+
| `/tidy-write` | Toggle `write` output |
|
|
53
|
+
| `/tidy-markdown` | Toggle markdown mode (edit/write expand for Markdown files) |
|
|
30
54
|
|
|
31
55
|
## Usage
|
|
32
56
|
|
|
@@ -61,6 +85,22 @@ If a shortcut still does nothing on your machine, check:
|
|
|
61
85
|
1. **Terminal or input method swallows the key.** Some terminals and IMEs (e.g. Chinese input methods, Windows Alt-menu accelerators) intercept `Ctrl+Alt+...` before Pi ever sees it. Try a different combination or a different terminal.
|
|
62
86
|
2. **tmux / screen.** If Pi runs inside tmux with the default configuration, tmux's own key handling may interfere.
|
|
63
87
|
3. **Custom keys.** The bindings are registered in `tidy-tools.ts` via `pi.registerShortcut(...)` — edit the key strings there to rebind. Pi's built-in global expansion (`Ctrl+O`, `app.tools.expand`) is configured separately via `~/.pi/agent/keybindings.json`.
|
|
88
|
+
4. **Inspect the input.** Run `/tidy-key-debug`, then press one target shortcut. Pi shows the received bytes and any rewrite. If no notification appears, the terminal or input method consumed the key before Pi received it.
|
|
89
|
+
|
|
90
|
+
## Changelog
|
|
91
|
+
|
|
92
|
+
### 0.2.0
|
|
93
|
+
|
|
94
|
+
- All three tools collapsed by default (edit/write used to expand)
|
|
95
|
+
- New `/tidy-markdown`: edit/write on Markdown files auto-expand to full content, the rest stays collapsed; switching modes clears manual toggles
|
|
96
|
+
- Write expansion skips the official 10-line preview and shows full content; global `Ctrl+O` no longer affects any of the three tools
|
|
97
|
+
- Bash collapsed output is 2 rows with the count suffix inline, including error output
|
|
98
|
+
- Fixed tab-width overflow wrapping collapsed bash output on narrow terminals; fixed doubled edit padding from a nested render shell
|
|
99
|
+
|
|
100
|
+
### 0.1.3
|
|
101
|
+
|
|
102
|
+
- New `/tidy-bash`, `/tidy-edit`, `/tidy-write`, `/tidy-key-debug` commands
|
|
103
|
+
- Normalized the legacy `ESC` + control-character shortcut form
|
|
64
104
|
|
|
65
105
|
## Development
|
|
66
106
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,31 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
为 [Pi](https://github.com/earendil-works/pi-mono)(pi-coding-agent 的 TUI)打造的工具渲染精简扩展。它覆盖了 `bash`、`edit`、`write` 的渲染:默认呈现紧凑摘要,需要时切换为官方完整渲染。
|
|
4
4
|
|
|
5
|
+
> 只动显示层,不动执行层。三个工具的执行完全委托 Pi 官方实现,参数、行为、结果一字不差;插件只决定屏幕上画几行、用什么颜色。
|
|
6
|
+
|
|
5
7
|
[English](README.md)
|
|
6
8
|
|
|
7
9
|
## 默认视图
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
三个工具默认**全部折叠**:
|
|
10
12
|
|
|
11
|
-
| 工具 |
|
|
13
|
+
| 工具 | 折叠摘要 | 展开 |
|
|
12
14
|
|---|---|---|
|
|
13
|
-
| `bash` |
|
|
14
|
-
| `edit` |
|
|
15
|
-
| `write` |
|
|
15
|
+
| `bash` | `$ command` 加一行输出预览,计数后缀 `... (N more lines)` 内联在行尾 | 完整输出 |
|
|
16
|
+
| `edit` | `edit 路径(N blocks)` + `applied +a -b` | Pi 官方渲染:路径、修改块数、实时 diff 预览、原生布局 |
|
|
17
|
+
| `write` | `write 路径(N lines)` + `done` | Pi 官方渲染:路径、行数、语法高亮内容 |
|
|
18
|
+
|
|
19
|
+
折叠摘要每槽单行:命令 1 行加输出 1 行(计数后缀内联在输出行尾)。
|
|
20
|
+
|
|
21
|
+
## 模式
|
|
22
|
+
|
|
23
|
+
| 模式 | 行为 |
|
|
24
|
+
|---|---|
|
|
25
|
+
| compact(默认) | 三工具全折叠 |
|
|
26
|
+
| markdown(`/tidy-markdown` 切换) | Markdown 文件(`.md`、`.mdx`、`.markdown`)的 `edit`/`write` 展开全文,其余照样折叠 |
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
切换模式会清空各工具的手动钉选并重渲染已有行。模式不持久化,重启后回到 compact。
|
|
18
29
|
|
|
19
30
|
## 快捷键
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
Mac 上按 `Command+Option+字母`(实测有效,对应插件注册的 `Ctrl+Alt+字母`;其他平台按 `Ctrl+Alt+字母`):
|
|
33
|
+
|
|
34
|
+
| 快捷键(Mac) | 工具 | 切换内容 |
|
|
22
35
|
|---|---|---|
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
36
|
+
| `Command+Option+B` | `bash` | 折叠摘要 ↔ **一键完整输出** |
|
|
37
|
+
| `Command+Option+E` | `edit` | 官方渲染 ↔ 折叠摘要 |
|
|
38
|
+
| `Command+Option+W` | `write` | 官方渲染 ↔ 折叠摘要 |
|
|
26
39
|
|
|
27
40
|
每次切换会弹出通知提示当前状态;再按一次切回。
|
|
28
41
|
|
|
29
|
-
`bash` 展开时**一次按键直接显示完整输出**——跳过 Pi
|
|
42
|
+
`bash` 和 `write` 展开时**一次按键直接显示完整输出**——跳过 Pi 内置预览(bash 的 5 行尾部预览、write 的 10 行预览)。`edit` 展开时使用 Pi **官方渲染**(它没有预览态)。全局 `Ctrl+O` 对这三个工具均无效。
|
|
43
|
+
|
|
44
|
+
## 命令
|
|
45
|
+
|
|
46
|
+
终端快捷键冲突或无法传入 Pi 时,使用这些命令:
|
|
47
|
+
|
|
48
|
+
| 命令 | 工具 |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `/tidy-bash` | 切换 `bash` 输出 |
|
|
51
|
+
| `/tidy-edit` | 切换 `edit` 输出 |
|
|
52
|
+
| `/tidy-write` | 切换 `write` 输出 |
|
|
53
|
+
| `/tidy-markdown` | 切换 Markdown 模式(Markdown 文件的 edit/write 展开) |
|
|
30
54
|
|
|
31
55
|
## 使用方法
|
|
32
56
|
|
|
@@ -61,6 +85,22 @@ Pi 会在终端支持时协商 [Kitty 键盘协议](https://sw.kovidgoyal.net/ki
|
|
|
61
85
|
1. **终端或输入法吞掉了按键。** 有些终端和输入法(例如中文输入法、Windows 的 Alt 菜单加速键)会在 Pi 收到按键之前把它拦截。换一个组合键或换一个终端试试。
|
|
62
86
|
2. **tmux / screen。** 如果 Pi 运行在 tmux 默认配置里,tmux 自己的按键处理可能干扰。
|
|
63
87
|
3. **自定义按键。** 绑定在 `tidy-tools.ts` 里通过 `pi.registerShortcut(...)` 注册——改那里的按键字符串即可重新绑定。Pi 内置的全局展开(`Ctrl+O`,即 `app.tools.expand`)则单独通过 `~/.pi/agent/keybindings.json` 配置。
|
|
88
|
+
4. **检查输入。** 执行 `/tidy-key-debug`,再按一次目标快捷键。Pi 会显示收到的字节和改写结果。若没有通知,终端或输入法在 Pi 收到按键前已拦截它。
|
|
89
|
+
|
|
90
|
+
## 更新日志
|
|
91
|
+
|
|
92
|
+
### 0.2.0 - 2026-09-05
|
|
93
|
+
|
|
94
|
+
- 默认三工具全折叠(edit/write 原默认展开)
|
|
95
|
+
- 新增 `/tidy-markdown`:Markdown 文件的 edit/write 自动展开全文,其余照样折叠;切模式清空手动钉选
|
|
96
|
+
- write 展开跳过官方 10 行预览,直接全文;全局 `Ctrl+O` 对三工具失效
|
|
97
|
+
- bash 折叠压成 2 行:计数后缀内联到输出行尾,失败输出(红色)同样单行
|
|
98
|
+
- 修 tab 宽度导致的窄终端折行;修 edit 双层外框多出的 padding
|
|
99
|
+
|
|
100
|
+
### 0.1.3
|
|
101
|
+
|
|
102
|
+
- 新增 `/tidy-bash`、`/tidy-edit`、`/tidy-write`、`/tidy-key-debug` 命令
|
|
103
|
+
- 修传统 `ESC` + 控制字符形式的快捷键归一化
|
|
64
104
|
|
|
65
105
|
## 开发
|
|
66
106
|
|
package/package.json
CHANGED
package/tidy-tools.ts
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* - Expanded: Ctrl+Alt+E (edit), Ctrl+Alt+W (write), Ctrl+Alt+B (bash)
|
|
8
8
|
* toggle each tool between collapsed and fully expanded rendering.
|
|
9
9
|
* - The global Ctrl+O tool expansion does not affect these three tools.
|
|
10
|
+
* - Modes: compact (default, all three collapsed) and markdown
|
|
11
|
+
* (`/tidy-markdown` toggles; edit/write on Markdown files expand,
|
|
12
|
+
* the rest stays collapsed). Not persisted across restarts.
|
|
10
13
|
*
|
|
11
14
|
* Usage:
|
|
12
15
|
* pi -e ./tidy-tools.ts
|
|
@@ -32,8 +35,8 @@ import {
|
|
|
32
35
|
|
|
33
36
|
const MAX_COLLAPSED_COMMAND_LINES = 1;
|
|
34
37
|
const MAX_COLLAPSED_CONTENT_LINES = 3;
|
|
35
|
-
// bash
|
|
36
|
-
const MAX_COLLAPSED_BASH_OUTPUT_LINES =
|
|
38
|
+
// bash 折叠输出:预览与计数后缀合并为 1 行(命令 1 行 + 输出 1 行)。
|
|
39
|
+
const MAX_COLLAPSED_BASH_OUTPUT_LINES = 1;
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
42
|
* Truncate rendered rows to at most maxLines, keeping the beginning.
|
|
@@ -65,6 +68,14 @@ function firstLine(text: string | undefined): string {
|
|
|
65
68
|
return line.length > 80 ? `${line.slice(0, 77)}...` : line;
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
// 终端 tab 按 8 的倍数对齐,pi-tui 按 3 列计算。不展开 tab 会导致
|
|
72
|
+
// 行宽估算小于终端实际渲染,窄终端下折成两行。与官方 replaceTabs
|
|
73
|
+
// 保持一致:tab 展开为 3 个空格。
|
|
74
|
+
|
|
75
|
+
function expandTabs(text: string): string {
|
|
76
|
+
return text.replace(/\t/g, " ");
|
|
77
|
+
}
|
|
78
|
+
|
|
68
79
|
function errorText(result: { content: { type: string; text?: string }[] }): string {
|
|
69
80
|
return result.content
|
|
70
81
|
.filter((c) => c.type === "text")
|
|
@@ -119,6 +130,46 @@ class LimitedLinesText implements Component {
|
|
|
119
130
|
}
|
|
120
131
|
}
|
|
121
132
|
|
|
133
|
+
class CollapsedBashOutput implements Component {
|
|
134
|
+
// 首行预览 + 计数后缀合并为 1 行,后缀常显。预览放不下时截断并加省略标记。
|
|
135
|
+
constructor(
|
|
136
|
+
private readonly preview: string,
|
|
137
|
+
private readonly suffix: string,
|
|
138
|
+
private readonly truncatedMarker: string,
|
|
139
|
+
) {}
|
|
140
|
+
|
|
141
|
+
render(width: number): string[] {
|
|
142
|
+
if (!this.suffix) {
|
|
143
|
+
return truncateRows(
|
|
144
|
+
wrapTextWithAnsi(this.preview, Math.max(1, width)),
|
|
145
|
+
MAX_COLLAPSED_BASH_OUTPUT_LINES,
|
|
146
|
+
this.truncatedMarker,
|
|
147
|
+
width,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const suffixWidth = visibleWidth(this.suffix);
|
|
151
|
+
if (suffixWidth >= width) {
|
|
152
|
+
return [sliceByColumn(this.suffix, 0, Math.max(1, width), true)];
|
|
153
|
+
}
|
|
154
|
+
const previewBudget = width - suffixWidth - 1;
|
|
155
|
+
if (visibleWidth(this.preview) <= previewBudget) {
|
|
156
|
+
return [`${this.preview} ${this.suffix}`];
|
|
157
|
+
}
|
|
158
|
+
const markerWidth = visibleWidth(this.truncatedMarker);
|
|
159
|
+
const preview = sliceByColumn(
|
|
160
|
+
this.preview,
|
|
161
|
+
0,
|
|
162
|
+
Math.max(0, previewBudget - markerWidth),
|
|
163
|
+
true,
|
|
164
|
+
);
|
|
165
|
+
const row = `${preview}${this.truncatedMarker} ${this.suffix}`;
|
|
166
|
+
// 极窄宽度下省略标记本身都放不下时做最终钳制,保证恒为 1 行。
|
|
167
|
+
return [visibleWidth(row) > width ? sliceByColumn(row, 0, width, true) : row];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
invalidate(): void {}
|
|
171
|
+
}
|
|
172
|
+
|
|
122
173
|
class CollapsedToolShell extends Box {
|
|
123
174
|
private callComponent?: Component;
|
|
124
175
|
private resultComponent?: Component;
|
|
@@ -160,13 +211,37 @@ function getCollapsedBackground(isPartial: boolean, isError: boolean): string {
|
|
|
160
211
|
return isError ? "toolErrorBg" : "toolSuccessBg";
|
|
161
212
|
}
|
|
162
213
|
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
214
|
+
// 显示模式:compact 下三工具默认全折叠;markdown 模式下 edit/write 遇到
|
|
215
|
+
// Markdown 文件默认展开全文,其余照样折叠。默认 compact,不持久化。
|
|
216
|
+
let markdownMode = false;
|
|
217
|
+
// 手动钉选:Ctrl+Alt+? 或 /tidy-<tool> 设置,优先于模式默认;切换模式时清空。
|
|
218
|
+
const manualExpanded = new Map<string, boolean>();
|
|
219
|
+
|
|
220
|
+
function isMarkdownPath(value: unknown): boolean {
|
|
221
|
+
if (typeof value !== "string") return false;
|
|
222
|
+
return /\.(md|mdx|markdown)$/i.test(value.trim());
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function toolArgsMarkdown(args: any): boolean {
|
|
226
|
+
return isMarkdownPath(args?.path) || isMarkdownPath(args?.file_path);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 不带文件上下文的工具级基准(用于翻转手动钉选):compact 下全 false;
|
|
230
|
+
// markdown 模式下 edit/write 为 true(具体到文件时再按后缀细化)。
|
|
231
|
+
function modeBaseExpanded(name: string): boolean {
|
|
232
|
+
if (!markdownMode) return false;
|
|
233
|
+
return name === "edit" || name === "write";
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function modeDefaultExpanded(name: string, args: any): boolean {
|
|
237
|
+
if (!markdownMode) return false;
|
|
238
|
+
if (name === "edit" || name === "write") return toolArgsMarkdown(args);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function isToolExpanded(name: string, args: any): boolean {
|
|
243
|
+
return manualExpanded.get(name) ?? modeDefaultExpanded(name, args);
|
|
244
|
+
}
|
|
170
245
|
|
|
171
246
|
// Some terminals (Kitty keyboard protocol flag-1 mode) encode Ctrl+letter as
|
|
172
247
|
// a CSI-u sequence using the control character code without a Ctrl modifier
|
|
@@ -180,8 +255,17 @@ const CTRL_CODE_TO_KEY: Record<number, string> = {
|
|
|
180
255
|
2: "b", // Ctrl+B
|
|
181
256
|
};
|
|
182
257
|
const ALT_MODIFIER = 2;
|
|
258
|
+
let debugNextTerminalInput = false;
|
|
183
259
|
|
|
184
260
|
function convertCtrlAltSequence(data: string): string | undefined {
|
|
261
|
+
// Legacy terminals send Ctrl+Alt+letter as ESC followed by the Ctrl
|
|
262
|
+
// character. Pi deliberately ignores this form after Kitty negotiation,
|
|
263
|
+
// so normalize it before Pi's key matcher sees it.
|
|
264
|
+
if (data.length === 2 && data[0] === "\x1b") {
|
|
265
|
+
const key = CTRL_CODE_TO_KEY[data.charCodeAt(1)];
|
|
266
|
+
if (key) return `\x1b[${key.charCodeAt(0)};7u`; // 7 = Ctrl|Alt (1-indexed)
|
|
267
|
+
}
|
|
268
|
+
|
|
185
269
|
const match = data.match(/^\x1b\[(\d+);(\d+)(?::(\d+))?u$/);
|
|
186
270
|
if (!match) return undefined;
|
|
187
271
|
const code = Number(match[1]);
|
|
@@ -192,14 +276,18 @@ function convertCtrlAltSequence(data: string): string | undefined {
|
|
|
192
276
|
return `\x1b[${key.charCodeAt(0)};7${eventSuffix}u`; // 7 = Ctrl|Alt (1-indexed)
|
|
193
277
|
}
|
|
194
278
|
|
|
195
|
-
function
|
|
196
|
-
toolExpanded.set(name, !(toolExpanded.get(name) ?? false));
|
|
279
|
+
function refreshToolRows(ctx: ExtensionContext): void {
|
|
197
280
|
const globalExpanded = ctx.ui.getToolsExpanded();
|
|
198
281
|
// Force every tool row to re-render. The global value is restored
|
|
199
282
|
// immediately, so only the rows' renderers refresh, not the global state.
|
|
200
283
|
ctx.ui.setToolsExpanded(!globalExpanded);
|
|
201
284
|
ctx.ui.setToolsExpanded(globalExpanded);
|
|
202
|
-
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function toggleToolExpanded(ctx: ExtensionContext, name: string): void {
|
|
288
|
+
manualExpanded.set(name, !(manualExpanded.get(name) ?? modeBaseExpanded(name)));
|
|
289
|
+
refreshToolRows(ctx);
|
|
290
|
+
ctx.ui.notify(`${name}: ${manualExpanded.get(name) ? "expanded" : "collapsed"}`, "info");
|
|
203
291
|
}
|
|
204
292
|
|
|
205
293
|
// Cache the official tool implementation by working directory, as pi may run
|
|
@@ -235,6 +323,9 @@ function registerCollapsibleTool(
|
|
|
235
323
|
getDef: (cwd: string) => ToolDefinition<any, any, any>,
|
|
236
324
|
collapsedCall: (args: any, theme: any) => Component,
|
|
237
325
|
collapsedResult: (result: any, options: any, theme: any, context: any) => Component,
|
|
326
|
+
// 跳过官方预览态,展开即全文(bash 行为)。为 false 时保留官方默认视图,
|
|
327
|
+
// 其内部预览 / 全文仍由全局 Ctrl+O 决定。
|
|
328
|
+
forceFullExpansion = false,
|
|
238
329
|
) {
|
|
239
330
|
const def = getDef(process.cwd());
|
|
240
331
|
const usesSelfShell = def.renderShell === "self";
|
|
@@ -247,8 +338,11 @@ function registerCollapsibleTool(
|
|
|
247
338
|
parameters: def.parameters,
|
|
248
339
|
prepareArguments: def.prepareArguments,
|
|
249
340
|
constrainedSampling: def.constrainedSampling,
|
|
250
|
-
//
|
|
251
|
-
//
|
|
341
|
+
// Mirror the official renderShell. Omitting it falls back to
|
|
342
|
+
// "default", which double-wraps "self" tools like edit in an
|
|
343
|
+
// extra Box: one extra padding line on top and bottom when
|
|
344
|
+
// expanded, and a Box-in-Box when collapsed.
|
|
345
|
+
...(usesSelfShell ? { renderShell: "self" as const } : {}),
|
|
252
346
|
|
|
253
347
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
254
348
|
return getDef(ctx.cwd).execute(toolCallId, params, signal, onUpdate, ctx);
|
|
@@ -256,11 +350,17 @@ function registerCollapsibleTool(
|
|
|
256
350
|
|
|
257
351
|
renderCall(args, theme, context) {
|
|
258
352
|
const def = getDef(context.cwd);
|
|
259
|
-
if (
|
|
353
|
+
if (isToolExpanded(def.name, args) && def.renderCall) {
|
|
260
354
|
return def.renderCall(
|
|
261
355
|
args as never,
|
|
262
356
|
theme as never,
|
|
263
|
-
|
|
357
|
+
// One-key full expansion: skip the official preview so
|
|
358
|
+
// the per-tool shortcut always shows complete output.
|
|
359
|
+
{
|
|
360
|
+
...context,
|
|
361
|
+
...(forceFullExpansion ? { expanded: true } : {}),
|
|
362
|
+
lastComponent: undefined,
|
|
363
|
+
} as never,
|
|
264
364
|
);
|
|
265
365
|
}
|
|
266
366
|
const component = collapsedCall(args, theme);
|
|
@@ -277,10 +377,10 @@ function registerCollapsibleTool(
|
|
|
277
377
|
|
|
278
378
|
renderResult(result, options, theme, context) {
|
|
279
379
|
const def = getDef(context.cwd);
|
|
280
|
-
if (
|
|
380
|
+
if (isToolExpanded(def.name, context.args) && def.renderResult) {
|
|
281
381
|
return def.renderResult(
|
|
282
382
|
result as never,
|
|
283
|
-
options as never,
|
|
383
|
+
{ ...options, ...(forceFullExpansion ? { expanded: true } : {}) } as never,
|
|
284
384
|
theme as never,
|
|
285
385
|
{ ...context, lastComponent: undefined } as never,
|
|
286
386
|
);
|
|
@@ -336,7 +436,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
336
436
|
},
|
|
337
437
|
|
|
338
438
|
renderCall(args, theme, context) {
|
|
339
|
-
if (
|
|
439
|
+
if (isToolExpanded("bash", args)) {
|
|
340
440
|
const def = getBashDef(context.cwd);
|
|
341
441
|
if (def.renderCall) {
|
|
342
442
|
return def.renderCall(
|
|
@@ -349,7 +449,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
349
449
|
}
|
|
350
450
|
}
|
|
351
451
|
|
|
352
|
-
const
|
|
452
|
+
const rawCommand = args.command || "...";
|
|
453
|
+
const command = typeof rawCommand === "string" ? expandTabs(rawCommand) : "...";
|
|
353
454
|
const timeout = args.timeout as number | undefined;
|
|
354
455
|
const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
|
|
355
456
|
const renderedCommand = theme.fg("toolTitle", theme.bold(`$ ${command}`)) + timeoutSuffix;
|
|
@@ -362,7 +463,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
362
463
|
},
|
|
363
464
|
|
|
364
465
|
renderResult(result, options, theme, context) {
|
|
365
|
-
if (
|
|
466
|
+
if (isToolExpanded("bash", context.args)) {
|
|
366
467
|
const def = getBashDef(context.cwd);
|
|
367
468
|
if (def.renderResult) {
|
|
368
469
|
return def.renderResult(
|
|
@@ -376,26 +477,40 @@ export default function (pi: ExtensionAPI) {
|
|
|
376
477
|
}
|
|
377
478
|
}
|
|
378
479
|
|
|
379
|
-
const output = errorText(result).trim().replace(/\r/g, "");
|
|
480
|
+
const output = expandTabs(errorText(result).trim().replace(/\r/g, ""));
|
|
481
|
+
const allLines = output.split("\n");
|
|
482
|
+
const first = allLines[0] ?? "";
|
|
483
|
+
const moreSuffix =
|
|
484
|
+
allLines.length > 1
|
|
485
|
+
? theme.fg("muted", `... (${allLines.length - 1} more lines)`)
|
|
486
|
+
: "";
|
|
487
|
+
// 失败输出同样压成 1 行:红色首行 + 内联计数,与成功态同形。
|
|
380
488
|
if (context.isError) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
489
|
+
if (!first) {
|
|
490
|
+
return new CollapsedBashOutput(
|
|
491
|
+
theme.fg("error", "failed"),
|
|
492
|
+
"",
|
|
493
|
+
theme.fg("muted", "..."),
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
return new CollapsedBashOutput(
|
|
497
|
+
theme.fg("error", first),
|
|
498
|
+
moreSuffix,
|
|
384
499
|
theme.fg("muted", "..."),
|
|
385
500
|
);
|
|
386
501
|
}
|
|
387
502
|
// Preview only the first line. Wrapping the full output on every
|
|
388
503
|
// frame is what made the TUI lag with large outputs.
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
theme.fg("muted",
|
|
504
|
+
if (!first) {
|
|
505
|
+
return new CollapsedBashOutput(
|
|
506
|
+
theme.fg("muted", "(no output)"),
|
|
507
|
+
"",
|
|
508
|
+
theme.fg("muted", "..."),
|
|
394
509
|
);
|
|
395
510
|
}
|
|
396
|
-
return new
|
|
397
|
-
|
|
398
|
-
|
|
511
|
+
return new CollapsedBashOutput(
|
|
512
|
+
theme.fg("toolOutput", first),
|
|
513
|
+
moreSuffix,
|
|
399
514
|
theme.fg("muted", "..."),
|
|
400
515
|
);
|
|
401
516
|
},
|
|
@@ -453,8 +568,39 @@ export default function (pi: ExtensionAPI) {
|
|
|
453
568
|
}
|
|
454
569
|
return new Text(theme.fg("success", "done"), 0, 0);
|
|
455
570
|
},
|
|
571
|
+
// 展开即全文,跳过官方 10 行预览:三工具统一两档,Ctrl+O 彻底失效。
|
|
572
|
+
true,
|
|
456
573
|
);
|
|
457
574
|
|
|
575
|
+
pi.registerCommand("tidy-bash", {
|
|
576
|
+
description: "Toggle bash output expansion",
|
|
577
|
+
handler: async (_args, ctx) => toggleToolExpanded(ctx, "bash"),
|
|
578
|
+
});
|
|
579
|
+
pi.registerCommand("tidy-edit", {
|
|
580
|
+
description: "Toggle edit output expansion",
|
|
581
|
+
handler: async (_args, ctx) => toggleToolExpanded(ctx, "edit"),
|
|
582
|
+
});
|
|
583
|
+
pi.registerCommand("tidy-write", {
|
|
584
|
+
description: "Toggle write output expansion",
|
|
585
|
+
handler: async (_args, ctx) => toggleToolExpanded(ctx, "write"),
|
|
586
|
+
});
|
|
587
|
+
pi.registerCommand("tidy-markdown", {
|
|
588
|
+
description: "Toggle markdown mode (edit/write expand for Markdown files)",
|
|
589
|
+
handler: async (_args, ctx) => {
|
|
590
|
+
markdownMode = !markdownMode;
|
|
591
|
+
manualExpanded.clear();
|
|
592
|
+
refreshToolRows(ctx);
|
|
593
|
+
ctx.ui.notify(`markdown mode: ${markdownMode ? "on" : "off"}`, "info");
|
|
594
|
+
},
|
|
595
|
+
});
|
|
596
|
+
pi.registerCommand("tidy-key-debug", {
|
|
597
|
+
description: "Show the next raw terminal key sequence",
|
|
598
|
+
handler: async (_args, ctx) => {
|
|
599
|
+
debugNextTerminalInput = true;
|
|
600
|
+
ctx.ui.notify("Press one key to inspect its terminal sequence.", "info");
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
|
|
458
604
|
// --- Per-tool expansion shortcuts, independent of the global Ctrl+O ---
|
|
459
605
|
pi.registerShortcut("ctrl+alt+e", {
|
|
460
606
|
description: "Toggle edit expansion",
|
|
@@ -472,8 +618,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
472
618
|
// Some terminals encode Ctrl+Alt+letter as a control-character CSI-u
|
|
473
619
|
// sequence. Rewrite those three sequences before they reach the editor.
|
|
474
620
|
pi.on("session_start", (_event, ctx) => {
|
|
475
|
-
ctx.ui.onTerminalInput
|
|
621
|
+
ctx.ui.onTerminalInput((data) => {
|
|
476
622
|
const converted = convertCtrlAltSequence(data);
|
|
623
|
+
if (debugNextTerminalInput) {
|
|
624
|
+
debugNextTerminalInput = false;
|
|
625
|
+
const bytes = [...data].map((char) => char.charCodeAt(0)).join(", ");
|
|
626
|
+
ctx.ui.notify(
|
|
627
|
+
`raw: ${JSON.stringify(data)} [${bytes}]${converted ? ` → ${JSON.stringify(converted)}` : ""}`,
|
|
628
|
+
"info",
|
|
629
|
+
);
|
|
630
|
+
}
|
|
477
631
|
return converted ? { data: converted } : undefined;
|
|
478
632
|
});
|
|
479
633
|
});
|