@quandev104/pi-style 0.1.1 → 0.1.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.
@@ -1491,6 +1491,7 @@ function stripAnsi(value) {
1491
1491
  break;
1492
1492
  }
1493
1493
  }
1494
+ if (i + 1 < value.length && value.charCodeAt(i + 1) === 7) i++;
1494
1495
  }
1495
1496
  }
1496
1497
  return output;
@@ -2059,7 +2060,7 @@ function renderSystemContextPanel(resolved, items, minTotalWidth) {
2059
2060
  "\u2500".repeat(pathWidth)
2060
2061
  )}${divider}${resolved.apply("dim", "\u2500".repeat(metricWidth))}`;
2061
2062
  const lines = [
2062
- renderPanelBorder(resolved, "\u250C", "\u2510", panelWidth),
2063
+ renderPanelBorder(resolved, "\u256D", "\u256E", panelWidth),
2063
2064
  renderPanelLine(resolved, titleLine, panelWidth),
2064
2065
  renderPanelLine(resolved, header, panelWidth),
2065
2066
  renderPanelLine(resolved, separator, panelWidth)
@@ -2081,7 +2082,7 @@ function renderSystemContextPanel(resolved, items, minTotalWidth) {
2081
2082
  )
2082
2083
  );
2083
2084
  }
2084
- lines.push(renderPanelBorder(resolved, "\u2514", "\u2518", panelWidth));
2085
+ lines.push(renderPanelBorder(resolved, "\u2570", "\u256F", panelWidth));
2085
2086
  return lines;
2086
2087
  }
2087
2088
  function groupToolDetails(tools) {
@@ -2130,7 +2131,7 @@ function renderToolsPanel(resolved, tools, minTotalWidth) {
2130
2131
  "\u2500".repeat(countWidth)
2131
2132
  )}${divider}${resolved.apply("dim", "\u2500".repeat(toolsWidth))}`;
2132
2133
  const lines = [
2133
- renderPanelBorder(resolved, "\u250C", "\u2510", panelWidth),
2134
+ renderPanelBorder(resolved, "\u256D", "\u256E", panelWidth),
2134
2135
  renderPanelLine(resolved, titleLine, panelWidth),
2135
2136
  renderPanelLine(resolved, header, panelWidth),
2136
2137
  renderPanelLine(resolved, separator, panelWidth)
@@ -2152,7 +2153,7 @@ function renderToolsPanel(resolved, tools, minTotalWidth) {
2152
2153
  )
2153
2154
  );
2154
2155
  }
2155
- lines.push(renderPanelBorder(resolved, "\u2514", "\u2518", panelWidth));
2156
+ lines.push(renderPanelBorder(resolved, "\u2570", "\u256F", panelWidth));
2156
2157
  return lines;
2157
2158
  }
2158
2159
  var STARTUP_INDENT = " ";
@@ -3754,6 +3755,12 @@ import { relative, resolve as resolve2 } from "path";
3754
3755
 
3755
3756
  // extension-src/pi-style/shared/elapsed.ts
3756
3757
  var ELAPSED_KEY = "__elapsedMs";
