@heyhuynhgiabuu/pi-pretty 0.6.29 → 0.6.31

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/src/render.ts CHANGED
@@ -200,7 +200,16 @@ export function preserveBoxBackground(ansi: string): string {
200
200
  });
201
201
  }
202
202
 
203
- export function fillToolBackground(text: string, bg = BG_BASE, width?: number): string {
203
+ /** Left indent for self-rendered rows: host `ctx.outputPad` spaces, falling back to 1 on older/invalid hosts. */
204
+ export function toolIndent(ctx?: Pick<RenderContext, "outputPad">): string {
205
+ const pad = ctx?.outputPad;
206
+ // Reject unusable values, including pads wider than the terminal (avoids giant allocations).
207
+ return typeof pad === "number" && Number.isInteger(pad) && pad >= 0 && pad <= termWidth()
208
+ ? " ".repeat(pad)
209
+ : TOOL_RESULT_INDENT;
210
+ }
211
+
212
+ export function fillToolBackground(text: string, bg = BG_BASE, width?: number, indent = TOOL_RESULT_INDENT): string {
204
213
  return text
205
214
  .split("\n")
206
215
  .map((line) => {
@@ -209,7 +218,8 @@ export function fillToolBackground(text: string, bg = BG_BASE, width?: number):
209
218
  return bg ? bg + stripped : stripped;
210
219
  }
211
220
  const plainLead = line.replace(ANSI_CAPTURE_RE, "");
212
- const skipPad = line.startsWith(TOOL_RESULT_INDENT) || plainLead.startsWith(TOOL_RESULT_INDENT);
221
+ // Indented rows keep their natural width; with no indent every row is padded.
222
+ const skipPad = indent !== "" && (line.startsWith(indent) || plainLead.startsWith(indent));
213
223
  const fitted = truncateToWidth(line, width, "", !skipPad);
214
224
  const stripped = preserveBoxBackground(fitted);
215
225
  return bg ? bg + stripped : stripped;
@@ -218,8 +228,8 @@ export function fillToolBackground(text: string, bg = BG_BASE, width?: number):
218
228
  }
219
229
 
220
230
  /** Add exactly the missing terminal row after a rendered tool body. */
221
- export function fillToolBody(text: string, bg = BG_BASE, width?: number): string {
222
- return fillToolBackground(text.endsWith("\n") ? text : `${text}\n`, bg, width);
231
+ export function fillToolBody(text: string, bg = BG_BASE, width?: number, indent = TOOL_RESULT_INDENT): string {
232
+ return fillToolBackground(text.endsWith("\n") ? text : `${text}\n`, bg, width, indent);
223
233
  }
224
234
 
225
235
  const TOOL_TITLE_STATE_KEY = "__piPrettyToolTitle";
@@ -250,29 +260,44 @@ function lnum(n: number, w: number): string {
250
260
  // Tool metrics line
251
261
  // ---------------------------------------------------------------------------
252
262
 
253
- export function renderToolMetrics(result: AgentToolResult<Record<string, unknown>>): string {
263
+ /** Prefer the host's final duration (0 is valid); fall back to wrapper-recorded metadata. */
264
+ function resolveDurationMs(
265
+ result: AgentToolResult<Record<string, unknown>>,
266
+ ctx?: Pick<RenderContext, "durationMs">,
267
+ ): number | undefined {
268
+ const host = ctx?.durationMs;
269
+ if (typeof host === "number" && Number.isFinite(host) && host >= 0) return host;
270
+ const details = result.details as Record<string, unknown> | undefined;
271
+ return details?.[ELAPSED_KEY] as number | undefined;
272
+ }
273
+
274
+ export function renderToolMetrics(
275
+ result: AgentToolResult<Record<string, unknown>>,
276
+ ctx?: Pick<RenderContext, "durationMs">,
277
+ ): string {
254
278
  const details = result.details as Record<string, unknown> | undefined;
255
- if (!details) return "";
256
- const elapsed = formatElapsedMs(details[ELAPSED_KEY] as number | undefined);
257
- const chars = formatCharCount(details[CHARS_KEY] as number | undefined);
279
+ const elapsed = formatElapsedMs(resolveDurationMs(result, ctx));
280
+ const chars = formatCharCount(details?.[CHARS_KEY] as number | undefined);
258
281
  if (!elapsed && !chars) return "";
259
282
  return `${FG_DIM}· ${[elapsed, chars].filter(Boolean).join(" · ")}${RST}`;
260
283
  }
261
284
 
262
- export function renderToolDuration(result: AgentToolResult<Record<string, unknown>>): string {
263
- const details = result.details as Record<string, unknown> | undefined;
264
- return formatElapsedMs(details?.[ELAPSED_KEY] as number | undefined);
285
+ export function renderToolDuration(
286
+ result: AgentToolResult<Record<string, unknown>>,
287
+ ctx?: Pick<RenderContext, "durationMs">,
288
+ ): string {
289
+ return formatElapsedMs(resolveDurationMs(result, ctx));
265
290
  }
266
291
 
267
292
  // ---------------------------------------------------------------------------
268
293
  // Error renderer
269
294
  // ---------------------------------------------------------------------------
270
295
 
271
- export function renderToolError(error: string, theme: ThemeLike): string {
296
+ export function renderToolError(error: string, theme: ThemeLike, indent = TOOL_RESULT_INDENT): string {
272
297
  const body = compactErrorLines(error)
273
- .map((line) => `${TOOL_RESULT_INDENT}${line ? theme.fg("error", line) : ""}`)
298
+ .map((line) => `${indent}${line ? theme.fg("error", line) : ""}`)
274
299
  .join("\n");
275
- return fillToolBody(body, BG_ERROR);
300
+ return fillToolBody(body, BG_ERROR, undefined, indent);
276
301
  }
277
302
 
278
303
  // ---------------------------------------------------------------------------
@@ -305,14 +330,14 @@ export async function renderFileContent(
305
330
  // Bash — colored exit status
306
331
  // ---------------------------------------------------------------------------
307
332
 
308
- export function renderBashOutput(text: string): string {
333
+ export function renderBashOutput(text: string, indent = TOOL_RESULT_INDENT): string {
309
334
  const lines = text.split("\n");
310
335
  const maxShow = MAX_PREVIEW_LINES;
311
336
  const show = lines.slice(0, maxShow);
312
337
  const remaining = lines.length - maxShow;
313
338
 
314
339
  let body = show.join("\n");
315
- if (remaining > 0) body += `\n${TOOL_RESULT_INDENT}${FG_DIM}… ${remaining} more lines${RST}`;
340
+ if (remaining > 0) body += `\n${indent}${FG_DIM}… ${remaining} more lines${RST}`;
316
341
 
317
342
  return body;
318
343
  }
@@ -351,6 +376,7 @@ export function renderTree(text: string, _basePath: string): string {
351
376
  // Find — grouped file list (plain, no tree characters or icons)
352
377
  // ---------------------------------------------------------------------------
353
378
 
379
+ /** File rows keep the structural one-space indent; the tool applies host outer padding. */
354
380
  export function renderFindResults(text: string, theme?: ThemeLike): string {
355
381
  const lines = text.trim().split("\n").filter(Boolean);
356
382
  if (!lines.length) return theme ? theme.fg("dim", "(no matches)") : `${FG_DIM}(no matches)${RST}`;
@@ -392,7 +418,7 @@ export function renderFindResults(text: string, theme?: ThemeLike): string {
392
418
  // Grep — highlighted matches with line numbers
393
419
  // ---------------------------------------------------------------------------
394
420
 
395
- export async function renderGrepResults(text: string, pattern: string): Promise<string> {
421
+ export async function renderGrepResults(text: string, pattern: string, indent = TOOL_RESULT_INDENT): Promise<string> {
396
422
  const lines = normalizeLineEndings(text).split("\n");
397
423
  if (!lines.length || (lines.length === 1 && !lines[0].trim())) return `${FG_DIM}(no matches)${RST}`;
398
424
 
@@ -423,10 +449,10 @@ export async function renderGrepResults(text: string, pattern: string): Promise<
423
449
  const nw = Math.max(3, lineNo.length);
424
450
  let display = content;
425
451
  if (re) display = content.replace(re, `${RST}${FG_YELLOW}\x1b[1m$1${RST}`);
426
- out.push(`${TOOL_RESULT_INDENT}${lnum(Number(lineNo), nw)} ${FG_RULE}│${RST} ${display}${RST}`);
452
+ out.push(`${indent}${lnum(Number(lineNo), nw)} ${FG_RULE}│${RST} ${display}${RST}`);
427
453
  count++;
428
454
  } else if (line.trim() === "--") {
429
- out.push(`${TOOL_RESULT_INDENT}${FG_DIM} ···${RST}`);
455
+ out.push(`${indent}${FG_DIM} ···${RST}`);
430
456
  } else if (line.trim()) {
431
457
  out.push(line);
432
458
  count++;
@@ -445,7 +471,7 @@ export function makeRenderCall(toolName: string) {
445
471
  resolveBaseBackground(theme);
446
472
  const text = ctx.lastComponent ?? new TuiText("", 0, 0);
447
473
  const bg = ctx.isError ? BG_ERROR : undefined;
448
- text.setText(fillToolBackground(`${theme.fg("toolTitle", theme.bold(toolName))}`, bg));
474
+ text.setText(fillToolBackground(`${theme.fg("toolTitle", theme.bold(toolName))}`, bg, undefined, toolIndent(ctx)));
449
475
  return text;
450
476
  };
451
477
  }
@@ -455,26 +481,30 @@ export function makeRenderResult() {
455
481
  resolveBaseBackground(theme);
456
482
  const text = ctx.lastComponent ?? new TuiText("", 0, 0);
457
483
  if (ctx.isError) {
458
- text.setText(renderToolError(getTextContent(result) || "Error", theme));
484
+ text.setText(renderToolError(getTextContent(result) || "Error", theme, toolIndent(ctx)));
459
485
  return text;
460
486
  }
461
487
  const content = getTextContent(result);
462
488
  if (content) {
463
489
  const renderWidth = termWidth();
490
+ const indent = toolIndent(ctx);
464
491
  const lines = content.split("\n");
465
492
  const maxShow = ctx.expanded ? lines.length : Math.min(lines.length, MAX_PREVIEW_LINES);
466
493
  const preview = lines.slice(0, maxShow).join("\n");
467
494
  const more = lines.length > maxShow ? `\n${FG_DIM}... ${lines.length - maxShow} more lines${RST}` : "";
468
- const metrics = renderToolMetrics(result);
495
+ const metrics = renderToolMetrics(result, ctx);
469
496
  text.setText(
470
497
  fillToolBody(
471
- `${TOOL_RESULT_INDENT}${preview}${more}${metrics ? `\n${TOOL_RESULT_INDENT}${metrics}` : ""}`,
498
+ `${indent}${preview}${more}${metrics ? `\n${indent}${metrics}` : ""}`,
472
499
  undefined,
473
500
  renderWidth,
501
+ indent,
474
502
  ),
475
503
  );
476
504
  } else {
477
- text.setText(fillToolBody(`${TOOL_RESULT_INDENT}${theme.fg("dim", "(no text output)")}`));
505
+ text.setText(
506
+ fillToolBody(`${toolIndent(ctx)}${theme.fg("dim", "(no text output)")}`, undefined, undefined, toolIndent(ctx)),
507
+ );
478
508
  }
479
509
  return text;
480
510
  };
package/src/tools/bash.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /* pi-pretty: bash tool -- command execution with styled output. */
2
2
 
3
3
  import type { AgentToolResult, ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
4
- import { resolveBaseBackground, TOOL_RESULT_INDENT, termWidth } from "../config.js";
4
+ import { resolveBaseBackground, termWidth } from "../config.js";
5
5
  import {
6
6
  CHARS_KEY,
7
7
  compactErrorLines,
@@ -17,6 +17,7 @@ import {
17
17
  renderToolDuration,
18
18
  renderToolError,
19
19
  setCollapsedToolTitle,
20
+ toolIndent,
20
21
  } from "../render.js";
21
22
  import { resolveTextCtor } from "../tui-text.js";
22
23
  import type { BashDetails, ComponentLike, RenderCtxLike, SdkToolDef, TextContent, ThemeLike } from "../types.js";
@@ -92,6 +93,7 @@ export function registerBashTool(
92
93
  ],
93
94
  parameters: sdkTool.parameters,
94
95
  constrainedSampling: sdkTool.constrainedSampling,
96
+ outputSchema: sdkTool.outputSchema,
95
97
  renderShell: "self",
96
98
 
97
99
  execute: wrapExecuteWithMetrics(async (tid, params, sig, upd, ctx: ExtensionContext) => {
@@ -105,6 +107,7 @@ export function registerBashTool(
105
107
  resolveBaseBackground(theme);
106
108
  const text = ctx.lastComponent ?? new TC("", 0, 0);
107
109
  const t = typeof args.timeout === "number" ? ` ${theme.fg("muted", `(timeout ${args.timeout}s)`)}` : "";
110
+ const ind = toolIndent(ctx);
108
111
  const tw = termWidth() || 80;
109
112
  const rawCmd = String(args.command ?? "");
110
113
  const headerBudget = ctx.expanded ? tw : Math.max(8, tw - 20);
@@ -116,11 +119,7 @@ export function registerBashTool(
116
119
  : rawCmd;
117
120
  const commandLabel = theme.fg(ctx.isError ? "error" : "toolTitle", theme.bold(`$ ${cmd}`));
118
121
  const renderTitle = (suffix = ""): string =>
119
- fillToolBackground(
120
- `\n${TOOL_RESULT_INDENT}${commandLabel}${t}${suffix}\n`,
121
- undefined,
122
- ctx.expanded ? undefined : tw,
123
- );
122
+ fillToolBackground(`\n${ind}${commandLabel}${t}${suffix}\n`, undefined, ctx.expanded ? undefined : tw, ind);
124
123
  rememberToolTitle(ctx, text, renderTitle);
125
124
  text.setText(renderTitle());
126
125
  return text;
@@ -153,26 +152,29 @@ export function registerBashTool(
153
152
  const cleaned = stripBashExitStatusLine(d.text);
154
153
  const output = isErr ? compactErrorLines(cleaned).join("\n") : cleaned;
155
154
  const lineCount = output.split("\n").length;
156
- const info = [
157
- `${lineCount} lines`,
158
- renderToolDuration(displayResult),
159
- rejectedMetrics ? formatCharCount(rejectedMetrics.chars) : "",
160
- !ctx.expanded ? "ctrl+o to expand" : "",
161
- ]
162
- .filter(Boolean)
163
- .map((part) => theme.fg("dim", part))
164
- .join(theme.fg("dim", " · "));
165
- const header = `${TOOL_RESULT_INDENT}${info}`;
155
+ const buildInfo = (): string =>
156
+ [
157
+ `${lineCount} lines`,
158
+ renderToolDuration(displayResult, ctx),
159
+ rejectedMetrics ? formatCharCount(rejectedMetrics.chars) : "",
160
+ !ctx.expanded ? "ctrl+o to expand" : "",
161
+ ]
162
+ .filter(Boolean)
163
+ .map((part) => theme.fg("dim", part))
164
+ .join(theme.fg("dim", " · "));
165
+ const info = buildInfo();
166
166
  const rw = termWidth();
167
167
 
168
168
  if (setCollapsedToolTitle(ctx, text, ` ${info}`)) return text;
169
169
 
170
170
  const renderFn = (w: number) => {
171
- if (!ctx.expanded) return fillToolBody(header, undefined, w);
172
- if (!output.trim()) return fillToolBody(header, undefined, w);
171
+ const ind = toolIndent(ctx);
172
+ const header = `${ind}${buildInfo()}`;
173
+ if (!ctx.expanded) return fillToolBody(header, undefined, w, ind);
174
+ if (!output.trim()) return fillToolBody(header, undefined, w, ind);
173
175
  const show = output.split("\n");
174
- const out = [header, "", ...show.map((line: string) => `${TOOL_RESULT_INDENT}${line}`)];
175
- return fillToolBody(out.join("\n"), undefined, w);
176
+ const out = [header, "", ...show.map((line: string) => `${ind}${line}`)];
177
+ return fillToolBody(out.join("\n"), undefined, w, ind);
176
178
  };
177
179
 
178
180
  text.setText(renderFn(rw));
@@ -183,7 +185,7 @@ export function registerBashTool(
183
185
  let key: string | undefined;
184
186
  (text as unknown as Record<string, unknown>).render = (w: number) => {
185
187
  const width = Math.max(1, Math.floor(w || termWidth()));
186
- const k = `bash:${ctx.expanded ? "1" : "0"}:${width}:${d.exitCode ?? "killed"}:${output.length}:${renderToolDuration(displayResult)}`;
188
+ const k = `bash:${ctx.expanded ? "1" : "0"}:${width}:${d.exitCode ?? "killed"}:${output.length}:${renderToolDuration(displayResult, ctx)}:${toolIndent(ctx).length}`;
187
189
  if (key !== k) {
188
190
  text.setText(renderFn(width));
189
191
  key = k;
@@ -195,13 +197,17 @@ export function registerBashTool(
195
197
  }
196
198
 
197
199
  if (ctx.isError) {
198
- text.setText(renderToolError(tc || "Error", theme));
200
+ text.setText(renderToolError(tc || "Error", theme, toolIndent(ctx)));
199
201
  return text;
200
202
  }
201
203
  const fc = displayResult.content?.[0];
204
+ const ind = toolIndent(ctx);
202
205
  text.setText(
203
206
  fillToolBody(
204
- `${TOOL_RESULT_INDENT}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "done")}`,
207
+ `${ind}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "done")}`,
208
+ undefined,
209
+ undefined,
210
+ ind,
205
211
  ),
206
212
  );
207
213
  return text;
package/src/tools/find.ts CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { isAbsolute, relative } from "node:path";
4
4
  import type { AgentToolResult, ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
5
- import { BG_ERROR, FG_DIM, RST, resolveBaseBackground, TOOL_RESULT_INDENT } from "../config.js";
5
+ import { BG_ERROR, FG_DIM, RST, resolveBaseBackground } from "../config.js";
6
6
  import { isLikelyGlobPattern, normalizeFindGlobPattern } from "../find-glob.js";
7
7
  import { shortPath } from "../helpers.js";
8
8
  import { NOTICE_PARTIAL_FILE_INDEX } from "../notices.js";
@@ -14,6 +14,7 @@ import {
14
14
  renderToolDuration,
15
15
  renderToolError,
16
16
  setCollapsedToolTitle,
17
+ toolIndent,
17
18
  } from "../render.js";
18
19
  import { resolveTextCtor } from "../tui-text.js";
19
20
  import type { FffServiceWithCursor, FindDetails, RenderCtxLike, SdkToolDef, TextContent, ThemeLike } from "../types.js";
@@ -171,8 +172,9 @@ export function registerFindTool(
171
172
  const pathPart = theme.fg("toolOutput", pathArg);
172
173
  const limitPart = limit !== undefined && limit !== null ? theme.fg("dim", ` limit ${limit}`) : "";
173
174
  const out = `${findLabel} ${patternPart}${inPart}${pathPart}${limitPart}`;
175
+ const ind = toolIndent(ctx);
174
176
  const renderTitle = (suffix = ""): string =>
175
- fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}${suffix}\n`, ctx.isError ? BG_ERROR : undefined);
177
+ fillToolBackground(`\n${ind}${out}${suffix}\n`, ctx.isError ? BG_ERROR : undefined, undefined, ind);
176
178
  rememberToolTitle(ctx, text, renderTitle);
177
179
  text.setText(renderTitle());
178
180
  return text;
@@ -182,8 +184,9 @@ export function registerFindTool(
182
184
  resolveBaseBackground(theme);
183
185
  const r = result;
184
186
  const text = (ctx as RenderCtxLike).lastComponent ?? new TC("", 0, 0);
187
+ const ind = toolIndent(ctx);
185
188
  if (ctx.isError) {
186
- text.setText(renderToolError(getText(r) || "Error", theme));
189
+ text.setText(renderToolError(getText(r) || "Error", theme, ind));
187
190
  return text;
188
191
  }
189
192
  const d = r.details as FindDetails | undefined;
@@ -191,36 +194,37 @@ export function registerFindTool(
191
194
  if (!d.text.trim()) {
192
195
  const notice = d.notices?.length ? ` ${theme.fg("warning", `[${d.notices.join(". ")}]`)}` : "";
193
196
  if (setCollapsedToolTitle(ctx, text, ` ${FG_DIM}0 files${RST}${notice}`)) return text;
194
- const noticeStr = d.notices?.length
195
- ? `\n${TOOL_RESULT_INDENT}${theme.fg("warning", `[${d.notices.join(". ")}]`)}`
196
- : "";
197
- text.setText(fillToolBody(`${TOOL_RESULT_INDENT}${theme.fg("dim", "0 files")}${noticeStr}`));
197
+ const noticeStr = d.notices?.length ? `\n${ind}${theme.fg("warning", `[${d.notices.join(". ")}]`)}` : "";
198
+ text.setText(fillToolBody(`${ind}${theme.fg("dim", "0 files")}${noticeStr}`, undefined, undefined, ind));
198
199
  return text;
199
200
  }
200
201
  if (!ctx.expanded) {
201
- const duration = renderToolDuration(r);
202
+ const duration = renderToolDuration(r, ctx);
202
203
  const summary = `${FG_DIM}${d.matchCount} files — ctrl+o to expand${RST}${duration ? `${FG_DIM}· ${duration}${RST}` : ""}`;
203
204
  if (setCollapsedToolTitle(ctx, text, ` ${summary}`)) return text;
204
- text.setText(fillToolBody(`${TOOL_RESULT_INDENT}${summary}`));
205
+ text.setText(fillToolBody(`${ind}${summary}`, undefined, undefined, ind));
205
206
  return text;
206
207
  }
207
208
  const rendered = renderFindResults(d.text, theme)
208
209
  .split("\n")
209
- .map((l) => `${TOOL_RESULT_INDENT}${l}`)
210
+ .map((l) => `${ind}${l}`)
210
211
  .join("\n");
211
- const noticeStr = d.notices?.length
212
- ? `\n${TOOL_RESULT_INDENT}${theme.fg("warning", `[${d.notices.join(". ")}]`)}`
213
- : "";
214
- const duration = renderToolDuration(r);
212
+ const noticeStr = d.notices?.length ? `\n${ind}${theme.fg("warning", `[${d.notices.join(". ")}]`)}` : "";
213
+ const duration = renderToolDuration(r, ctx);
215
214
  text.setText(
216
215
  fillToolBody(
217
- `${TOOL_RESULT_INDENT}${theme.fg("dim", `${d.matchCount} files`)}${duration ? `${FG_DIM}· ${duration}${RST}` : ""}\n${rendered}${noticeStr}`,
216
+ `${ind}${theme.fg("dim", `${d.matchCount} files`)}${duration ? `${FG_DIM}· ${duration}${RST}` : ""}\n${rendered}${noticeStr}`,
217
+ undefined,
218
+ undefined,
219
+ ind,
218
220
  ),
219
221
  );
220
222
  return text;
221
223
  }
222
224
  const fc = r.content?.[0] as TextContent | undefined;
223
- text.setText(fillToolBody(`${TOOL_RESULT_INDENT}${theme.fg("dim", fc?.text?.slice(0, 120) ?? "0 files")}`));
225
+ text.setText(
226
+ fillToolBody(`${ind}${theme.fg("dim", fc?.text?.slice(0, 120) ?? "0 files")}`, undefined, undefined, ind),
227
+ );
224
228
  return text;
225
229
  },
226
230
  } as unknown as ToolDefinition);
package/src/tools/grep.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /* pi-pretty: grep tool -- FFF-backed text search with SDK fallback. */
2
2
 
3
3
  import type { AgentToolResult, ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
4
- import { BG_ERROR, FG_DIM, RST, resolveBaseBackground, TOOL_RESULT_INDENT } from "../config.js";
4
+ import { BG_ERROR, FG_DIM, RST, resolveBaseBackground } from "../config.js";
5
5
  import { fffFormatGrepText } from "../fff-helpers.js";
6
6
  import { normalizeLineEndings, shortPath } from "../helpers.js";
7
7
  import { NOTICE_PARTIAL_FILE_INDEX } from "../notices.js";
@@ -11,6 +11,7 @@ import {
11
11
  rememberToolTitle,
12
12
  renderToolError,
13
13
  setCollapsedToolTitle,
14
+ toolIndent,
14
15
  } from "../render.js";
15
16
  import { resolveTextCtor } from "../tui-text.js";
16
17
  import type { FffServiceWithCursor, GrepDetails, RenderCtxLike, SdkToolDef, TextContent, ThemeLike } from "../types.js";
@@ -122,8 +123,9 @@ export function registerGrepTool(
122
123
  if (limit !== undefined && limit !== null) out += theme.fg("dim", ` limit ${limit}`);
123
124
  if (literal) out += theme.fg("dim", ` (literal)`);
124
125
  if (caseInsensitive) out += theme.fg("dim", ` (case-insensitive)`);
126
+ const ind = toolIndent(ctx);
125
127
  const renderTitle = (suffix = ""): string =>
126
- fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}${suffix}\n`, ctx.isError ? BG_ERROR : undefined);
128
+ fillToolBackground(`\n${ind}${out}${suffix}\n`, ctx.isError ? BG_ERROR : undefined, undefined, ind);
127
129
  rememberToolTitle(ctx, text, renderTitle);
128
130
  text.setText(renderTitle());
129
131
  return text;
@@ -132,6 +134,7 @@ export function registerGrepTool(
132
134
  renderResult(result: Result, _opt: unknown, theme: ThemeLike, ctx: RenderCtxLike) {
133
135
  resolveBaseBackground(theme);
134
136
  const text = ctx.lastComponent ?? new T("", 0, 0);
137
+ const ind = toolIndent(ctx);
135
138
  if (ctx.isError) {
136
139
  text.setText(
137
140
  renderToolError(
@@ -140,6 +143,7 @@ export function registerGrepTool(
140
143
  .map((c) => c.text)
141
144
  .join("\n") || "Error",
142
145
  theme,
146
+ ind,
143
147
  ),
144
148
  );
145
149
  return text;
@@ -150,7 +154,7 @@ export function registerGrepTool(
150
154
  if (!ctx.expanded) {
151
155
  const summary = `${FG_DIM}${lines.length} lines — ctrl+o to expand${RST}`;
152
156
  if (setCollapsedToolTitle(ctx, text, ` ${summary}`)) return text;
153
- text.setText(fillToolBody(`${TOOL_RESULT_INDENT}${summary}`, ctx.isError ? BG_ERROR : undefined));
157
+ text.setText(fillToolBody(`${ind}${summary}`, ctx.isError ? BG_ERROR : undefined, undefined, ind));
154
158
  return text;
155
159
  }
156
160
  const maxShow = lines.length;
@@ -164,14 +168,14 @@ export function registerGrepTool(
164
168
  if (remaining > 0) {
165
169
  out.push(theme.fg("muted", `… (${remaining} more ${remaining === 1 ? "line" : "lines"}, to expand)`));
166
170
  }
167
- const body = out.map((l) => `${TOOL_RESULT_INDENT}${l}`).join("\n");
168
- text.setText(fillToolBody(body, ctx.isError ? BG_ERROR : undefined));
171
+ const body = out.map((l) => `${ind}${l}`).join("\n");
172
+ text.setText(fillToolBody(body, ctx.isError ? BG_ERROR : undefined, undefined, ind));
169
173
  return text;
170
174
  }
171
175
  const fc = result.content?.[0];
172
176
  const fallback = fc && "text" in fc ? String(fc.text).slice(0, 120) : "no matches";
173
177
  text.setText(
174
- fillToolBody(`${TOOL_RESULT_INDENT}${theme.fg("dim", fallback)}`, ctx.isError ? BG_ERROR : undefined),
178
+ fillToolBody(`${ind}${theme.fg("dim", fallback)}`, ctx.isError ? BG_ERROR : undefined, undefined, ind),
175
179
  );
176
180
  return text;
177
181
  },
package/src/tools/ls.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /* pi-pretty: ls tool -- directory listing with styled output. */
2
2
 
3
3
  import type { AgentToolResult, ExtensionAPI, ExtensionContext, ToolDefinition } from "@earendil-works/pi-coding-agent";
4
- import { BG_ERROR, FG_DIM, RST, resolveBaseBackground, TOOL_RESULT_INDENT } from "../config.js";
4
+ import { BG_ERROR, FG_DIM, RST, resolveBaseBackground } from "../config.js";
5
5
  import { shortPath } from "../helpers.js";
6
6
  import {
7
7
  fillToolBackground,
@@ -11,6 +11,7 @@ import {
11
11
  renderToolMetrics,
12
12
  renderTree,
13
13
  setCollapsedToolTitle,
14
+ toolIndent,
14
15
  } from "../render.js";
15
16
  import { resolveTextCtor } from "../tui-text.js";
16
17
  import type { LsDetails, RenderCtxLike, SdkToolDef, TextContent, ThemeLike } from "../types.js";
@@ -60,8 +61,9 @@ export function registerLsTool(
60
61
  let out = theme.fg("toolTitle", theme.bold("ls"));
61
62
  if (path) out += ` ${theme.fg("accent", path)}`;
62
63
  if (limit !== undefined && limit !== null) out += theme.fg("toolOutput", ` (limit ${limit})`);
64
+ const ind = toolIndent(ctx);
63
65
  const renderTitle = (suffix = ""): string =>
64
- fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}${suffix}\n`, ctx.isError ? BG_ERROR : undefined);
66
+ fillToolBackground(`\n${ind}${out}${suffix}\n`, ctx.isError ? BG_ERROR : undefined, undefined, ind);
65
67
  rememberToolTitle(ctx, text, renderTitle);
66
68
  text.setText(renderTitle());
67
69
  return text;
@@ -71,25 +73,29 @@ export function registerLsTool(
71
73
  resolveBaseBackground(theme);
72
74
 
73
75
  const text = ctx.lastComponent ?? new TC("", 0, 0);
76
+ const ind = toolIndent(ctx);
74
77
  if (ctx.isError) {
75
- text.setText(renderToolError(getText(result) || "Error", theme));
78
+ text.setText(renderToolError(getText(result) || "Error", theme, ind));
76
79
  return text;
77
80
  }
78
81
  const d = result.details as LsDetails | undefined;
79
82
  if (d?._type === "lsResult" && d.text) {
80
83
  if (!ctx.expanded) {
81
- const summary = `${FG_DIM}${d.entryCount} entries — ctrl+o to expand${RST}${renderToolMetrics(result)}`;
84
+ const summary = `${FG_DIM}${d.entryCount} entries — ctrl+o to expand${RST}${renderToolMetrics(result, ctx)}`;
82
85
  if (setCollapsedToolTitle(ctx, text, ` ${summary}`)) return text;
83
- text.setText(fillToolBody(`${TOOL_RESULT_INDENT}${summary}`));
86
+ text.setText(fillToolBody(`${ind}${summary}`, undefined, undefined, ind));
84
87
  return text;
85
88
  }
86
89
  const rendered = renderTree(d.text, d.path)
87
90
  .split("\n")
88
- .map((l) => `${TOOL_RESULT_INDENT}${l}`)
91
+ .map((l) => `${ind}${l}`)
89
92
  .join("\n");
90
93
  text.setText(
91
94
  fillToolBody(
92
- `${TOOL_RESULT_INDENT}${FG_DIM}${d.entryCount} entries${RST}${renderToolMetrics(result)}\n${rendered}`,
95
+ `${ind}${FG_DIM}${d.entryCount} entries${RST}${renderToolMetrics(result, ctx)}\n${rendered}`,
96
+ undefined,
97
+ undefined,
98
+ ind,
93
99
  ),
94
100
  );
95
101
  return text;
@@ -97,7 +103,10 @@ export function registerLsTool(
97
103
  const fc = result.content?.[0];
98
104
  text.setText(
99
105
  fillToolBody(
100
- `${TOOL_RESULT_INDENT}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "done")}`,
106
+ `${ind}${theme.fg("dim", fc && "text" in fc ? String(fc.text).slice(0, 120) : "done")}`,
107
+ undefined,
108
+ undefined,
109
+ ind,
101
110
  ),
102
111
  );
103
112
  return text;