@runminton/pi-tidy-tools 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/README.zh-CN.md +13 -2
- package/package.json +1 -1
- package/tidy-tools.ts +82 -26
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ All three tools start **Collapsed**:
|
|
|
18
18
|
|
|
19
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
20
|
|
|
21
|
+
In Pi's fullscreen mode, left-click any tool block to toggle that row between its compact summary and full rendering. Click it again to collapse it. Each tool has only these two states; `bash` and `write` skip Pi's intermediate previews.
|
|
22
|
+
|
|
21
23
|
## Modes
|
|
22
24
|
|
|
23
25
|
| Mode | Behavior |
|
|
@@ -25,7 +27,7 @@ Collapsed summaries are single-line per slot: one command line plus one output l
|
|
|
25
27
|
| compact (default) | all three tools collapsed |
|
|
26
28
|
| markdown (`/tidy-markdown` toggles) | `edit`/`write` on Markdown files (`.md`, `.mdx`, `.markdown`) expand to full content; everything else stays collapsed |
|
|
27
29
|
|
|
28
|
-
Switching modes clears
|
|
30
|
+
Switching modes clears shortcut state and row-local click state, then re-renders existing rows. The mode is not persisted across restarts.
|
|
29
31
|
|
|
30
32
|
## Shortcuts
|
|
31
33
|
|
|
@@ -37,7 +39,7 @@ On Mac press `Command+Option+letter` (verified; maps to the registered `Ctrl+Alt
|
|
|
37
39
|
| `Command+Option+E` | `edit` | official rendering ↔ collapsed summary |
|
|
38
40
|
| `Command+Option+W` | `write` | official rendering ↔ collapsed summary |
|
|
39
41
|
|
|
40
|
-
A notification shows the
|
|
42
|
+
A shortcut toggles all existing rows of that tool type and clears their prior row-local click state. A notification shows the new state; press again to switch back.
|
|
41
43
|
|
|
42
44
|
`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
45
|
|
|
@@ -89,6 +91,15 @@ If a shortcut still does nothing on your machine, check:
|
|
|
89
91
|
|
|
90
92
|
## Changelog
|
|
91
93
|
|
|
94
|
+
### 0.2.2
|
|
95
|
+
|
|
96
|
+
- Cached collapsed bash output by terminal width to reduce repeated rendering work during TUI redraws with many tool calls
|
|
97
|
+
|
|
98
|
+
### 0.2.1
|
|
99
|
+
|
|
100
|
+
- Added per-block fullscreen clicking for bash/edit/write, toggling between compact and full rendering
|
|
101
|
+
- Kept tool-wide shortcuts; a shortcut applies one state to all rows of that tool type
|
|
102
|
+
|
|
92
103
|
### 0.2.0
|
|
93
104
|
|
|
94
105
|
- All three tools collapsed by default (edit/write used to expand)
|
package/README.zh-CN.md
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
折叠摘要每槽单行:命令 1 行加输出 1 行(计数后缀内联在输出行尾)。
|
|
20
20
|
|
|
21
|
+
在 Pi 的 fullscreen 模式下,左键点击任意一个工具块,可单独在紧凑摘要和完整渲染之间切换。再次点击即收起。三个工具都只有这两种状态;`bash` 和 `write` 不经过官方中间预览。
|
|
22
|
+
|
|
21
23
|
## 模式
|
|
22
24
|
|
|
23
25
|
| 模式 | 行为 |
|
|
@@ -25,7 +27,7 @@
|
|
|
25
27
|
| compact(默认) | 三工具全折叠 |
|
|
26
28
|
| markdown(`/tidy-markdown` 切换) | Markdown 文件(`.md`、`.mdx`、`.markdown`)的 `edit`/`write` 展开全文,其余照样折叠 |
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
切换模式会清空快捷键状态和各工具块的点击状态,并重渲染已有行。模式不持久化,重启后回到 compact。
|
|
29
31
|
|
|
30
32
|
## 快捷键
|
|
31
33
|
|
|
@@ -37,7 +39,7 @@ Mac 上按 `Command+Option+字母`(实测有效,对应插件注册的 `Ctrl+
|
|
|
37
39
|
| `Command+Option+E` | `edit` | 官方渲染 ↔ 折叠摘要 |
|
|
38
40
|
| `Command+Option+W` | `write` | 官方渲染 ↔ 折叠摘要 |
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
快捷键按工具类型切换所有已有工具块,并清除该工具之前的逐块点击状态。每次切换会弹出通知;再按一次切回。
|
|
41
43
|
|
|
42
44
|
`bash` 和 `write` 展开时**一次按键直接显示完整输出**——跳过 Pi 内置预览(bash 的 5 行尾部预览、write 的 10 行预览)。`edit` 展开时使用 Pi **官方渲染**(它没有预览态)。全局 `Ctrl+O` 对这三个工具均无效。
|
|
43
45
|
|
|
@@ -89,6 +91,15 @@ Pi 会在终端支持时协商 [Kitty 键盘协议](https://sw.kovidgoyal.net/ki
|
|
|
89
91
|
|
|
90
92
|
## 更新日志
|
|
91
93
|
|
|
94
|
+
### 0.2.2
|
|
95
|
+
|
|
96
|
+
- 按终端宽度缓存 bash 折叠输出,降低多轮工具调用场景下 TUI 重绘开销
|
|
97
|
+
|
|
98
|
+
### 0.2.1
|
|
99
|
+
|
|
100
|
+
- 支持在 Pi fullscreen 模式下点击单个 bash/edit/write 工具块,在紧凑摘要和完整渲染之间切换
|
|
101
|
+
- 保留工具级快捷键;快捷键会统一设置同类工具块
|
|
102
|
+
|
|
92
103
|
### 0.2.0 - 2026-09-05
|
|
93
104
|
|
|
94
105
|
- 默认三工具全折叠(edit/write 原默认展开)
|
package/package.json
CHANGED
package/tidy-tools.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Overrides the TUI rendering of bash, edit and write while delegating
|
|
5
5
|
* execution to the official implementations:
|
|
6
6
|
* - Collapsed: show a small summary with an ellipsis for long content.
|
|
7
|
-
* - Expanded:
|
|
8
|
-
*
|
|
7
|
+
* - Expanded: click an individual tool block in fullscreen mode, or use
|
|
8
|
+
* Ctrl+Alt+E (edit), Ctrl+Alt+W (write), Ctrl+Alt+B (bash) for all rows
|
|
9
|
+
* of that tool, to toggle collapsed and fully expanded rendering.
|
|
9
10
|
* - The global Ctrl+O tool expansion does not affect these three tools.
|
|
10
11
|
* - Modes: compact (default, all three collapsed) and markdown
|
|
11
12
|
* (`/tidy-markdown` toggles; edit/write on Markdown files expand,
|
|
@@ -26,6 +27,7 @@ import type { EditToolDetails } from "@earendil-works/pi-coding-agent";
|
|
|
26
27
|
import {
|
|
27
28
|
Box,
|
|
28
29
|
Container,
|
|
30
|
+
MouseRegion,
|
|
29
31
|
Text,
|
|
30
32
|
type Component,
|
|
31
33
|
sliceByColumn,
|
|
@@ -131,6 +133,9 @@ class LimitedLinesText implements Component {
|
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
class CollapsedBashOutput implements Component {
|
|
136
|
+
private cachedWidth?: number;
|
|
137
|
+
private cachedLines?: string[];
|
|
138
|
+
|
|
134
139
|
// 首行预览 + 计数后缀合并为 1 行,后缀常显。预览放不下时截断并加省略标记。
|
|
135
140
|
constructor(
|
|
136
141
|
private readonly preview: string,
|
|
@@ -139,6 +144,16 @@ class CollapsedBashOutput implements Component {
|
|
|
139
144
|
) {}
|
|
140
145
|
|
|
141
146
|
render(width: number): string[] {
|
|
147
|
+
if (this.cachedLines !== undefined && this.cachedWidth === width) {
|
|
148
|
+
return this.cachedLines;
|
|
149
|
+
}
|
|
150
|
+
const lines = this.renderUncached(width);
|
|
151
|
+
this.cachedWidth = width;
|
|
152
|
+
this.cachedLines = lines;
|
|
153
|
+
return lines;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private renderUncached(width: number): string[] {
|
|
142
157
|
if (!this.suffix) {
|
|
143
158
|
return truncateRows(
|
|
144
159
|
wrapTextWithAnsi(this.preview, Math.max(1, width)),
|
|
@@ -167,7 +182,10 @@ class CollapsedBashOutput implements Component {
|
|
|
167
182
|
return [visibleWidth(row) > width ? sliceByColumn(row, 0, width, true) : row];
|
|
168
183
|
}
|
|
169
184
|
|
|
170
|
-
invalidate(): void {
|
|
185
|
+
invalidate(): void {
|
|
186
|
+
this.cachedWidth = undefined;
|
|
187
|
+
this.cachedLines = undefined;
|
|
188
|
+
}
|
|
171
189
|
}
|
|
172
190
|
|
|
173
191
|
class CollapsedToolShell extends Box {
|
|
@@ -216,6 +234,9 @@ function getCollapsedBackground(isPartial: boolean, isError: boolean): string {
|
|
|
216
234
|
let markdownMode = false;
|
|
217
235
|
// 手动钉选:Ctrl+Alt+? 或 /tidy-<tool> 设置,优先于模式默认;切换模式时清空。
|
|
218
236
|
const manualExpanded = new Map<string, boolean>();
|
|
237
|
+
// Per-tool revisions let a mode or tool-wide toggle replace row-local clicks
|
|
238
|
+
// without disturbing clicked rows belonging to the other tools.
|
|
239
|
+
const expansionRevisions = new Map<string, number>();
|
|
219
240
|
|
|
220
241
|
function isMarkdownPath(value: unknown): boolean {
|
|
221
242
|
if (typeof value !== "string") return false;
|
|
@@ -239,8 +260,34 @@ function modeDefaultExpanded(name: string, args: any): boolean {
|
|
|
239
260
|
return false;
|
|
240
261
|
}
|
|
241
262
|
|
|
242
|
-
|
|
243
|
-
|
|
263
|
+
interface RowExpansionState {
|
|
264
|
+
revision: number;
|
|
265
|
+
override?: boolean;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function getRowExpansionState(name: string, context: any): RowExpansionState {
|
|
269
|
+
const revision = expansionRevisions.get(name) ?? 0;
|
|
270
|
+
let state = context.state.tidyExpansion as RowExpansionState | undefined;
|
|
271
|
+
if (!state || state.revision !== revision) {
|
|
272
|
+
state = { revision };
|
|
273
|
+
context.state.tidyExpansion = state;
|
|
274
|
+
}
|
|
275
|
+
return state;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function isToolExpanded(name: string, args: any, context: any): boolean {
|
|
279
|
+
const rowState = getRowExpansionState(name, context);
|
|
280
|
+
return rowState.override ?? manualExpanded.get(name) ?? modeDefaultExpanded(name, args);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function makeToolClickable(component: Component, name: string, args: any, context: any): Component {
|
|
284
|
+
return new MouseRegion(component, (event) => {
|
|
285
|
+
if (event.type !== "click" || event.button !== "left") return undefined;
|
|
286
|
+
const rowState = getRowExpansionState(name, context);
|
|
287
|
+
rowState.override = !isToolExpanded(name, args, context);
|
|
288
|
+
context.invalidate();
|
|
289
|
+
return { handled: true };
|
|
290
|
+
});
|
|
244
291
|
}
|
|
245
292
|
|
|
246
293
|
// Some terminals (Kitty keyboard protocol flag-1 mode) encode Ctrl+letter as
|
|
@@ -286,6 +333,7 @@ function refreshToolRows(ctx: ExtensionContext): void {
|
|
|
286
333
|
|
|
287
334
|
function toggleToolExpanded(ctx: ExtensionContext, name: string): void {
|
|
288
335
|
manualExpanded.set(name, !(manualExpanded.get(name) ?? modeBaseExpanded(name)));
|
|
336
|
+
expansionRevisions.set(name, (expansionRevisions.get(name) ?? 0) + 1);
|
|
289
337
|
refreshToolRows(ctx);
|
|
290
338
|
ctx.ui.notify(`${name}: ${manualExpanded.get(name) ? "expanded" : "collapsed"}`, "info");
|
|
291
339
|
}
|
|
@@ -350,8 +398,8 @@ function registerCollapsibleTool(
|
|
|
350
398
|
|
|
351
399
|
renderCall(args, theme, context) {
|
|
352
400
|
const def = getDef(context.cwd);
|
|
353
|
-
if (isToolExpanded(def.name, args) && def.renderCall) {
|
|
354
|
-
|
|
401
|
+
if (isToolExpanded(def.name, args, context) && def.renderCall) {
|
|
402
|
+
const component = def.renderCall(
|
|
355
403
|
args as never,
|
|
356
404
|
theme as never,
|
|
357
405
|
// One-key full expansion: skip the official preview so
|
|
@@ -362,9 +410,10 @@ function registerCollapsibleTool(
|
|
|
362
410
|
lastComponent: undefined,
|
|
363
411
|
} as never,
|
|
364
412
|
);
|
|
413
|
+
return makeToolClickable(component, def.name, args, context);
|
|
365
414
|
}
|
|
366
415
|
const component = collapsedCall(args, theme);
|
|
367
|
-
if (!usesSelfShell) return component;
|
|
416
|
+
if (!usesSelfShell) return makeToolClickable(component, def.name, args, context);
|
|
368
417
|
|
|
369
418
|
const shell = getCollapsedToolShell(
|
|
370
419
|
context.state,
|
|
@@ -372,21 +421,22 @@ function registerCollapsibleTool(
|
|
|
372
421
|
getCollapsedBackground(context.isPartial, context.isError),
|
|
373
422
|
);
|
|
374
423
|
shell.setCall(component);
|
|
375
|
-
return shell;
|
|
424
|
+
return makeToolClickable(shell, def.name, args, context);
|
|
376
425
|
},
|
|
377
426
|
|
|
378
427
|
renderResult(result, options, theme, context) {
|
|
379
428
|
const def = getDef(context.cwd);
|
|
380
|
-
if (isToolExpanded(def.name, context.args) && def.renderResult) {
|
|
381
|
-
|
|
429
|
+
if (isToolExpanded(def.name, context.args, context) && def.renderResult) {
|
|
430
|
+
const component = def.renderResult(
|
|
382
431
|
result as never,
|
|
383
432
|
{ ...options, ...(forceFullExpansion ? { expanded: true } : {}) } as never,
|
|
384
433
|
theme as never,
|
|
385
434
|
{ ...context, lastComponent: undefined } as never,
|
|
386
435
|
);
|
|
436
|
+
return makeToolClickable(component, def.name, context.args, context);
|
|
387
437
|
}
|
|
388
438
|
const component = collapsedResult(result, options, theme, context);
|
|
389
|
-
if (!usesSelfShell) return component;
|
|
439
|
+
if (!usesSelfShell) return makeToolClickable(component, def.name, context.args, context);
|
|
390
440
|
|
|
391
441
|
const shell = getCollapsedToolShell(
|
|
392
442
|
context.state,
|
|
@@ -436,16 +486,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
436
486
|
},
|
|
437
487
|
|
|
438
488
|
renderCall(args, theme, context) {
|
|
439
|
-
if (isToolExpanded("bash", args)) {
|
|
489
|
+
if (isToolExpanded("bash", args, context)) {
|
|
440
490
|
const def = getBashDef(context.cwd);
|
|
441
491
|
if (def.renderCall) {
|
|
442
|
-
|
|
492
|
+
const component = def.renderCall(
|
|
443
493
|
args as never,
|
|
444
494
|
theme as never,
|
|
445
495
|
// One-key full expansion for bash: skip the official
|
|
446
496
|
// preview so Ctrl+Alt+B shows the complete output.
|
|
447
497
|
{ ...context, expanded: true, lastComponent: undefined } as never,
|
|
448
498
|
);
|
|
499
|
+
return makeToolClickable(component, "bash", args, context);
|
|
449
500
|
}
|
|
450
501
|
}
|
|
451
502
|
|
|
@@ -455,18 +506,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
455
506
|
const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
|
|
456
507
|
const renderedCommand = theme.fg("toolTitle", theme.bold(`$ ${command}`)) + timeoutSuffix;
|
|
457
508
|
|
|
458
|
-
return new LimitedLinesText(
|
|
509
|
+
return makeToolClickable(new LimitedLinesText(
|
|
459
510
|
renderedCommand,
|
|
460
511
|
MAX_COLLAPSED_COMMAND_LINES,
|
|
461
512
|
theme.fg("muted", "..."),
|
|
462
|
-
);
|
|
513
|
+
), "bash", args, context);
|
|
463
514
|
},
|
|
464
515
|
|
|
465
516
|
renderResult(result, options, theme, context) {
|
|
466
|
-
if (isToolExpanded("bash", context.args)) {
|
|
517
|
+
if (isToolExpanded("bash", context.args, context)) {
|
|
467
518
|
const def = getBashDef(context.cwd);
|
|
468
519
|
if (def.renderResult) {
|
|
469
|
-
|
|
520
|
+
const component = def.renderResult(
|
|
470
521
|
result as never,
|
|
471
522
|
// One-key full expansion for bash: skip the official
|
|
472
523
|
// preview so Ctrl+Alt+B shows the complete output.
|
|
@@ -474,9 +525,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
474
525
|
theme as never,
|
|
475
526
|
{ ...context, lastComponent: undefined } as never,
|
|
476
527
|
);
|
|
528
|
+
return makeToolClickable(component, "bash", context.args, context);
|
|
477
529
|
}
|
|
478
530
|
}
|
|
479
531
|
|
|
532
|
+
const clickable = (component: Component) => makeToolClickable(component, "bash", context.args, context);
|
|
480
533
|
const output = expandTabs(errorText(result).trim().replace(/\r/g, ""));
|
|
481
534
|
const allLines = output.split("\n");
|
|
482
535
|
const first = allLines[0] ?? "";
|
|
@@ -487,32 +540,32 @@ export default function (pi: ExtensionAPI) {
|
|
|
487
540
|
// 失败输出同样压成 1 行:红色首行 + 内联计数,与成功态同形。
|
|
488
541
|
if (context.isError) {
|
|
489
542
|
if (!first) {
|
|
490
|
-
return new CollapsedBashOutput(
|
|
543
|
+
return clickable(new CollapsedBashOutput(
|
|
491
544
|
theme.fg("error", "failed"),
|
|
492
545
|
"",
|
|
493
546
|
theme.fg("muted", "..."),
|
|
494
|
-
);
|
|
547
|
+
));
|
|
495
548
|
}
|
|
496
|
-
return new CollapsedBashOutput(
|
|
549
|
+
return clickable(new CollapsedBashOutput(
|
|
497
550
|
theme.fg("error", first),
|
|
498
551
|
moreSuffix,
|
|
499
552
|
theme.fg("muted", "..."),
|
|
500
|
-
);
|
|
553
|
+
));
|
|
501
554
|
}
|
|
502
555
|
// Preview only the first line. Wrapping the full output on every
|
|
503
556
|
// frame is what made the TUI lag with large outputs.
|
|
504
557
|
if (!first) {
|
|
505
|
-
return new CollapsedBashOutput(
|
|
558
|
+
return clickable(new CollapsedBashOutput(
|
|
506
559
|
theme.fg("muted", "(no output)"),
|
|
507
560
|
"",
|
|
508
561
|
theme.fg("muted", "..."),
|
|
509
|
-
);
|
|
562
|
+
));
|
|
510
563
|
}
|
|
511
|
-
return new CollapsedBashOutput(
|
|
564
|
+
return clickable(new CollapsedBashOutput(
|
|
512
565
|
theme.fg("toolOutput", first),
|
|
513
566
|
moreSuffix,
|
|
514
567
|
theme.fg("muted", "..."),
|
|
515
|
-
);
|
|
568
|
+
));
|
|
516
569
|
},
|
|
517
570
|
});
|
|
518
571
|
|
|
@@ -589,6 +642,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
589
642
|
handler: async (_args, ctx) => {
|
|
590
643
|
markdownMode = !markdownMode;
|
|
591
644
|
manualExpanded.clear();
|
|
645
|
+
for (const name of ["bash", "edit", "write"]) {
|
|
646
|
+
expansionRevisions.set(name, (expansionRevisions.get(name) ?? 0) + 1);
|
|
647
|
+
}
|
|
592
648
|
refreshToolRows(ctx);
|
|
593
649
|
ctx.ui.notify(`markdown mode: ${markdownMode ? "on" : "off"}`, "info");
|
|
594
650
|
},
|