pi-shorthand 0.10.0 → 0.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-shorthand",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Pi tool for editing repositories with Bun programs, with first-class support for TypeScript and JavaScript.",
5
5
  "keywords": [
6
6
  "ast-grep",
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "type": "module",
28
28
  "dependencies": {
29
- "shorthand-code": "0.10.0"
29
+ "shorthand-code": "0.12.0"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "@earendil-works/pi-ai": "*",
package/src/display.ts CHANGED
@@ -25,8 +25,7 @@ const ANSI_CUBE_VALUES = [0, 95, 135, 175, 215, 255];
25
25
  const ANSI_GRAY_VALUES = Array.from({ length: 24 }, (_, index) => 8 + index * 10);
26
26
 
27
27
  const OUTPUT_PREVIEW_LINES = 5; // like Pi's bash tool
28
- const INLINE_DIFF_LINES = 40; // a longer diff collapses to a list of its files…
29
- const LISTED_FILES = 8; // …showing this many, then "and N more files"
28
+ const LISTED_FILES = 8; // a diff too tall for the screen lists this many files, then "and N more files"
30
29
  const EXPANDED_DIFF_LINES = 2000; // even expanded, a diff of hundreds of files stops here
31
30
 
32
31
  export function callLine(
@@ -60,7 +59,8 @@ function indent(line: string): string {
60
59
  return ` ${line}`;
61
60
  }
62
61
 
63
- export function resultLines(run: RunResult, expanded: boolean, theme: Theme): string[] {
62
+ /** `collapseDiff` swaps the applied diff for a list of its files; see FitsScreen for when. */
63
+ export function resultLines(run: RunResult, expanded: boolean, theme: Theme, collapseDiff = false): string[] {
64
64
  const applied = run.changes.filter((change) => run.applied.includes(change.path));
65
65
  const notApplied = run.changes.filter(
66
66
  (change) => !run.applied.includes(change.path) && !run.rolledBack.includes(change.path),
@@ -95,7 +95,7 @@ export function resultLines(run: RunResult, expanded: boolean, theme: Theme): st
95
95
  const sections: string[][] = [];
96
96
  const background: ToolBackground =
97
97
  run.exitCode === 0 && run.conflicts.length === 0 && run.rolledBack.length === 0 ? "toolSuccessBg" : "toolErrorBg";
98
- if (applied.length > 0) sections.push(diffLines(applied, expanded, theme, background));
98
+ if (applied.length > 0) sections.push(diffLines(applied, collapseDiff && !expanded, theme, background));
99
99
  if (output && (expanded || !error)) sections.push(outputLines(output, expanded, run.changes.length > 0, theme));
100
100
  if (notApplied.length > 0) sections.push(notAppliedLines(notApplied, expanded, theme, background));
101
101
  for (const section of sections) lines.push("", ...section);
@@ -157,14 +157,12 @@ function outputLines(output: string, expanded: boolean, labelled: boolean, theme
157
157
  return [...label, hint, ...lines.slice(-OUTPUT_PREVIEW_LINES)];
158
158
  }
159
159
 
160
- /** Each file's diff under its name, in Pi's own diff style. A long diff collapses to a list of files. */
161
- function diffLines(changes: FileChange[], expanded: boolean, theme: Theme, background: ToolBackground): string[] {
162
- const files = changes.map((change) => [fileLine(change, theme), ...renderFileDiff(change, theme, background)]);
163
- const total = files.reduce((sum, file) => sum + file.length, 0);
164
- if (!expanded && total > INLINE_DIFF_LINES) {
160
+ /** Each file's diff under its name, in Pi's own diff style, or when collapsed a list of the files. */
161
+ function diffLines(changes: FileChange[], collapse: boolean, theme: Theme, background: ToolBackground): string[] {
162
+ if (collapse) {
165
163
  return [...fileList(changes, theme), theme.fg("muted", `(${keyHint("app.tools.expand", "to see the diff")})`)];
166
164
  }
167
-
165
+ const files = changes.map((change) => [fileLine(change, theme), ...renderFileDiff(change, theme, background)]);
168
166
  const lines = files.flatMap((file, index) => (index === 0 ? file : ["", ...file]));
169
167
  if (lines.length <= EXPANDED_DIFF_LINES) return lines;
170
168
  const more = lines.length - EXPANDED_DIFF_LINES;
@@ -316,7 +314,7 @@ function colorDistance(first: number[], second: number[]): number {
316
314
 
317
315
  function notAppliedLines(changes: FileChange[], expanded: boolean, theme: Theme, background: ToolBackground): string[] {
318
316
  const heading = theme.fg("muted", `Would have changed ${fileCount(changes)} · `) + stats(changes, theme);
319
- if (expanded) return [heading, "", ...diffLines(changes, true, theme, background)];
317
+ if (expanded) return [heading, "", ...diffLines(changes, false, theme, background)];
320
318
  return [heading + theme.fg("muted", ` (${keyHint("app.tools.expand", "to see the diff")})`)];
321
319
  }
322
320
 
package/src/index.ts CHANGED
@@ -7,7 +7,7 @@ import { createRequire } from "node:module";
7
7
  import * as path from "node:path";
8
8
  import { StringEnum } from "@earendil-works/pi-ai";
9
9
  import { type ExtensionAPI, type Theme, truncateHead, truncateTail } from "@earendil-works/pi-coding-agent";
10
- import { Text } from "@earendil-works/pi-tui";
10
+ import { type Component, Text } from "@earendil-works/pi-tui";
11
11
  import { Type } from "typebox";
12
12
  import {
13
13
  DEFAULT_TIMEOUT_SECONDS,
@@ -35,7 +35,7 @@ Common operations:
35
35
  - await refactor.renameFile({ from, to }) moves a TypeScript file and updates module paths that resolve to it.
36
36
  - await refactor.move({ file, symbol, to }) moves a top-level declaration to another file and updates the imports that follow it.
37
37
 
38
- See the shorthand skill for renames, moves and call-site migrations. Read its advanced-refactors.md guide only to extract code, move syntax or use rule objects. The default timeout is two seconds; request more for longer programs. Time spent inside the helpers above does not count toward it, up to 60 extra seconds.`;
38
+ See the shorthand skill for renames, moves and call-site migrations. Read its advanced-refactors.md guide only to extract code, move syntax or use rule objects. The default timeout is five seconds; request more for longer programs. Time spent inside the helpers above does not count toward it, up to 60 extra seconds.`;
39
39
 
40
40
  const GRAPH_DESCRIPTION =
41
41
  "- await graph.query(request | request[]) queries symbols and relationships; graph results can be passed to sg as scope. The graph shows the repository before this program's edits.\n";
@@ -151,7 +151,7 @@ export function renderCodeResult(
151
151
  result: { content: readonly unknown[]; details?: unknown },
152
152
  { expanded, isPartial }: { expanded: boolean; isPartial: boolean },
153
153
  theme: Theme,
154
- ): Text {
154
+ ): Component {
155
155
  if (isPartial) {
156
156
  const progress = (result.details as { progress?: string } | undefined)?.progress;
157
157
  return new Text(theme.fg("muted", progress ? `running… ${progress}` : "running…"), 0, 0);
@@ -170,5 +170,32 @@ export function renderCodeResult(
170
170
  }
171
171
  const run = result.details as RunResult | undefined;
172
172
  if (!run) return new Text(theme.fg("error", unstructuredResultText(result.content)), 0, 0);
173
- return new Text(resultLines(run, expanded, theme).join("\n"), 0, 0);
173
+ const full = new Text(resultLines(run, expanded, theme).join("\n"), 0, 0);
174
+ if (expanded) return full;
175
+ return new FitsScreen(full, new Text(resultLines(run, false, theme, true).join("\n"), 0, 0));
176
+ }
177
+
178
+ /** Rows Pi keeps for itself below a tool result: the editor, its borders and the footer. */
179
+ const PI_CHROME_ROWS = 8;
180
+
181
+ /**
182
+ * Shows the whole result while it fits on screen once wrapped, like Pi's edit tool, and otherwise the version
183
+ * with the diff collapsed to a list of files. Measured on each render, so resizing the terminal is followed.
184
+ */
185
+ export class FitsScreen implements Component {
186
+ constructor(
187
+ private readonly full: Component,
188
+ private readonly collapsed: Component,
189
+ private readonly rows = () => process.stdout.rows || 24,
190
+ ) {}
191
+
192
+ render(width: number): string[] {
193
+ const lines = this.full.render(width);
194
+ return lines.length <= this.rows() - PI_CHROME_ROWS ? lines : this.collapsed.render(width);
195
+ }
196
+
197
+ invalidate(): void {
198
+ this.full.invalidate();
199
+ this.collapsed.invalidate();
200
+ }
174
201
  }