3758
+ function formatElapsedMs(ms) {
3759
+ if (typeof ms !== "number" || !Number.isFinite(ms)) return "";
3760
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
3761
+ const s = ms / 1e3;
3762
+ return s < 10 ? `${s.toFixed(1)}s` : `${Math.round(s)}s`;
3763
+ }
3757
3764
  function getElapsedMs(result) {
3758
3765
  if (!result || typeof result.details !== "object" || result.details === null) return void 0;
3759
3766
  const elapsed = result.details[ELAPSED_KEY];
@@ -4114,16 +4121,19 @@ function formatCompactCount(value) {
4114
4121
  return `${Math.round(value / 1e6)}M`;
4115
4122
  }
4116
4123
  function formatBoxedWords(text) {
4117
- return `\u270E ~${formatCompactCount(countWords2(text))} words`;
4124
+ return `~${formatCompactCount(countWords2(text))} words`;
4118
4125
  }
4119
4126
  var BOX_HORIZONTAL = "\u2500";
4120
4127
  var BOX_VERTICAL = "\u2502";
4121
4128
  var BOX_SIDE_PADDING = 2;
4122
4129
  var BOX_MIN_WIDTH = 12;
4123
- var COMPACT_TOOL_NAME_WIDTH = safeVisibleWidth("Search");
4124
- var COMPACT_FOOTER_ELAPSED_WIDTH = 8;
4125
- var COMPACT_FOOTER_EXTRA_WIDTH = 8;
4126
- var COMPACT_FOOTER_WORDS_WIDTH = safeVisibleWidth("\u270E ~1.2k words");
4130
+ var BOX_ROUND_TOP_LEFT = "\u256D";
4131
+ var BOX_ROUND_TOP_RIGHT = "\u256E";
4132
+ var BOX_ROUND_BOTTOM_LEFT = "\u2570";
4133
+ var BOX_ROUND_BOTTOM_RIGHT = "\u256F";
4134
+ var BOX_DIVIDER_LEFT = "\u251C";
4135
+ var BOX_DIVIDER_RIGHT = "\u2524";
4136
+ var BOX_LABELED_RIGHT_DASH_MIN = 3;
4127
4137
  function boxWidth(width) {
4128
4138
  return Math.max(BOX_MIN_WIDTH, width);
4129
4139
  }
@@ -4216,19 +4226,8 @@ function formatBoxedStatusIcon(theme, isError) {
4216
4226
  }
4217
4227
  function formatBoxedToolTitle(theme, name, isError) {
4218
4228
  const rawTitle = `\u2794 ${name}`;
4219
- const coloredTitle = `${colorFromExtra(theme, "bashPromptColor", "bashMode", rawTitle)} ${formatBoxedStatusIcon(theme, isError)}`;
4220
- const title = typeof theme?.bold === "function" ? theme.bold(coloredTitle) : coloredTitle;
4221
- return `${title} ${boxText(theme, "|")}`;
4222
- }
4223
- function formatCompactBoxedToolTitle(theme, name, isError) {
4224
- const paddedName = padVisibleRight(name, COMPACT_TOOL_NAME_WIDTH);
4225
- const rawTitle = `\u2794 ${paddedName}`;
4226
- const coloredTitle = `${colorFromExtra(theme, "bashPromptColor", "bashMode", rawTitle)} ${formatBoxedStatusIcon(theme, isError)}`;
4227
- const title = typeof theme?.bold === "function" ? theme.bold(coloredTitle) : coloredTitle;
4228
- return `${title} ${boxText(theme, "|")}`;
4229
- }
4230
- function boxText(theme, text) {
4231
- return `${RESET_INTENSITY}${theme.fg("borderMuted", text)}`;
4229
+ const coloredTitle = isError ? theme.fg("error", `${rawTitle} \u2717`) : `${colorFromExtra(theme, "bashPromptColor", "bashMode", rawTitle)} ${formatBoxedStatusIcon(theme, false)}`;
4230
+ return typeof theme?.bold === "function" ? theme.bold(coloredTitle) : coloredTitle;
4232
4231
  }
4233
4232
  function boxFrameText(theme, text) {
4234
4233
  return `${RESET_INTENSITY}${theme.fg("border", text)}`;
@@ -4238,23 +4237,41 @@ function boxBorder(theme, left, right, width) {
4238
4237
  const innerWidth = renderedWidth - 2;
4239
4238
  return boxFrameText(theme, `${left}${BOX_HORIZONTAL.repeat(innerWidth)}${right}`);
4240
4239
  }
4241
- function padVisibleRight(text, width) {
4242
- return `${text}${" ".repeat(Math.max(0, width - safeVisibleWidth(text)))}`;
4243
- }
4244
- function boxLineWithRight(theme, left, right, width) {
4240
+ function boxLabeledBorder(theme, start, end, leftLabel, rightLabel, width) {
4245
4241
  const renderedWidth = boxWidth(width);
4246
- const contentWidth = boxInnerWidth(renderedWidth);
4247
- const divider = ` ${boxText(theme, "|")} `;
4248
- const dividerWidth = safeVisibleWidth(divider);
4242
+ let left = leftLabel ?? "";
4243
+ const right = rightLabel ?? "";
4244
+ let leftWidth = safeVisibleWidth(left);
4249
4245
  const rightWidth = safeVisibleWidth(right);
4246
+ const leftOverhead = left ? 3 : 0;
4247
+ const rightOverhead = right ? 2 : 0;
4248
+ let rightFill = right ? BOX_LABELED_RIGHT_DASH_MIN : 0;
4249
+ let fill = renderedWidth - start.length - end.length - leftOverhead - leftWidth - rightOverhead - rightWidth - rightFill;
4250
+ if (right && fill < 0) {
4251
+ rightFill = 1;
4252
+ fill = renderedWidth - start.length - end.length - leftOverhead - leftWidth - rightOverhead - rightWidth - rightFill;
4253
+ }
4254
+ if (fill < 0) {
4255
+ const reserved = start.length + end.length + leftOverhead + (right ? rightOverhead + rightWidth + rightFill : 0) + 1;
4256
+ const maxLeft = renderedWidth - reserved;
4257
+ left = maxLeft > 0 ? safeTruncateToWidth(left, maxLeft, "\u2026") : "";
4258
+ leftWidth = safeVisibleWidth(left);
4259
+ fill = renderedWidth - start.length - end.length - (left ? leftWidth + leftOverhead : 0) - (right ? rightOverhead + rightWidth + rightFill : 0);
4260
+ }
4261
+ const parts = [boxFrameText(theme, `${start}${left ? "\u2500 " : ""}`)];
4262
+ if (left) parts.push(left);
4263
+ parts.push(boxFrameText(theme, `${left ? " " : ""}${BOX_HORIZONTAL.repeat(Math.max(0, fill))}`));
4264
+ if (right) {
4265
+ parts.push(boxFrameText(theme, " "), right, boxFrameText(theme, ` ${BOX_HORIZONTAL.repeat(rightFill)}`));
4266
+ }
4267
+ parts.push(boxFrameText(theme, end));
4268
+ return parts.join("");
4269
+ }
4270
+ function boxBlankLine(theme, width) {
4271
+ const renderedWidth = boxWidth(width);
4272
+ const contentWidth = boxInnerWidth(renderedWidth);
4250
4273
  const sidePad = " ".repeat(BOX_SIDE_PADDING);
4251
- if (!right || rightWidth + dividerWidth >= contentWidth) {
4252
- return boxLine(theme, right || left, renderedWidth);
4253
- }
4254
- const maxLeftWidth = Math.max(1, contentWidth - dividerWidth - rightWidth - 1);
4255
- const truncatedLeft = safeTruncateToWidth(left, maxLeftWidth, "\u2026");
4256
- const gap = " ".repeat(Math.max(1, contentWidth - safeVisibleWidth(truncatedLeft) - dividerWidth - rightWidth));
4257
- return `${boxFrameText(theme, BOX_VERTICAL)}${sidePad}${truncatedLeft}${gap}${divider}${right}${sidePad}${boxFrameText(theme, BOX_VERTICAL)}`;
4274
+ return `${boxFrameText(theme, BOX_VERTICAL)}${sidePad}${" ".repeat(contentWidth)}${sidePad}${boxFrameText(theme, BOX_VERTICAL)}`;
4258
4275
  }
4259
4276
  function boxLine(theme, content, width) {
4260
4277
  const renderedWidth = boxWidth(width);
@@ -4269,12 +4286,6 @@ function boxLine(theme, content, width) {
4269
4286
  const fill = " ".repeat(Math.max(0, contentWidth - safeVisibleWidth(truncated)));
4270
4287
  return `${boxFrameText(theme, BOX_VERTICAL)}${sidePad}${truncated}${fill}${sidePad}${boxFrameText(theme, BOX_VERTICAL)}`;
4271
4288
  }
4272
- function boxInsetDivider(theme, width) {
4273
- const renderedWidth = boxWidth(width);
4274
- const lineWidth = boxInnerWidth(renderedWidth);
4275
- const sidePad = " ".repeat(BOX_SIDE_PADDING);
4276
- return `${boxFrameText(theme, BOX_VERTICAL)}${sidePad}${boxText(theme, BOX_HORIZONTAL.repeat(lineWidth))}${sidePad}${boxFrameText(theme, BOX_VERTICAL)}`;
4277
- }
4278
4289
  function boxedWrappedLines(theme, content, width) {
4279
4290
  return safeWrapTextWithAnsi(content, boxInnerWidth(width)).map((line) => boxLine(theme, line, width));
4280
4291
  }
@@ -4327,20 +4338,28 @@ function renderBoxedToolCall(theme, toolName, detailLines, options = {}) {
4327
4338
  render(width) {
4328
4339
  if (cache?.width === width) return cache.lines;
4329
4340
  const title = formatBoxedToolTitle(theme, toolName, options.isError);
4341
+ const headerLabel = options.headerDetail ? `${title} \xB7 ${options.headerDetail}` : title;
4330
4342
  const renderedWidth = boxWidth(width);
4331
4343
  const lines = [
4332
- boxBorder(theme, "\u250C", "\u2510", renderedWidth),
4333
- boxLine(theme, title, renderedWidth),
4334
- boxInsetDivider(theme, renderedWidth),
4344
+ boxLabeledBorder(theme, BOX_ROUND_TOP_LEFT, BOX_ROUND_TOP_RIGHT, headerLabel, void 0, renderedWidth),
4345
+ boxBlankLine(theme, renderedWidth),
4335
4346
  ...detailLines.flatMap((line) => boxedWrappedLines(theme, line, renderedWidth))
4336
4347
  ];
4337
4348
  if (options.isPending) {
4338
4349
  const pendingText = options.pendingText ?? "Waiting for output\u2026";
4339
4350
  lines.push(
4340
- boxInsetDivider(theme, renderedWidth),
4341
- ...boxedWrappedLines(theme, `${theme.fg("muted", "\u2026")} ${theme.fg("dim", pendingText)}`, renderedWidth),
4342
- boxBorder(theme, "\u2514", "\u2518", renderedWidth)
4351
+ boxBlankLine(theme, renderedWidth),
4352
+ boxLabeledBorder(
4353
+ theme,
4354
+ BOX_ROUND_BOTTOM_LEFT,
4355
+ BOX_ROUND_BOTTOM_RIGHT,
4356
+ theme.fg("dim", `\u2026 ${pendingText}`),
4357
+ void 0,
4358
+ renderedWidth
4359
+ )
4343
4360
  );
4361
+ } else {
4362
+ lines.push(boxBlankLine(theme, renderedWidth));
4344
4363
  }
4345
4364
  cache = { width, lines };
4346
4365
  return lines;
@@ -4362,25 +4381,39 @@ function renderCompactBoxedToolCall(theme, toolName, detailLine, options = {}) {
4362
4381
  },
4363
4382
  render(width) {
4364
4383
  const renderedWidth = boxWidth(width);
4365
- const title = `${formatCompactBoxedToolTitle(theme, toolName, options.isError)} ${detailLine}`;
4384
+ const title = formatBoxedToolTitle(theme, toolName, options.isError);
4385
+ const headerLabel = detailLine ? `${title} \xB7 ${detailLine}` : title;
4366
4386
  const compactFooter = typeof options.state?.[COMPACT_FOOTER_KEY] === "string" ? options.state[COMPACT_FOOTER_KEY] : "";
4367
4387
  const _footerIsError = Boolean(options.state?.[COMPACT_FOOTER_ERROR_KEY]);
4368
4388
  const _footerIsPartial = Boolean(options.state?.[COMPACT_FOOTER_PARTIAL_KEY]);
4389
+ const lines = [
4390
+ boxLabeledBorder(theme, BOX_ROUND_TOP_LEFT, BOX_ROUND_TOP_RIGHT, headerLabel, void 0, renderedWidth),
4391
+ boxBlankLine(theme, renderedWidth)
4392
+ ];
4369
4393
  if (compactFooter) {
4370
- return [
4371
- boxBorder(theme, "\u250C", "\u2510", renderedWidth),
4372
- boxLineWithRight(theme, title, compactFooter, renderedWidth),
4373
- boxBorder(theme, "\u2514", "\u2518", renderedWidth)
4374
- ];
4375
- }
4376
- const lines = [boxBorder(theme, "\u250C", "\u2510", renderedWidth), boxLine(theme, title, renderedWidth)];
4377
- if (options.isPending) {
4394
+ lines.push(
4395
+ boxLabeledBorder(
4396
+ theme,
4397
+ BOX_ROUND_BOTTOM_LEFT,
4398
+ BOX_ROUND_BOTTOM_RIGHT,
4399
+ compactFooter,
4400
+ void 0,
4401
+ renderedWidth
4402
+ )
4403
+ );
4404
+ } else if (options.isPending) {
4378
4405
  const pendingText = options.pendingText ?? "Waiting for output\u2026";
4379
4406
  lines.push(
4380
- boxInsetDivider(theme, renderedWidth),
4381
- ...boxedWrappedLines(theme, `${theme.fg("muted", "\u2026")} ${theme.fg("dim", pendingText)}`, renderedWidth),
4382
- boxBorder(theme, "\u2514", "\u2518", renderedWidth)
4407
+ boxLabeledBorder(
4408
+ theme,
4409
+ BOX_ROUND_BOTTOM_LEFT,
4410
+ BOX_ROUND_BOTTOM_RIGHT,
4411
+ theme.fg("dim", `\u2026 ${pendingText}`),
4412
+ void 0,
4413
+ renderedWidth
4414
+ )
4383
4415
  );
4416
+ } else {
4384
4417
  }
4385
4418
  return lines;
4386
4419
  }
@@ -4400,46 +4433,50 @@ function renderBoxedToolResult(theme, body, options = {}) {
4400
4433
  const bodyLines = typeof body === "function" ? body(maxContentWidth) : body.render(maxContentWidth);
4401
4434
  const errorPrefix = options.isError ? [theme.fg("error", "\u2717 Error")] : [];
4402
4435
  const outputLines = bodyLines.length > 0 ? [...errorPrefix, ...bodyLines] : [theme.fg("muted", `\u2205 ${options.emptyText ?? "(no output)"}`)];
4403
- const footerLines = options.footerLines ?? [];
4404
- const renderedFooterLines = footerLines.length > 0 ? [boxInsetDivider(theme, renderedWidth), ...footerLines.map((line) => boxLine(theme, line, renderedWidth))] : [];
4436
+ const footerText = (options.footerLines ?? []).join(" \xB7 ");
4437
+ const dividerText = typeof options.dividerLabel === "function" ? options.dividerLabel(renderedWidth) : options.dividerLabel ?? "Response";
4405
4438
  const rendered = [
4406
- boxInsetDivider(theme, renderedWidth),
4439
+ boxLabeledBorder(
4440
+ theme,
4441
+ BOX_DIVIDER_LEFT,
4442
+ BOX_DIVIDER_RIGHT,
4443
+ theme.fg("dim", dividerText),
4444
+ options.dividerRightLabel ? theme.fg("dim", options.dividerRightLabel) : void 0,
4445
+ renderedWidth
4446
+ ),
4447
+ boxBlankLine(theme, renderedWidth),
4407
4448
  ...renderBoxedOutputLines(theme, outputLines, renderedWidth, options.renderLineBudget ?? outputLines.length),
4408
- ...renderedFooterLines,
4409
- boxBorder(theme, "\u2514", "\u2518", renderedWidth)
4449
+ boxBlankLine(theme, renderedWidth),
4450
+ boxLabeledBorder(
4451
+ theme,
4452
+ BOX_ROUND_BOTTOM_LEFT,
4453
+ BOX_ROUND_BOTTOM_RIGHT,
4454
+ footerText,
4455
+ options.expandHint ? theme.fg("dim", options.expandHint) : void 0,
4456
+ renderedWidth
4457
+ )
4410
4458
  ];
4411
4459
  cache = { width, lines: rendered };
4412
4460
  return rendered;
4413
4461
  }
4414
4462
  };
4415
4463
  }
4416
- function formatBoxedFooterFromValues(theme, elapsedMs, output, extraParts = [], fixedColumns = false) {
4464
+ function formatBoxedFooterFromValues(theme, elapsedMs, output, extraParts = []) {
4417
4465
  const wall = elapsedMs === void 0 ? "--" : `${(elapsedMs / 1e3).toFixed(2)}s`;
4418
- const elapsedPart = `${theme.fg("text", "\u25F7")} ${theme.fg("dim", wall)}`;
4466
+ const elapsedPart = theme.fg("text", wall);
4419
4467
  const extraPartList = extraParts.filter(Boolean).map((part) => theme.fg("dim", part));
4420
4468
  const wordsPart = theme.fg("dim", formatBoxedWords(output));
4421
- const parts = fixedColumns ? [
4422
- padVisibleRight(elapsedPart, COMPACT_FOOTER_ELAPSED_WIDTH),
4423
- ...extraPartList.map((part) => padVisibleRight(part, COMPACT_FOOTER_EXTRA_WIDTH)),
4424
- padVisibleRight(wordsPart, COMPACT_FOOTER_WORDS_WIDTH)
4425
- ] : [elapsedPart, ...extraPartList, wordsPart];
4426
- return parts.join(theme.fg("dim", " \xB7 "));
4469
+ return [elapsedPart, ...extraPartList, wordsPart].join(theme.fg("dim", " \xB7 "));
4427
4470
  }
4428
- function formatBoxedFooterParts(theme, result, extraParts = [], fixedColumns = false, elapsedMs) {
4429
- return formatBoxedFooterFromValues(
4430
- theme,
4431
- elapsedMs ?? getElapsedMs(result),
4432
- getTextOutput(result),
4433
- extraParts,
4434
- fixedColumns
4435
- );
4471
+ function formatBoxedFooterParts(theme, result, extraParts = [], elapsedMs) {
4472
+ return formatBoxedFooterFromValues(theme, elapsedMs ?? getElapsedMs(result), getTextOutput(result), extraParts);
4436
4473
  }
4437
4474
  function formatBoxedFooter(theme, result, extraParts = [], elapsedMs) {
4438
- return formatBoxedFooterParts(theme, result, extraParts, false, elapsedMs);
4475
+ return formatBoxedFooterParts(theme, result, extraParts, elapsedMs);
4439
4476
  }
4440
4477
  function renderCompactBoxedFooter(theme, result, options = {}) {
4441
4478
  if (options.state && typeof options.state === "object") {
4442
- options.state[COMPACT_FOOTER_KEY] = formatBoxedFooterParts(theme, result, [], true, options.elapsedMs);
4479
+ options.state[COMPACT_FOOTER_KEY] = formatBoxedFooterParts(theme, result, [], options.elapsedMs);
4443
4480
  options.state[COMPACT_FOOTER_ERROR_KEY] = Boolean(options.isError);
4444
4481
  options.state[COMPACT_FOOTER_PARTIAL_KEY] = Boolean(options.isPartial);
4445
4482
  return { invalidate() {
@@ -4451,8 +4488,14 @@ function renderCompactBoxedFooter(theme, result, options = {}) {
4451
4488
  render(width) {
4452
4489
  const renderedWidth = boxWidth(width);
4453
4490
  return [
4454
- boxLine(theme, formatBoxedFooterParts(theme, result, [], false, options.elapsedMs), renderedWidth),
4455
- boxBorder(theme, "\u2514", "\u2518", renderedWidth)
4491
+ boxLabeledBorder(
4492
+ theme,
4493
+ BOX_ROUND_BOTTOM_LEFT,
4494
+ BOX_ROUND_BOTTOM_RIGHT,
4495
+ formatBoxedFooterParts(theme, result, [], options.elapsedMs),
4496
+ void 0,
4497
+ renderedWidth
4498
+ )
4456
4499
  ];
4457
4500
  }
4458
4501
  };
@@ -4516,12 +4559,12 @@ ${hint}`);
4516
4559
 
4517
4560
  // extension-src/pi-style/features/messages/boxed-block.ts
4518
4561
  function formatMessageBlockTitle(theme, kind, title, icon = "\u2794") {
4519
- const rawTitle = title ? `${icon} ${kind} | ${title}` : `${icon} ${kind}`;
4562
+ const rawTitle = title ? `${icon} ${kind} \xB7 ${title}` : `${icon} ${kind}`;
4520
4563
  const coloredTitle = theme.fg("accent", rawTitle);
4521
4564
  return typeof theme?.bold === "function" ? theme.bold(coloredTitle) : coloredTitle;
4522
4565
  }
4523
4566
  function renderBoxedMessageBlock(theme, options) {
4524
- const { kind, title, right, body, icon = "\u2794", hasDivider = true, cache: shouldCache = true } = options;
4567
+ const { kind, title, right, body, icon = "\u2794", cache: shouldCache = true } = options;
4525
4568
  let cache = null;
4526
4569
  return {
4527
4570
  invalidate() {
@@ -4532,23 +4575,18 @@ function renderBoxedMessageBlock(theme, options) {
4532
4575
  const renderedWidth = boxWidth(width);
4533
4576
  const contentWidth = boxInnerWidth(renderedWidth);
4534
4577
  const titleLine = formatMessageBlockTitle(theme, kind, title, icon);
4535
- const lines = [];
4536
- lines.push(boxBorder(theme, "\u250C", "\u2510", renderedWidth));
4578
+ const bodyLines = body(contentWidth);
4579
+ const lines = [
4580
+ boxLabeledBorder(theme, "\u256D", "\u256E", titleLine, void 0, renderedWidth),
4581
+ boxBlankLine(theme, renderedWidth),
4582
+ ...bodyLines.map((line) => boxLine(theme, line, renderedWidth))
4583
+ ];
4584
+ if (bodyLines.length > 0) lines.push(boxBlankLine(theme, renderedWidth));
4537
4585
  if (right) {
4538
- const rightStyled = theme.fg("dim", right);
4539
- lines.push(boxLineWithRight(theme, titleLine, rightStyled, renderedWidth));
4586
+ lines.push(boxLabeledBorder(theme, "\u2570", "\u256F", "", theme.fg("dim", right), renderedWidth));
4540
4587
  } else {
4541
- lines.push(boxLine(theme, titleLine, renderedWidth));
4542
- }
4543
- const bodyLines = body(contentWidth);
4544
- const showDivider = hasDivider === "auto" ? bodyLines.length > 0 : hasDivider;
4545
- if (showDivider) {
4546
- lines.push(boxInsetDivider(theme, renderedWidth));
4588
+ lines.push(boxBorder(theme, "\u2570", "\u256F", renderedWidth));
4547
4589
  }
4548
- for (const line of bodyLines) {
4549
- lines.push(boxLine(theme, line, renderedWidth));
4550
- }
4551
- lines.push(boxBorder(theme, "\u2514", "\u2518", renderedWidth));
4552
4590
  if (shouldCache) cache = { width, lines };
4553
4591
  return lines;
4554
4592
  }
@@ -4594,7 +4632,7 @@ function patchCompaction(instance, _original, theme) {
4594
4632
  const block = renderBoxedMessageBlock(theme, {
4595
4633
  kind: "Compaction",
4596
4634
  title: `${tokenStr} tokens`,
4597
- ...expanded ? {} : { right: `(${expandHint()})` },
4635
+ ...expanded ? {} : { right: expandHint() },
4598
4636
  body,
4599
4637
  icon: "\u229F",
4600
4638
  hasDivider: expanded
@@ -4614,7 +4652,7 @@ function patchSkill(instance, _original, theme) {
4614
4652
  const block = renderBoxedMessageBlock(theme, {
4615
4653
  kind: "Skill",
4616
4654
  title: skillName,
4617
- ...expanded ? {} : { right: `(${expandHint()})` },
4655
+ ...expanded ? {} : { right: expandHint() },
4618
4656
  body,
4619
4657
  icon: "\u229F",
4620
4658
  hasDivider: expanded
@@ -4632,7 +4670,7 @@ function patchBranch(instance, _original, theme) {
4632
4670
  const body = expanded && summary && markdownTheme ? createMarkdownBody(summary, markdownTheme, theme) : () => [];
4633
4671
  const block = renderBoxedMessageBlock(theme, {
4634
4672
  kind: "Branch",
4635
- ...expanded ? {} : { right: `(${expandHint()})` },
4673
+ ...expanded ? {} : { right: expandHint() },
4636
4674
  body,
4637
4675
  icon: "\u229F",
4638
4676
  hasDivider: expanded
@@ -4788,34 +4826,70 @@ function extractOscEnvelope(line) {
4788
4826
  if (bodyEnd < 0 || !line.endsWith(OSC133_ZONE_FINAL)) return void 0;
4789
4827
  return { start: OSC133_ZONE_START, body: line.slice(OSC133_ZONE_START.length, bodyEnd), end: line.slice(bodyEnd) };
4790
4828
  }
4791
- function prefixAtFirstContent(line, prefix) {
4829
+ var BG_RESET = "\x1B[49m";
4830
+ function splitLeadingMarkers(line) {
4792
4831
  let index = 0;
4832
+ while (line.startsWith("\x1B]", index)) {
4833
+ const bel = line.indexOf("\x07", index + 2);
4834
+ const st = line.indexOf("\x1B\\", index + 2);
4835
+ const end = bel === -1 ? st : st === -1 ? bel : Math.min(bel, st);
4836
+ if (end === -1) break;
4837
+ index = end + 1;
4838
+ }
4839
+ return { head: line.slice(0, index), rest: line.slice(index) };
4840
+ }
4841
+ function leadingSgr(line) {
4842
+ if (!line.startsWith("\x1B[")) return "";
4843
+ let index = 2;
4793
4844
  while (index < line.length) {
4794
- if (line[index] === "\x1B" && line[index + 1] === "[") {
4795
- index += 2;
4796
- while (index < line.length && (line.charCodeAt(index) < 64 || line.charCodeAt(index) > 126)) index++;
4797
- if (index < line.length) index++;
4798
- continue;
4799
- }
4800
- if (/\s/u.test(line[index] ?? "")) {
4801
- index++;
4802
- continue;
4803
- }
4804
- break;
4845
+ const code = line.charCodeAt(index);
4846
+ if (code >= 64 && code <= 126) return line.slice(0, index + 1);
4847
+ index++;
4805
4848
  }
4806
- return `${line.slice(0, index)}${prefix}${line.slice(index)}`;
4849
+ return "";
4807
4850
  }
4808
- function decorateMessageLine(line, index, lastIndex, contentIndex, options) {
4851
+ function isBackgroundSgr(sequence) {
4852
+ if (!sequence.startsWith("\x1B[") || !sequence.endsWith("m")) return false;
4853
+ for (const code of sequence.slice(2, -1).split(";")) {
4854
+ const value = Number(code);
4855
+ if (value === 48 || value === 49) return true;
4856
+ if (value >= 40 && value <= 47) return true;
4857
+ if (value >= 100 && value <= 107) return true;
4858
+ }
4859
+ return false;
4860
+ }
4861
+ function rebuildAtWidth(line, width, lead) {
4862
+ const { head, rest } = splitLeadingMarkers(line);
4863
+ const bgAnsi = leadingSgr(rest);
4864
+ if (bgAnsi && isBackgroundSgr(bgAnsi) && rest.endsWith(BG_RESET)) {
4865
+ const body = rest.slice(bgAnsi.length, rest.length - BG_RESET.length);
4866
+ const pad = " ".repeat(Math.max(0, width - visibleWidth3(lead) - visibleWidth3(body)));
4867
+ return `${head}${bgAnsi}${lead}${body}${pad}${BG_RESET}`;
4868
+ }
4869
+ const padded = `${lead}${line}`;
4870
+ return `${padded}${" ".repeat(Math.max(0, width - visibleWidth3(padded)))}`;
4871
+ }
4872
+ function decorateMessageLine(line, index, lastIndex, contentIndex, width, options) {
4809
4873
  const { firstEnvelope, firstHasStart, multilineEnvelope, prefix } = options;
4810
4874
  const prefixWidth = visibleWidth3(prefix);
4875
+ const lead = index === contentIndex ? prefix : index > contentIndex ? " ".repeat(prefixWidth) : "";
4811
4876
  if (index === contentIndex && firstEnvelope)
4812
- return `${firstEnvelope.start}${prefixAtFirstContent(firstEnvelope.body, prefix)}${firstEnvelope.end}`;
4877
+ return `${firstEnvelope.start}${rebuildAtWidth(firstEnvelope.body, width, prefix)}${firstEnvelope.end}`;
4813
4878
  if (index === contentIndex && firstHasStart)
4814
- return `${OSC133_ZONE_START}${prefix}${line.slice(OSC133_ZONE_START.length)}`;
4879
+ return `${OSC133_ZONE_START}${rebuildAtWidth(line.slice(OSC133_ZONE_START.length), width, prefix)}`;
4815
4880
  if (index === lastIndex && multilineEnvelope && index !== contentIndex)
4816
- return `${OSC133_ZONE_END}${OSC133_ZONE_FINAL}${line.slice((OSC133_ZONE_END + OSC133_ZONE_FINAL).length)}`;
4817
- if (index === contentIndex) return `${prefix}${line}`;
4818
- return index > contentIndex ? `${" ".repeat(prefixWidth)}${line}` : line;
4881
+ return `${OSC133_ZONE_END}${OSC133_ZONE_FINAL}${rebuildAtWidth(
4882
+ line.slice((OSC133_ZONE_END + OSC133_ZONE_FINAL).length),
4883
+ width,
4884
+ lead
4885
+ )}`;
4886
+ if (index === contentIndex && index === lastIndex && multilineEnvelope && line.startsWith(OSC133_ZONE_END + OSC133_ZONE_FINAL))
4887
+ return `${OSC133_ZONE_END}${OSC133_ZONE_FINAL}${rebuildAtWidth(
4888
+ line.slice((OSC133_ZONE_END + OSC133_ZONE_FINAL).length),
4889
+ width,
4890
+ prefix
4891
+ )}`;
4892
+ return rebuildAtWidth(line, width, lead);
4819
4893
  }
4820
4894
  function contentText(line) {
4821
4895
  let output = "";
@@ -4849,23 +4923,21 @@ function prefixNative(lines, width, prefix) {
4849
4923
  const first = nativeLines[0] ?? "";
4850
4924
  const last = nativeLines.at(-1) ?? "";
4851
4925
  const multilineEnvelope = nativeLines.length > 1 && last.startsWith(OSC133_ZONE_END + OSC133_ZONE_FINAL);
4852
- const firstContentIndex = nativeLines.findIndex(
4853
- (line, index) => index !== nativeLines.length - 1 || !multilineEnvelope ? hasContent(line) : false
4854
- );
4926
+ const firstContentIndex = nativeLines.findIndex((line, index) => {
4927
+ if (index !== nativeLines.length - 1 || !multilineEnvelope) return hasContent(line);
4928
+ return !nativeLines.slice(0, index).some((earlier) => hasContent(earlier)) && hasContent(line);
4929
+ });
4855
4930
  if (firstContentIndex < 0) return nativeLines;
4856
4931
  const firstEnvelope = firstContentIndex === 0 ? extractOscEnvelope(first) : void 0;
4857
4932
  const firstHasStart = firstContentIndex === 0 && first.startsWith(OSC133_ZONE_START);
4858
- const decorated = nativeLines.map((line, index) => {
4859
- if (index === firstContentIndex)
4860
- return decorateMessageLine(line, index, nativeLines.length - 1, firstContentIndex, {
4861
- firstEnvelope,
4862
- firstHasStart,
4863
- multilineEnvelope,
4864
- prefix
4865
- });
4866
- if (index > firstContentIndex && hasContent(line)) return `${" ".repeat(prefixWidth)}${line}`;
4867
- return line;
4868
- });
4933
+ const decorated = nativeLines.map(
4934
+ (line, index) => decorateMessageLine(line, index, nativeLines.length - 1, firstContentIndex, width, {
4935
+ firstEnvelope,
4936
+ firstHasStart,
4937
+ multilineEnvelope,
4938
+ prefix
4939
+ })
4940
+ );
4869
4941
  if (!decorated.every((line) => visibleWidth3(line) <= width)) return void 0;
4870
4942
  if (!nativeLines.every((line) => visibleWidth3(line) <= bodyWidth)) return void 0;
4871
4943
  return decorated;
@@ -4956,7 +5028,6 @@ function matchLimitReached(result) {
4956
5028
  }
4957
5029
 
4958
5030
  // extension-src/pi-style/features/tools/boxed/bash.ts
4959
- var MAX_BASH_PREVIEW_LINES = 5;
4960
5031
  var MAX_LINE_CHARS = 2e3;
4961
5032
  var ESC2 = "\x1B";
4962
5033
  var BASH_TOOL_NOTICE_PATTERN = /^\[Showing (?:last|lines)\b.*\. Full output: .+\]$/;
@@ -5085,13 +5156,16 @@ function formatTimeout(context) {
5085
5156
  const timeout = context?.args?.timeout ?? 300;
5086
5157
  return `${timeout}s`;
5087
5158
  }
5088
- function renderBoxedBashResult(theme, inner, result, context) {
5159
+ function renderBoxedBashResult(theme, inner, result, context, expandHint2) {
5089
5160
  const rawCommand = String(context?.args?.command ?? "...");
5090
5161
  const referenceLines = rawCommand.split("\n").map((line, index) => `${index === 0 ? "$ " : "> "}${line}`);
5091
5162
  return renderBoxedToolResult(theme, inner, {
5092
5163
  widthKey: bashWidthKey(rawCommand, context?.args?.timeout),
5093
5164
  referenceLines,
5094
- footerLines: [formatBoxedFooter(theme, result, [`\u23F9 ${formatTimeout(context)}`], getElapsed(context))],
5165
+ footerLines: [
5166
+ formatBoxedFooter(theme, result, [`timeout ${formatTimeout(context)}`], getElapsed(context))
5167
+ ],
5168
+ ...expandHint2 ? { expandHint: expandHint2 } : {},
5095
5169
  isError: context.isError,
5096
5170
  isPartial: Boolean(context.isPartial)
5097
5171
  });
@@ -5099,7 +5173,7 @@ function renderBoxedBashResult(theme, inner, result, context) {
5099
5173
  function getElapsed(context) {
5100
5174
  return getStateElapsedMs(context.state);
5101
5175
  }
5102
- function createBashResultPreview(theme, text, options, color, extraLinesBefore = 0) {
5176
+ function createBashResultPreview(theme, text, options, color) {
5103
5177
  let cacheKey = "";
5104
5178
  let cacheLines = null;
5105
5179
  return {
@@ -5114,7 +5188,7 @@ function createBashResultPreview(theme, text, options, color, extraLinesBefore =
5114
5188
  const cacheId = `${bodyWidth}|${expanded ? 1 : 0}|${cfg.maxExpandedLines}|${cfg.dimOutput ? 1 : 0}`;
5115
5189
  if (cacheLines && cacheKey === cacheId) return cacheLines;
5116
5190
  if (!expanded) {
5117
- const needed = MAX_BASH_PREVIEW_LINES;
5191
+ const needed = cfg.maxCollapsedLines;
5118
5192
  let totalNewlines = 0;
5119
5193
  let scanFrom = 0;
5120
5194
  for (let i = text.length - 1; i >= 0; i--) {
@@ -5143,15 +5217,8 @@ function createBashResultPreview(theme, text, options, color, extraLinesBefore =
5143
5217
  if (color === "error") return formatToolOutputLine(theme, truncated, "error");
5144
5218
  return cfg.dimOutput ? formatToolOutputLine(theme, truncated) : formatToolOutputLine(theme, truncated, "text");
5145
5219
  });
5146
- const remaining = extraLinesBefore + (scanFrom > 0 ? countNewlines(text, 0, scanFrom) : 0);
5147
- if (remaining <= 0) {
5148
- cacheKey = cacheId;
5149
- cacheLines = truncatedShown;
5150
- return cacheLines;
5151
- }
5152
- const hint = safeTruncateToWidth(`... ${remaining} more lines, press Ctrl+o to expand`, bodyWidth, "\u2026");
5153
5220
  cacheKey = cacheId;
5154
- cacheLines = [...truncatedShown, "", theme.fg("muted", hint)];
5221
+ cacheLines = truncatedShown;
5155
5222
  return cacheLines;
5156
5223
  }
5157
5224
  const normalized = replaceTabs(text);
@@ -5189,7 +5256,7 @@ var bashTool = {
5189
5256
  const raw = getTextOutput(result);
5190
5257
  const outputColor = context.isError ? "error" : "toolOutput";
5191
5258
  if (!options.expanded) {
5192
- const scanLines = MAX_BASH_PREVIEW_LINES + 10;
5259
+ const scanLines = getToolsRenderConfig().maxCollapsedLines + 10;
5193
5260
  let nlCount = 0;
5194
5261
  let tailStart = 0;
5195
5262
  for (let i = raw.length - 1; i >= 0; i--) {
@@ -5203,18 +5270,17 @@ var bashTool = {
5203
5270
  }
5204
5271
  const tail = stripBashToolNoticeLines(stripAnsi(raw.slice(tailStart)));
5205
5272
  const totalLinesBefore = tailStart > 0 ? countNewlines(raw, 0, tailStart) : 0;
5206
- const inner2 = createBashResultPreview(theme, tail, options, outputColor, totalLinesBefore);
5207
- return renderBoxedBashResult(theme, inner2, result, context);
5273
+ const inner2 = createBashResultPreview(theme, tail, options, outputColor);
5274
+ return renderBoxedBashResult(theme, inner2, result, context, totalLinesBefore > 0 ? "Ctrl+O for more" : void 0);
5208
5275
  }
5209
5276
  const output = stripBashToolNoticeLines(stripAnsi(raw));
5210
- const inner = createBashResultPreview(theme, output, options, outputColor, 0);
5277
+ const inner = createBashResultPreview(theme, output, options, outputColor);
5211
5278
  return renderBoxedBashResult(theme, inner, result, context);
5212
5279
  }
5213
5280
  };
5214
5281
 
5215
5282
  // extension-src/pi-style/features/tools/boxed/edit.ts
5216
5283
  import { getLanguageFromPath } from "@earendil-works/pi-coding-agent";
5217
- import { Text } from "@earendil-works/pi-tui";
5218
5284
 
5219
5285
  // extension-src/pi-style/shared/split-diff.ts
5220
5286
  import { highlightCode as highlightCode2 } from "@earendil-works/pi-coding-agent";
@@ -5225,6 +5291,9 @@ var ADD_ROW_BACKGROUND_MIX_RATIO = 0.24;
5225
5291
  var REMOVE_ROW_BACKGROUND_MIX_RATIO = 0.12;
5226
5292
  var ADD_INLINE_EMPHASIS_MIX_RATIO = 0.44;
5227
5293
  var REMOVE_INLINE_EMPHASIS_MIX_RATIO = 0.26;
5294
+ var CONTEXT_KEEP_DEFAULT = 2;
5295
+ var CONTEXT_RUN_SHOW_MAX = 4;
5296
+ var SPLIT_DIFF_MIN_WIDTH = 114;
5228
5297
  function ansi256ToRgb(code) {
5229
5298
  if (code <= 15) {
5230
5299
  const base16 = [
@@ -5490,15 +5559,6 @@ function countDiffStats(diff) {
5490
5559
  }
5491
5560
  return { additions, removals };
5492
5561
  }
5493
- function renderDiffMeter(theme, additions, removals, width = 20) {
5494
- const total = additions + removals;
5495
- if (total <= 0) return "";
5496
- const addBlocks = Math.round(additions / total * width);
5497
- const removeBlocks = Math.max(0, width - addBlocks);
5498
- const addBar = addBlocks > 0 ? theme.fg("toolDiffAdded", "\u2501".repeat(addBlocks)) : "";
5499
- const removeBar = removeBlocks > 0 ? theme.fg("toolDiffRemoved", "\u2501".repeat(removeBlocks)) : "";
5500
- return `${theme.fg("dim", "[")}${addBar}${removeBar}${theme.fg("dim", "]")}`;
5501
- }
5502
5562
  function extractEditedPath(message) {
5503
5563
  const m = message.match(/Successfully replaced (?:text|\d+ block\(s\)|lines L\d+-\d+) in (.+)\.$/);
5504
5564
  return m?.[1];
@@ -5511,11 +5571,85 @@ function firstText(content) {
5511
5571
  }
5512
5572
  return "";
5513
5573
  }
5514
- var SplitDiffComponent = class {
5515
- constructor(theme, rows, maxRows, language) {
5574
+ function longestChangedLineWidth(rows) {
5575
+ let longest = 0;
5576
+ for (const row of rows) {
5577
+ const candidates = [];
5578
+ if (row.kind === "changed") {
5579
+ if (row.left) candidates.push(row.left.line);
5580
+ if (row.right) candidates.push(row.right.line);
5581
+ } else if (row.kind === "added" && row.right) {
5582
+ candidates.push(row.right.line);
5583
+ } else if (row.kind === "removed" && row.left) {
5584
+ candidates.push(row.left.line);
5585
+ }
5586
+ for (const candidate of candidates) {
5587
+ longest = Math.max(longest, safeVisibleWidth(candidate));
5588
+ }
5589
+ }
5590
+ return longest;
5591
+ }
5592
+ function pickDiffMode(stats, rows, width) {
5593
+ if (stats.additions <= 0 || stats.removals <= 0) return "unified";
5594
+ if (width < SPLIT_DIFF_MIN_WIDTH) return "unified";
5595
+ if (longestChangedLineWidth(rows) > width / 2) return "unified";
5596
+ return "split";
5597
+ }
5598
+ function collapseContextRows(rows, options) {
5599
+ const keep = options.keep;
5600
+ const runShowMax = options.runShowMax ?? CONTEXT_RUN_SHOW_MAX;
5601
+ const out = [];
5602
+ let i = 0;
5603
+ while (i < rows.length) {
5604
+ const row = rows[i];
5605
+ if (row?.kind !== "context") {
5606
+ if (row) out.push({ kind: "row", row });
5607
+ i++;
5608
+ continue;
5609
+ }
5610
+ let j = i;
5611
+ while (j < rows.length && rows[j]?.kind === "context") j++;
5612
+ const run = j - i;
5613
+ if (run <= runShowMax) {
5614
+ for (let k = i; k < j; k++) out.push({ kind: "row", row: rows[k] });
5615
+ } else {
5616
+ const leading = i === 0;
5617
+ const trailing = j >= rows.length;
5618
+ const keepHead = leading ? 0 : Math.min(keep, run);
5619
+ const keepTail = trailing ? 0 : Math.min(keep, Math.max(0, run - keepHead));
5620
+ const hidden = Math.max(0, run - keepHead - keepTail);
5621
+ if (keepHead > 0) {
5622
+ for (let k = i; k < i + keepHead; k++) out.push({ kind: "row", row: rows[k] });
5623
+ }
5624
+ if (hidden > 0) out.push({ kind: "gap", hidden });
5625
+ if (keepTail > 0) {
5626
+ for (let k = j - keepTail; k < j; k++) out.push({ kind: "row", row: rows[k] });
5627
+ }
5628
+ }
5629
+ i = j;
5630
+ }
5631
+ return out;
5632
+ }
5633
+ function planEntries(rows, maxRows) {
5634
+ const budget = Math.max(1, maxRows);
5635
+ let entries = collapseContextRows(rows, { keep: CONTEXT_KEEP_DEFAULT });
5636
+ if (entries.length <= budget) return entries;
5637
+ entries = collapseContextRows(rows, { keep: 0, runShowMax: 0 });
5638
+ if (entries.length <= budget) return entries;
5639
+ const kept = entries.slice(0, Math.max(1, budget - 1));
5640
+ const omitted = Math.max(0, entries.length - kept.length);
5641
+ if (omitted <= 0) return kept;
5642
+ return [...kept, { kind: "omitted", count: omitted }];
5643
+ }
5644
+ function formatGapLabel(hidden) {
5645
+ return `\u22EF ${hidden} unchanged ${hidden === 1 ? "line" : "lines"} hidden`;
5646
+ }
5647
+ function formatOmittedLabel(count) {
5648
+ return `\u22EF ${count} ${count === 1 ? "line" : "lines"} omitted \xB7 Ctrl+O to show full diff`;
5649
+ }
5650
+ var DiffRenderContext = class {
5651
+ constructor(theme, rows, language) {
5516
5652
  this.theme = theme;
5517
- this.rows = rows;
5518
- this.maxRows = maxRows;
5519
5653
  this.language = language;
5520
5654
  let maxDigits = 3;
5521
5655
  for (const row of rows) {
@@ -5533,16 +5667,129 @@ var SplitDiffComponent = class {
5533
5667
  this.containerBgAnsi = theme.getBgAnsi?.("toolSuccessBg") ?? "";
5534
5668
  }
5535
5669
  theme;
5536
- rows;
5537
- maxRows;
5538
5670
  language;
5539
- cacheWidth;
5540
- cacheLines;
5541
5671
  lineNumberWidth;
5542
- highlightCache = /* @__PURE__ */ new Map();
5543
- inlineHighlights = /* @__PURE__ */ new WeakMap();
5544
5672
  palette;
5545
5673
  containerBgAnsi;
5674
+ highlightCache = /* @__PURE__ */ new Map();
5675
+ inlineHighlights = /* @__PURE__ */ new WeakMap();
5676
+ fg(color, text) {
5677
+ return this.theme.fg(color, text);
5678
+ }
5679
+ inlineSpans(line) {
5680
+ return this.inlineHighlights.get(line) ?? [];
5681
+ }
5682
+ syntaxHighlight(line) {
5683
+ if (!this.language) return stripInlineBreaksPreserveAnsi(line);
5684
+ const safeLine = sanitizeSingleLineText(line);
5685
+ const key = `${this.language}
5686
+ ${safeLine}`;
5687
+ const cached = this.highlightCache.get(key);
5688
+ if (cached) return cached;
5689
+ let highlighted = safeLine;
5690
+ try {
5691
+ highlighted = highlightCode2(safeLine, this.language)[0] ?? safeLine;
5692
+ highlighted = stripInlineBreaksPreserveAnsi(highlighted).replace(BG_ANSI_PATTERN2, "");
5693
+ } catch {
5694
+ highlighted = safeLine;
5695
+ }
5696
+ this.highlightCache.set(key, highlighted);
5697
+ return highlighted;
5698
+ }
5699
+ };
5700
+ var UnifiedDiffRenderer = class {
5701
+ constructor(ctx, entries) {
5702
+ this.ctx = ctx;
5703
+ this.entries = entries;
5704
+ }
5705
+ ctx;
5706
+ entries;
5707
+ gapLine(entry, width) {
5708
+ return padRenderedLineWidth(this.ctx.fg("muted", formatGapLabel(entry.hidden)), width);
5709
+ }
5710
+ omittedLine(entry, width) {
5711
+ return padRenderedLineWidth(this.ctx.fg("muted", formatOmittedLabel(entry.count)), width);
5712
+ }
5713
+ render(width) {
5714
+ const safeWidth = Math.max(20, width);
5715
+ const prefixWidth = 1 + 1 + this.ctx.lineNumberWidth + 2;
5716
+ const codeWidth = Math.max(1, safeWidth - prefixWidth);
5717
+ const lines = [];
5718
+ for (const entry of this.entries) {
5719
+ if (entry.kind === "gap") {
5720
+ lines.push(this.gapLine(entry, safeWidth));
5721
+ continue;
5722
+ }
5723
+ if (entry.kind === "omitted") {
5724
+ lines.push(this.omittedLine(entry, safeWidth));
5725
+ continue;
5726
+ }
5727
+ lines.push(...this.rowLines(entry.row, codeWidth, safeWidth));
5728
+ }
5729
+ return lines;
5730
+ }
5731
+ rowLines(row, codeWidth, width) {
5732
+ const segments = [];
5733
+ if (row.kind === "changed") {
5734
+ if (row.left) segments.push({ kind: "remove", line: row.left });
5735
+ if (row.right) segments.push({ kind: "add", line: row.right });
5736
+ } else if (row.kind === "added" && row.right) {
5737
+ segments.push({ kind: "add", line: row.right });
5738
+ } else if (row.kind === "removed" && row.left) {
5739
+ segments.push({ kind: "remove", line: row.left });
5740
+ } else {
5741
+ const line = row.left ?? row.right;
5742
+ if (line) segments.push({ kind: "context", line });
5743
+ }
5744
+ const out = [];
5745
+ for (const segment2 of segments) {
5746
+ out.push(...this.segmentLines(segment2.kind, segment2.line, codeWidth, width));
5747
+ }
5748
+ return out;
5749
+ }
5750
+ segmentLines(kind, line, codeWidth, width) {
5751
+ const isAdd = kind === "add";
5752
+ const isRemove = kind === "remove";
5753
+ const blank = line.line === "";
5754
+ const visualKind = kind === "context" || blank ? "context" : kind;
5755
+ const markerChar = kind === "context" ? " " : isAdd ? "+" : "-";
5756
+ const markerColor = isAdd ? "toolDiffAdded" : isRemove ? "toolDiffRemoved" : "dim";
5757
+ const gutterColor = visualKind === "context" ? "dim" : isAdd ? "toolDiffAdded" : "toolDiffRemoved";
5758
+ const gutter = line.lineNumber.trim().padStart(this.ctx.lineNumberWidth, " ");
5759
+ const firstPrefixAnsi = `${this.ctx.fg(markerColor, markerChar)} ${this.ctx.fg(gutterColor, gutter)} `;
5760
+ const firstPrefixPlain = `${markerChar} ${gutter} `;
5761
+ const contPrefixAnsi = `${this.ctx.fg("dim", " ")} ${this.ctx.fg("dim", " ".repeat(this.ctx.lineNumberWidth))} `;
5762
+ const contPrefixPlain = ` ${" ".repeat(1 + this.ctx.lineNumberWidth)} `;
5763
+ const rowBg = visualKind === "add" ? this.ctx.palette.addRowBgAnsi : visualKind === "remove" ? this.ctx.palette.removeRowBgAnsi : void 0;
5764
+ const plainSegments = wrapPlainText(line.line, codeWidth);
5765
+ const out = [];
5766
+ for (let i = 0; i < plainSegments.length; i++) {
5767
+ const prefixAnsi = i === 0 ? firstPrefixAnsi : contPrefixAnsi;
5768
+ const prefixPlain = i === 0 ? firstPrefixPlain : contPrefixPlain;
5769
+ const plainSegment = plainSegments[i] ?? "";
5770
+ let segment2 = this.ctx.syntaxHighlight(plainSegment);
5771
+ segment2 = fitToWidth(segment2, codeWidth);
5772
+ let rendered = prefixAnsi + segment2;
5773
+ const expectedWidth = safeVisibleWidth(prefixPlain) + codeWidth;
5774
+ const currentWidth = safeVisibleWidth(stripAnsi(rendered));
5775
+ if (currentWidth < expectedWidth) {
5776
+ rendered += " ".repeat(expectedWidth - currentWidth);
5777
+ }
5778
+ if (rowBg) {
5779
+ rendered = `${rowBg}${keepBackgroundAcrossResets(rendered, rowBg)}${this.ctx.containerBgAnsi}`;
5780
+ }
5781
+ out.push(padRenderedLineWidth(rendered, width));
5782
+ }
5783
+ return out;
5784
+ }
5785
+ };
5786
+ var SplitDiffRenderer = class {
5787
+ constructor(ctx, entries) {
5788
+ this.ctx = ctx;
5789
+ this.entries = entries;
5790
+ }
5791
+ ctx;
5792
+ entries;
5546
5793
  getCellLineKind(kind, side) {
5547
5794
  if (kind === "changed") return side === "left" ? "remove" : "add";
5548
5795
  if (kind === "removed" && side === "left") return "remove";
@@ -5562,23 +5809,23 @@ var SplitDiffComponent = class {
5562
5809
  return "dim";
5563
5810
  }
5564
5811
  getRowBackground(lineKind) {
5565
- if (lineKind === "add") return this.palette.addRowBgAnsi;
5566
- if (lineKind === "remove") return this.palette.removeRowBgAnsi;
5812
+ if (lineKind === "add") return this.ctx.palette.addRowBgAnsi;
5813
+ if (lineKind === "remove") return this.ctx.palette.removeRowBgAnsi;
5567
5814
  return void 0;
5568
5815
  }
5569
5816
  getEmphasisBackground(lineKind) {
5570
- if (lineKind === "add") return this.palette.addEmphasisBgAnsi;
5571
- if (lineKind === "remove") return this.palette.removeEmphasisBgAnsi;
5817
+ if (lineKind === "add") return this.ctx.palette.addEmphasisBgAnsi;
5818
+ if (lineKind === "remove") return this.ctx.palette.removeEmphasisBgAnsi;
5572
5819
  return void 0;
5573
5820
  }
5574
5821
  getCellFillBackground(kind, side) {
5575
5822
  switch (kind) {
5576
5823
  case "changed":
5577
- return side === "left" ? this.palette.removeRowBgAnsi : this.palette.addRowBgAnsi;
5824
+ return side === "left" ? this.ctx.palette.removeRowBgAnsi : this.ctx.palette.addRowBgAnsi;
5578
5825
  case "removed":
5579
- return side === "left" ? this.palette.removeRowBgAnsi : void 0;
5826
+ return side === "left" ? this.ctx.palette.removeRowBgAnsi : void 0;
5580
5827
  case "added":
5581
- return side === "right" ? this.palette.addRowBgAnsi : void 0;
5828
+ return side === "right" ? this.ctx.palette.addRowBgAnsi : void 0;
5582
5829
  default:
5583
5830
  return void 0;
5584
5831
  }
@@ -5587,57 +5834,40 @@ var SplitDiffComponent = class {
5587
5834
  const lineKind = this.getCellLineKind(kind, side);
5588
5835
  const markerChar = lineKind === "add" || lineKind === "remove" ? "\u258C" : " ";
5589
5836
  const markerColor = lineKind === "add" ? "toolDiffAdded" : lineKind === "remove" ? "toolDiffRemoved" : "borderMuted";
5590
- const marker2 = this.theme.fg(markerColor, markerChar);
5591
- const lineNumber = this.theme.fg("dim", " ".repeat(this.lineNumberWidth));
5592
- const divider = this.theme.fg("borderMuted", " \u2502 ");
5837
+ const marker2 = this.ctx.fg(markerColor, markerChar);
5838
+ const lineNumber = this.ctx.fg("dim", " ".repeat(this.ctx.lineNumberWidth));
5839
+ const divider = this.ctx.fg("borderMuted", " \u2502 ");
5593
5840
  const prefix = `${marker2} ${lineNumber}${divider}`;
5594
- const prefixPlain = `${markerChar} ${" ".repeat(this.lineNumberWidth)} \u2502 `;
5841
+ const prefixPlain = `${markerChar} ${" ".repeat(this.ctx.lineNumberWidth)} \u2502 `;
5595
5842
  const tailWidth = Math.max(0, columnWidth - safeVisibleWidth(prefixPlain));
5596
5843
  let rendered = prefix + " ".repeat(tailWidth);
5597
5844
  const bg = this.getCellFillBackground(kind, side);
5598
5845
  if (!bg) return padRenderedLineWidth(rendered, columnWidth);
5599
- rendered = `${bg}${keepBackgroundAcrossResets(rendered, bg)}${this.containerBgAnsi}`;
5846
+ rendered = `${bg}${keepBackgroundAcrossResets(rendered, bg)}${this.ctx.containerBgAnsi}`;
5600
5847
  return padRenderedLineWidth(rendered, columnWidth);
5601
5848
  }
5602
- syntaxHighlight(line) {
5603
- if (!this.language) return stripInlineBreaksPreserveAnsi(line);
5604
- const safeLine = sanitizeSingleLineText(line);
5605
- const key = `${this.language}
5606
- ${safeLine}`;
5607
- const cached = this.highlightCache.get(key);
5608
- if (cached) return cached;
5609
- let highlighted = safeLine;
5610
- try {
5611
- highlighted = highlightCode2(safeLine, this.language)[0] ?? safeLine;
5612
- highlighted = stripInlineBreaksPreserveAnsi(highlighted).replace(BG_ANSI_PATTERN2, "");
5613
- } catch {
5614
- highlighted = safeLine;
5615
- }
5616
- this.highlightCache.set(key, highlighted);
5617
- return highlighted;
5618
- }
5619
5849
  formatCellLines(kind, side, line, columnWidth) {
5620
5850
  if (!line) return [this.blankCell(kind, side, columnWidth)];
5621
5851
  const lineKind = this.getVisualLineKind(kind, side, line);
5622
5852
  const markerChar = lineKind === "add" || lineKind === "remove" ? "\u258C" : " ";
5623
5853
  const markerColor = lineKind === "add" ? "toolDiffAdded" : lineKind === "remove" ? "toolDiffRemoved" : "borderMuted";
5624
- const lineNumber = line.lineNumber.trim().padStart(this.lineNumberWidth, " ");
5625
- const firstPrefixAnsi = this.theme.fg(markerColor, markerChar) + " " + this.theme.fg(this.getNumberColor(lineKind), lineNumber) + this.theme.fg("borderMuted", " \u2502 ");
5854
+ const lineNumber = line.lineNumber.trim().padStart(this.ctx.lineNumberWidth, " ");
5855
+ const firstPrefixAnsi = this.ctx.fg(markerColor, markerChar) + " " + this.ctx.fg(this.getNumberColor(lineKind), lineNumber) + this.ctx.fg("borderMuted", " \u2502 ");
5626
5856
  const firstPrefixPlain = `${markerChar} ${lineNumber} \u2502 `;
5627
- const contPrefixAnsi = this.theme.fg(markerColor, markerChar) + " " + this.theme.fg("dim", " ".repeat(this.lineNumberWidth)) + this.theme.fg("borderMuted", " \u2502 ");
5628
- const contPrefixPlain = `${markerChar} ${" ".repeat(this.lineNumberWidth)} \u2502 `;
5857
+ const contPrefixAnsi = this.ctx.fg(markerColor, markerChar) + " " + this.ctx.fg("dim", " ".repeat(this.ctx.lineNumberWidth)) + this.ctx.fg("borderMuted", " \u2502 ");
5858
+ const contPrefixPlain = `${markerChar} ${" ".repeat(this.ctx.lineNumberWidth)} \u2502 `;
5629
5859
  const codeWidth = Math.max(1, columnWidth - safeVisibleWidth(firstPrefixPlain));
5630
5860
  const rowBg = this.getRowBackground(lineKind);
5631
5861
  const emphasisBg = this.getEmphasisBackground(lineKind);
5632
5862
  const plainSegments = wrapPlainText(line.line, codeWidth);
5633
5863
  const lines = [];
5634
- const spans = this.inlineHighlights.get(line) ?? [];
5864
+ const spans = this.ctx.inlineSpans(line);
5635
5865
  let consumed = 0;
5636
5866
  for (let i = 0; i < plainSegments.length; i++) {
5637
5867
  const prefixAnsi = i === 0 ? firstPrefixAnsi : contPrefixAnsi;
5638
5868
  const prefixPlain = i === 0 ? firstPrefixPlain : contPrefixPlain;
5639
5869
  const plainSegment = plainSegments[i] ?? "";
5640
- let segment2 = this.syntaxHighlight(plainSegment);
5870
+ let segment2 = this.ctx.syntaxHighlight(plainSegment);
5641
5871
  if (spans.length > 0 && emphasisBg) {
5642
5872
  const segmentStart = consumed;
5643
5873
  for (let si = spans.length - 1; si >= 0; si--) {
@@ -5651,7 +5881,7 @@ ${safeLine}`;
5651
5881
  localStart,
5652
5882
  localEnd,
5653
5883
  emphasisBg,
5654
- rowBg ?? this.containerBgAnsi
5884
+ rowBg ?? this.ctx.containerBgAnsi
5655
5885
  );
5656
5886
  }
5657
5887
  }
@@ -5664,33 +5894,38 @@ ${safeLine}`;
5664
5894
  rendered += " ".repeat(expectedWidth - currentWidth);
5665
5895
  }
5666
5896
  if (rowBg) {
5667
- rendered = `${rowBg}${keepBackgroundAcrossResets(rendered, rowBg)}${this.containerBgAnsi}`;
5897
+ rendered = `${rowBg}${keepBackgroundAcrossResets(rendered, rowBg)}${this.ctx.containerBgAnsi}`;
5668
5898
  }
5669
5899
  lines.push(padRenderedLineWidth(rendered, columnWidth));
5670
5900
  consumed += plainSegment.length;
5671
5901
  }
5672
5902
  return lines;
5673
5903
  }
5904
+ gapLine(entry, width) {
5905
+ return padRenderedLineWidth(this.ctx.fg("muted", formatGapLabel(entry.hidden)), width);
5906
+ }
5907
+ omittedLine(entry, width) {
5908
+ return padRenderedLineWidth(this.ctx.fg("muted", formatOmittedLabel(entry.count)), width);
5909
+ }
5674
5910
  render(width) {
5675
- if (this.cacheWidth === width && this.cacheLines) return this.cacheLines;
5676
5911
  const safeWidth = Math.max(20, width);
5677
- const columnSeparator = this.theme.fg("borderMuted", " \u2502 ");
5912
+ const columnSeparator = this.ctx.fg("borderMuted", " \u2502 ");
5678
5913
  const separatorWidth = safeVisibleWidth(stripAnsi(columnSeparator));
5679
5914
  const leftWidth = Math.max(20, Math.floor((safeWidth - separatorWidth) / 2));
5680
5915
  const rightWidth = Math.max(20, safeWidth - separatorWidth - leftWidth);
5681
5916
  const formatBorderCell = (columnWidth, junction) => {
5682
5917
  const safeColumnWidth = Math.max(1, columnWidth);
5683
5918
  const chars = "\u2500".repeat(safeColumnWidth).split("");
5684
- const dividerIndex = this.lineNumberWidth + 3;
5919
+ const dividerIndex = this.ctx.lineNumberWidth + 3;
5685
5920
  if (dividerIndex >= 0 && dividerIndex < chars.length) {
5686
5921
  chars[dividerIndex] = junction;
5687
5922
  }
5688
- return this.theme.fg("borderMuted", chars.join(""));
5923
+ return this.ctx.fg("borderMuted", chars.join(""));
5689
5924
  };
5690
5925
  const formatHeaderCell = (label, columnWidth) => {
5691
5926
  const markerPad = " ";
5692
- const lineNumberLabel = fitToWidth(label, this.lineNumberWidth);
5693
- const prefixAnsi = this.theme.fg("borderMuted", markerPad) + this.theme.fg("dim", lineNumberLabel) + this.theme.fg("borderMuted", " \u2502 ");
5927
+ const lineNumberLabel = fitToWidth(label, this.ctx.lineNumberWidth);
5928
+ const prefixAnsi = this.ctx.fg("borderMuted", markerPad) + this.ctx.fg("dim", lineNumberLabel) + this.ctx.fg("borderMuted", " \u2502 ");
5694
5929
  const prefixPlain = `${markerPad}${stripAnsi(lineNumberLabel)} \u2502 `;
5695
5930
  const codeWidth = Math.max(0, columnWidth - safeVisibleWidth(prefixPlain));
5696
5931
  return padRenderedLineWidth(prefixAnsi + " ".repeat(codeWidth), columnWidth);
@@ -5698,7 +5933,7 @@ ${safeLine}`;
5698
5933
  const lines = [];
5699
5934
  lines.push(
5700
5935
  padRenderedLineWidth(
5701
- formatBorderCell(leftWidth, "\u252C") + this.theme.fg("borderMuted", "\u2500\u252C\u2500") + formatBorderCell(rightWidth, "\u252C"),
5936
+ formatBorderCell(leftWidth, "\u252C") + this.ctx.fg("borderMuted", "\u2500\u252C\u2500") + formatBorderCell(rightWidth, "\u252C"),
5702
5937
  safeWidth
5703
5938
  )
5704
5939
  );
@@ -5708,28 +5943,70 @@ ${safeLine}`;
5708
5943
  safeWidth
5709
5944
  )
5710
5945
  );
5711
- for (const row of this.rows.slice(0, this.maxRows)) {
5946
+ for (const entry of this.entries) {
5947
+ if (entry.kind === "gap") {
5948
+ lines.push(this.gapLine(entry, safeWidth));
5949
+ continue;
5950
+ }
5951
+ if (entry.kind === "omitted") {
5952
+ lines.push(this.omittedLine(entry, safeWidth));
5953
+ continue;
5954
+ }
5955
+ const row = entry.row;
5712
5956
  const leftCellLines = this.formatCellLines(row.kind, "left", row.left, leftWidth);
5713
5957
  const rightCellLines = this.formatCellLines(row.kind, "right", row.right, rightWidth);
5714
5958
  const rowHeight = Math.max(leftCellLines.length, rightCellLines.length);
5715
5959
  for (let i = 0; i < rowHeight; i++) {
5716
- const leftFallbackKind = row.kind === "changed" ? "context" : row.kind;
5717
- const rightFallbackKind = row.kind === "changed" ? "context" : row.kind;
5718
- const leftCell = leftCellLines[i] ?? this.blankCell(leftFallbackKind, "left", leftWidth);
5719
- const rightCell = rightCellLines[i] ?? this.blankCell(rightFallbackKind, "right", rightWidth);
5960
+ const fallbackKind = row.kind === "changed" ? "context" : row.kind;
5961
+ const leftCell = leftCellLines[i] ?? this.blankCell(fallbackKind, "left", leftWidth);
5962
+ const rightCell = rightCellLines[i] ?? this.blankCell(fallbackKind, "right", rightWidth);
5720
5963
  const joined = padRenderedLineWidth(leftCell + columnSeparator + rightCell, safeWidth);
5721
5964
  lines.push(joined);
5722
5965
  }
5723
5966
  }
5724
- if (this.rows.length > this.maxRows) {
5725
- lines.push(this.theme.fg("muted", `... ${this.rows.length - this.maxRows} more rows`));
5726
- }
5727
5967
  lines.push(
5728
5968
  padRenderedLineWidth(
5729
- formatBorderCell(leftWidth, "\u2534") + this.theme.fg("borderMuted", "\u2500\u2534\u2500") + formatBorderCell(rightWidth, "\u2534"),
5969
+ formatBorderCell(leftWidth, "\u2534") + this.ctx.fg("borderMuted", "\u2500\u2534\u2500") + formatBorderCell(rightWidth, "\u2534"),
5730
5970
  safeWidth
5731
5971
  )
5732
5972
  );
5973
+ return lines;
5974
+ }
5975
+ };
5976
+ var AdaptiveDiffComponent = class {
5977
+ constructor(theme, rows, maxRows, language) {
5978
+ this.rows = rows;
5979
+ this.ctx = new DiffRenderContext(theme, rows, language);
5980
+ this.stats = { additions: 0, removals: 0 };
5981
+ for (const row of rows) {
5982
+ if (row.kind === "added" || row.kind === "changed") this.stats.additions++;
5983
+ if (row.kind === "removed" || row.kind === "changed") this.stats.removals++;
5984
+ }
5985
+ const entries = planEntries(rows, maxRows);
5986
+ this.collapsed = entries.some((entry) => entry.kind !== "row");
5987
+ this.unified = new UnifiedDiffRenderer(this.ctx, entries);
5988
+ this.split = new SplitDiffRenderer(this.ctx, entries);
5989
+ }
5990
+ rows;
5991
+ cacheWidth;
5992
+ cacheLines;
5993
+ ctx;
5994
+ stats;
5995
+ unified;
5996
+ split;
5997
+ collapsed;
5998
+ /** True when any unchanged context was collapsed or rows were omitted. */
5999
+ hasCollapsed() {
6000
+ return this.collapsed;
6001
+ }
6002
+ modeForWidth(width) {
6003
+ return pickDiffMode(this.stats, this.rows, Math.max(20, width));
6004
+ }
6005
+ render(width) {
6006
+ if (this.cacheWidth === width && this.cacheLines) return this.cacheLines;
6007
+ const safeWidth = Math.max(20, width);
6008
+ const mode = this.modeForWidth(safeWidth);
6009
+ const lines = mode === "split" ? this.split.render(safeWidth) : this.unified.render(safeWidth);
5733
6010
  this.cacheWidth = width;
5734
6011
  this.cacheLines = lines;
5735
6012
  return lines;
@@ -5737,18 +6014,32 @@ ${safeLine}`;
5737
6014
  invalidate() {
5738
6015
  this.cacheWidth = void 0;
5739
6016
  this.cacheLines = void 0;
5740
- this.highlightCache.clear();
5741
6017
  }
5742
6018
  };
5743
6019
 
5744
6020
  // extension-src/pi-style/features/tools/boxed/edit.ts
5745
6021
  var MAX_HIGHLIGHT_DIFF_CHARS = 12e3;
5746
6022
  var MAX_HIGHLIGHT_DIFF_ROWS = 120;
6023
+ function diffDividerLabel(theme, stats) {
6024
+ const plus = stats.additions > 0 ? theme.fg("toolDiffAdded", `+${stats.additions}`) : theme.fg("dim", "+0");
6025
+ const minus = stats.removals > 0 ? theme.fg("toolDiffRemoved", `-${stats.removals}`) : theme.fg("dim", "-0");
6026
+ return `Diff \xB7 ${plus} ${minus}`;
6027
+ }
6028
+ function editDiffFooter(theme, result, context, stats) {
6029
+ const elapsedMs = getElapsedMs(result) ?? stateElapsedMs(context);
6030
+ const parts = [];
6031
+ if (elapsedMs !== void 0) parts.push(theme.fg("text", formatElapsedMs(elapsedMs)));
6032
+ const plus = stats.additions > 0 ? theme.fg("toolDiffAdded", `+${stats.additions}`) : theme.fg("dim", "+0");
6033
+ const minus = stats.removals > 0 ? theme.fg("toolDiffRemoved", `-${stats.removals}`) : theme.fg("dim", "-0");
6034
+ parts.push(theme.fg("dim", "1 file"), `${plus} ${minus}`);
6035
+ return parts.join(theme.fg("dim", " \xB7 "));
6036
+ }
5747
6037
  var editTool = {
5748
6038
  call(args, theme, context) {
5749
6039
  noteExecutionStart(context);
5750
6040
  const detail = displayPath(String(args?.path ?? args?.file_path ?? ""), context);
5751
- return renderBoxedToolCall(theme, "Edit", [`${theme.fg("dim", "Path: ")}${detail}`], {
6041
+ return renderBoxedToolCall(theme, "Edit", [], {
6042
+ headerDetail: detail,
5752
6043
  isError: Boolean(context.isError),
5753
6044
  isPartial: Boolean(context.isPartial),
5754
6045
  isPending: Boolean(context.isPartial)
@@ -5783,25 +6074,24 @@ var editTool = {
5783
6074
  const rows = buildSplitRows(diff);
5784
6075
  const expanded = options.expanded;
5785
6076
  const shouldHighlight = Boolean(language) && diff.length <= MAX_HIGHLIGHT_DIFF_CHARS && rows.length <= MAX_HIGHLIGHT_DIFF_ROWS;
5786
- const { additions, removals } = countDiffStats(diff);
5787
- const meter = renderDiffMeter(theme, additions, removals);
5788
- const summary = `${theme.fg("dim", "\u21B3")} ${theme.fg("muted", "diff")} ${theme.fg("toolDiffAdded", `+${additions}`)} ${theme.fg("toolDiffRemoved", `-${removals}`)} ${theme.fg("muted", "split")}` + (meter ? ` ${meter}` : "");
6077
+ const stats = countDiffStats(diff);
5789
6078
  const maxRows = expanded ? 160 : 36;
5790
- const split = new SplitDiffComponent(theme, rows, maxRows, shouldHighlight ? language : void 0);
6079
+ const diffView = new AdaptiveDiffComponent(theme, rows, maxRows, shouldHighlight ? language : void 0);
6080
+ const expandHint2 = !expanded && diffView.hasCollapsed() ? "Ctrl+O more" : void 0;
5791
6081
  return renderBoxedToolResult(
5792
6082
  theme,
5793
6083
  {
5794
6084
  render(width) {
5795
- const safeWidth = Math.max(20, width);
5796
- const headerLines = new Text(summary, 0, 0).render(safeWidth);
5797
- return [...headerLines, ...split.render(safeWidth)];
6085
+ return diffView.render(width);
5798
6086
  },
5799
6087
  invalidate() {
5800
- split.invalidate();
6088
+ diffView.invalidate();
5801
6089
  }
5802
6090
  },
5803
6091
  {
5804
- footerLines: resultFooterLines(theme, result, context)
6092
+ dividerLabel: diffDividerLabel(theme, stats),
6093
+ ...expandHint2 ? { dividerRightLabel: expandHint2 } : {},
6094
+ footerLines: [editDiffFooter(theme, result, context, stats)]
5805
6095
  }
5806
6096
  );
5807
6097
  }
@@ -5823,19 +6113,20 @@ function renderFallbackResult(_toolName, result, options, theme, context) {
5823
6113
  const maxLines = expanded ? getToolsRenderConfig().maxExpandedLines : MAX_FALLBACK_PREVIEW_LINES;
5824
6114
  const output = getTextOutput(result);
5825
6115
  const elapsedMs = getStateElapsedMs(context.state);
6116
+ const { lines, omitted } = selectRenderLines(output, maxLines);
5826
6117
  return renderBoxedToolResult(
5827
6118
  theme,
5828
- (contentWidth) => {
5829
- const body = renderLines(theme, output, options, {
5830
- maxLines,
5831
- color: isError ? "error" : "toolOutput",
5832
- width: contentWidth
5833
- });
5834
- return body ? body.split("\n") : [];
6119
+ () => {
6120
+ const body = lines.map((line) => formatToolOutputLine(theme, line, isError ? "error" : "toolOutput"));
6121
+ if (expanded && omitted > 0) {
6122
+ body.push(theme.fg("muted", `\u2026 ${omitted} more lines omitted by render budget`));
6123
+ }
6124
+ return body;
5835
6125
  },
5836
6126
  {
5837
6127
  footerLines: [formatBoxedFooter(theme, result, [], elapsedMs)],
5838
6128
  renderLineBudget: maxLines,
6129
+ ...expanded || omitted <= 0 ? {} : { expandHint: "Ctrl+O for more" },
5839
6130
  isError,
5840
6131
  isPartial: Boolean(options.isPartial)
5841
6132
  }
@@ -6004,7 +6295,6 @@ var lsTool = {
6004
6295
 
6005
6296
  // extension-src/pi-style/features/tools/boxed/quick-edit.ts
6006
6297
  import { getLanguageFromPath as getLanguageFromPath2 } from "@earendil-works/pi-coding-agent";
6007
- import { Text as Text2 } from "@earendil-works/pi-tui";
6008
6298
  var MAX_HIGHLIGHT_DIFF_CHARS2 = 12e3;
6009
6299
  var MAX_HIGHLIGHT_DIFF_ROWS2 = 120;
6010
6300
  var QUICK_EDIT_TOOLS = {
@@ -6073,8 +6363,19 @@ function extractQuickEditDiff(text) {
6073
6363
  }
6074
6364
  return diffLines.length > 0 ? diffLines.join("\n") : void 0;
6075
6365
  }
6076
- function quickEditFooter(theme, context, output = "") {
6077
- return formatBoxedFooterFromValues(theme, getStateElapsedMs(context.state), output);
6366
+ function quickEditDividerLabel(theme, stats) {
6367
+ const plus = stats.additions > 0 ? theme.fg("toolDiffAdded", `+${stats.additions}`) : theme.fg("dim", "+0");
6368
+ const minus = stats.removals > 0 ? theme.fg("toolDiffRemoved", `-${stats.removals}`) : theme.fg("dim", "-0");
6369
+ return `Diff \xB7 ${plus} ${minus}`;
6370
+ }
6371
+ function quickEditDiffFooter(theme, result, context, stats) {
6372
+ const elapsedMs = getElapsedMs(result) ?? getStateElapsedMs(context.state);
6373
+ const parts = [];
6374
+ if (elapsedMs !== void 0) parts.push(theme.fg("text", formatElapsedMs(elapsedMs)));
6375
+ const plus = stats.additions > 0 ? theme.fg("toolDiffAdded", `+${stats.additions}`) : theme.fg("dim", "+0");
6376
+ const minus = stats.removals > 0 ? theme.fg("toolDiffRemoved", `-${stats.removals}`) : theme.fg("dim", "-0");
6377
+ parts.push(theme.fg("dim", "1 file"), `${plus} ${minus}`);
6378
+ return parts.join(theme.fg("dim", " \xB7 "));
6078
6379
  }
6079
6380
  function renderQuickEditResult(_toolName, result, options, theme, context, config) {
6080
6381
  if (options.isPartial) {
@@ -6087,7 +6388,7 @@ function renderQuickEditResult(_toolName, result, options, theme, context, confi
6087
6388
  const output = getTextOutput(result);
6088
6389
  if (context.isError) {
6089
6390
  return renderBoxedToolResult(theme, () => [theme.fg("error", stripAnsi(output).trim() || "Error")], {
6090
- footerLines: [quickEditFooter(theme, context, output)],
6391
+ footerLines: [quickEditFooter(theme, context)],
6091
6392
  isError: true
6092
6393
  });
6093
6394
  }
@@ -6095,7 +6396,7 @@ function renderQuickEditResult(_toolName, result, options, theme, context, confi
6095
6396
  if (!diff) {
6096
6397
  const fallback = stripAnsi(output).trim() || config.fallbackLabel;
6097
6398
  return renderBoxedToolResult(theme, () => [`${theme.fg("dim", "\u21B3")} ${theme.fg("muted", fallback)}`], {
6098
- footerLines: [quickEditFooter(theme, context, output)]
6399
+ footerLines: [quickEditFooter(theme, context)]
6099
6400
  });
6100
6401
  }
6101
6402
  const rows = buildSplitRows(diff);
@@ -6103,34 +6404,41 @@ function renderQuickEditResult(_toolName, result, options, theme, context, confi
6103
6404
  const argPath = String(context?.args?.path ?? "");
6104
6405
  const language = argPath ? getLanguageFromPath2(argPath) : void 0;
6105
6406
  const shouldHighlight = Boolean(language) && diff.length <= MAX_HIGHLIGHT_DIFF_CHARS2 && rows.length <= MAX_HIGHLIGHT_DIFF_ROWS2;
6106
- const { additions, removals } = countDiffStats(diff);
6107
- const meter = renderDiffMeter(theme, additions, removals);
6108
- const summary = `${theme.fg("dim", "\u21B3")} ${theme.fg("muted", "diff")} ${theme.fg("toolDiffAdded", `+${additions}`)} ${theme.fg("toolDiffRemoved", `-${removals}`)} ${theme.fg("muted", "split")}` + (meter ? ` ${meter}` : "");
6407
+ const stats = countDiffStats(diff);
6109
6408
  const maxRows = expanded ? 160 : 36;
6110
- const split = new SplitDiffComponent(theme, rows, maxRows, shouldHighlight ? language : void 0);
6409
+ const diffView = new AdaptiveDiffComponent(theme, rows, maxRows, shouldHighlight ? language : void 0);
6410
+ const expandHint2 = !expanded && diffView.hasCollapsed() ? "Ctrl+O more" : void 0;
6111
6411
  return renderBoxedToolResult(
6112
6412
  theme,
6113
6413
  {
6114
6414
  render(width) {
6115
- const safeWidth = Math.max(20, width);
6116
- const headerLines = new Text2(summary, 0, 0).render(safeWidth);
6117
- return [...headerLines, ...split.render(safeWidth)];
6415
+ return diffView.render(width);
6118
6416
  },
6119
6417
  invalidate() {
6120
- split.invalidate();
6418
+ diffView.invalidate();
6121
6419
  }
6122
6420
  },
6123
6421
  {
6124
- footerLines: [quickEditFooter(theme, context, output)]
6422
+ dividerLabel: quickEditDividerLabel(theme, stats),
6423
+ ...expandHint2 ? { dividerRightLabel: expandHint2 } : {},
6424
+ footerLines: [quickEditDiffFooter(theme, result, context, stats)]
6125
6425
  }
6126
6426
  );
6127
6427
  }
6428
+ function quickEditFooter(theme, context) {
6429
+ const elapsedMs = getStateElapsedMs(context.state);
6430
+ const parts = [];
6431
+ if (elapsedMs !== void 0) parts.push(theme.fg("text", formatElapsedMs(elapsedMs)));
6432
+ parts.push(theme.fg("dim", "1 file"));
6433
+ return parts.join(theme.fg("dim", " \xB7 "));
6434
+ }
6128
6435
  function quickEditTool(config) {
6129
6436
  return {
6130
6437
  call(args, theme, context) {
6131
6438
  noteExecutionStart(context);
6132
6439
  const detail = displayPath(String(args?.path ?? ""), context);
6133
- return renderBoxedToolCall(theme, config.toolLabel, [`${theme.fg("dim", "Path: ")}${detail}`], {
6440
+ return renderBoxedToolCall(theme, config.toolLabel, [], {
6441
+ headerDetail: detail,
6134
6442
  isError: Boolean(context.isError),
6135
6443
  isPartial: Boolean(context.isPartial),
6136
6444
  isPending: Boolean(context.isPartial)
@@ -7788,7 +8096,6 @@ function createPiStyleSessionCoordinator(pi, hooks = {}) {
7788
8096
  terminalInputUnsubscribe?.();
7789
8097
  terminalInputUnsubscribe = void 0;
7790
8098
  app.sessionShutdown();
7791
- compatibility.dispose();
7792
8099
  }
7793
8100
  };
7794
8101
  }