@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 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 per-tool manual toggles and re-renders existing rows. The mode is not persisted across restarts.
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 current state on each toggle; press again to switch back.
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
- 切换模式会清空各工具的手动钉选并重渲染已有行。模式不持久化,重启后回到 compact。
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runminton/pi-tidy-tools",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Collapse bash/edit/write into one-line summaries in the pi TUI; expand the official rendering per tool with Ctrl+Alt+B/W/E.",
5
5
  "license": "MIT",
6
6
  "type": "module",
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: Ctrl+Alt+E (edit), Ctrl+Alt+W (write), Ctrl+Alt+B (bash)
8
- * toggle each tool between collapsed and fully expanded rendering.
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
- function isToolExpanded(name: string, args: any): boolean {
243
- return manualExpanded.get(name) ?? modeDefaultExpanded(name, args);
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
- return def.renderCall(
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
- return def.renderResult(
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
- return def.renderCall(
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
- return def.renderResult(
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
  },