pi-cc-extensions 0.8.25 → 0.8.26

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.
@@ -2,14 +2,9 @@ import {
2
2
  type ExtensionAPI,
3
3
  type ExtensionCommandContext,
4
4
  estimateTokens,
5
+ getMarkdownTheme,
5
6
  } from "@earendil-works/pi-coding-agent";
6
- import {
7
- Key,
8
- matchesKey,
9
- truncateToWidth,
10
- visibleWidth,
11
- wrapTextWithAnsi,
12
- } from "@earendil-works/pi-tui";
7
+ import { Key, Markdown, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
13
8
 
14
9
  export type ContextPart = {
15
10
  label: string;
@@ -33,24 +28,18 @@ function normalizePreviewText(text: string): string {
33
28
  .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "");
34
29
  }
35
30
 
36
- export function createWrappedTextCache(content: string): {
37
- get(width: number): string[];
38
- invalidate(): void;
39
- } {
40
- let cachedWidth: number | undefined;
41
- let cachedLines: string[] | undefined;
31
+ export type DialogBounds = { left: number; top: number; width: number };
42
32
 
33
+ /** 1-based terminal hitbox of the [esc] close button on the dialog title row (row 2 of the box). */
34
+ export function escCloseHitbox(bounds: DialogBounds): {
35
+ row: number;
36
+ startCol: number;
37
+ endCol: number;
38
+ } {
43
39
  return {
44
- get(width: number): string[] {
45
- if (cachedLines !== undefined && cachedWidth === width) return cachedLines;
46
- cachedLines = wrapTextWithAnsi(content, width);
47
- cachedWidth = width;
48
- return cachedLines;
49
- },
50
- invalidate(): void {
51
- cachedWidth = undefined;
52
- cachedLines = undefined;
53
- },
40
+ row: bounds.top + 2,
41
+ startCol: bounds.left + bounds.width - 5,
42
+ endCol: bounds.left + bounds.width - 1,
54
43
  };
55
44
  }
56
45
 
@@ -65,7 +54,8 @@ export async function showTextPreview(
65
54
  let scrollOffset = 0;
66
55
  let pageSize = 1;
67
56
  let totalLines = 1;
68
- const wrappedContent = createWrappedTextCache(content);
57
+ let escHitbox: { row: number; startCol: number; endCol: number } | undefined;
58
+ const markdownView = new Markdown(content, 0, 0, getMarkdownTheme());
69
59
 
70
60
  const scrollTo = (nextOffset: number): void => {
71
61
  scrollOffset = Math.max(0, Math.min(nextOffset, Math.max(0, totalLines - pageSize)));
@@ -74,7 +64,7 @@ export async function showTextPreview(
74
64
 
75
65
  return {
76
66
  invalidate() {
77
- wrappedContent.invalidate();
67
+ markdownView.invalidate();
78
68
  },
79
69
  handleInput(data: string) {
80
70
  if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
@@ -91,12 +81,24 @@ export async function showTextPreview(
91
81
  const mouse = parseSgrMousePacket(data);
92
82
  if (mouse?.final !== "M") return;
93
83
  const button = mouseBaseButton(mouse.code);
84
+ if (button === 0 && (mouse.code & 32) === 0) {
85
+ if (
86
+ escHitbox &&
87
+ mouse.row === escHitbox.row &&
88
+ mouse.col >= escHitbox.startCol &&
89
+ mouse.col <= escHitbox.endCol
90
+ ) {
91
+ done(undefined);
92
+ return;
93
+ }
94
+ }
94
95
  if (button === 64) scrollTo(scrollOffset - 3);
95
96
  else if (button === 65) scrollTo(scrollOffset + 3);
96
97
  }
97
98
  },
98
99
  render(width: number) {
99
100
  const inner = Math.max(1, width - 2);
101
+ const escWidth = visibleWidth("[esc]");
100
102
  const bodyInner = Math.max(1, inner - 1);
101
103
  const bodyWidth = Math.max(1, bodyInner - 1);
102
104
  const terminalHeight = Math.max(1, tui.terminal.rows);
@@ -107,9 +109,13 @@ export async function showTextPreview(
107
109
  availableHeight,
108
110
  );
109
111
  pageSize = Math.max(1, viewportHeight - 6);
110
- const wrapped = wrappedContent.get(bodyWidth);
112
+ const wrapped = markdownView.render(bodyWidth);
111
113
  totalLines = wrapped.length;
112
114
  scrollOffset = Math.min(scrollOffset, Math.max(0, totalLines - pageSize));
115
+ // Centered overlay with margin 2: mirror TUI resolveOverlayLayout for anchor "center".
116
+ const overlayTop = 2 + Math.floor((availableHeight - viewportHeight) / 2);
117
+ const overlayLeft = Math.floor((Math.max(1, tui.terminal.columns) - width) / 2);
118
+ escHitbox = escCloseHitbox({ left: overlayLeft, top: overlayTop, width });
113
119
  const visible = wrapped.slice(scrollOffset, scrollOffset + pageSize);
114
120
  const border = (text: string) => theme.fg("border", text);
115
121
  const padLine = (text: string, lineWidth = inner): string => {
@@ -136,11 +142,11 @@ export async function showTextPreview(
136
142
  });
137
143
  const start = totalLines === 0 ? 0 : scrollOffset + 1;
138
144
  const end = Math.min(totalLines, scrollOffset + pageSize);
139
- const status = `${start}-${end} / ${totalLines} lines · ↑↓ PgUp/PgDn Home/End · Esc close`;
145
+ const status = `${start}-${end} / ${totalLines} lines · ↑↓ PgUp/PgDn Home/End · [esc] close`;
140
146
 
141
147
  return [
142
148
  border(`╭${"─".repeat(inner)}╮`),
143
- `${border("│")}${padLine(` ${theme.bold(theme.fg("accent", title))}`)}${border("│")}`,
149
+ `${border("│")}${padLine(` ${theme.bold(theme.fg("accent", title))}`, inner - escWidth)}${theme.fg("muted", "[esc]")}${border("│")}`,
144
150
  `${border("├")}${border("─".repeat(inner))}${border("┤")}`,
145
151
  ...bodyRows,
146
152
  `${border("├")}${border("─".repeat(inner))}${border("┤")}`,
@@ -392,6 +398,7 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
392
398
  startCol: number;
393
399
  endCol: number;
394
400
  }> = [];
401
+ let escHitbox: { row: number; startCol: number; endCol: number } | undefined;
395
402
 
396
403
  const padLine = (text: string, width: number): string => {
397
404
  const truncated = truncateToWidth(text, width, "…");
@@ -428,6 +435,15 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
428
435
  (mouse.code & 32) !== 0
429
436
  )
430
437
  return;
438
+ if (
439
+ escHitbox &&
440
+ mouse.row === escHitbox.row &&
441
+ mouse.col >= escHitbox.startCol &&
442
+ mouse.col <= escHitbox.endCol
443
+ ) {
444
+ done(undefined);
445
+ return;
446
+ }
431
447
  const hitbox = previewHitboxes.find(
432
448
  (candidate) =>
433
449
  mouse.row === candidate.row &&
@@ -444,6 +460,7 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
444
460
  },
445
461
  render(width: number) {
446
462
  const inner = Math.max(1, width - 2);
463
+ const escWidth = visibleWidth("[esc]");
447
464
  const percent = contextWindow > 0 ? (used / contextWindow) * 100 : 0;
448
465
  const title = theme.bold(theme.fg("accent", "Context Usage"));
449
466
  const subtitle = `${formatTokens(used)} / ${formatTokens(contextWindow)} tokens (${percent.toFixed(1)}%)`;
@@ -480,13 +497,13 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
480
497
  const border = (text: string) => theme.fg("border", text);
481
498
  const lines = [
482
499
  border(`╭${"─".repeat(inner)}╮`),
483
- `${border("│")}${padLine(` ${title} ${theme.fg("muted", subtitle)}`, inner)}${border("│")}`,
500
+ `${border("│")}${padLine(` ${title} ${theme.fg("muted", subtitle)}`, inner - escWidth)}${theme.fg("muted", "[esc]")}${border("│")}`,
484
501
  `${border("├")}${border("─".repeat(inner))}${border("┤")}`,
485
502
  `${border("│")}${padLine(` ${segments}`, inner)}${border("│")}`,
486
503
  `${border("│")}${" ".repeat(inner)}${border("│")}`,
487
504
  ...partRows.map((row) => `${border("│")}${row}${border("│")}`),
488
505
  `${border("├")}${border("─".repeat(inner))}${border("┤")}`,
489
- `${border("│")}${padLine(theme.fg("dim", " ↑↓ select · Click / Enter to preview · Esc to close"), inner)}${border("│")}`,
506
+ `${border("│")}${padLine(theme.fg("dim", " ↑↓ select · Click / Enter to preview · [esc] close"), inner)}${border("│")}`,
490
507
  border(`╰${"─".repeat(inner)}╯`),
491
508
  ];
492
509
 
@@ -499,6 +516,7 @@ export default function contextUsageExtension(pi: ExtensionAPI) {
499
516
  const overlayTop =
500
517
  1 + Math.floor((Math.max(1, terminalHeight - 2) - visibleHeight) / 2);
501
518
  const overlayLeft = Math.floor((Math.max(1, tui.terminal.columns) - width) / 2);
519
+ escHitbox = escCloseHitbox({ left: overlayLeft, top: overlayTop, width });
502
520
  previewHitboxes = visiblePreviews.flatMap((preview) => {
503
521
  const partIndex = allParts.findIndex((part) => part.label === preview.label);
504
522
  const line = 5 + partIndex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "0.8.25",
3
+ "version": "0.8.26",
4
4
  "description": "A Pi productivity suite with Claude Code-style UI, structured questionnaires, context inspection, and agent/session references",
5
5
  "type": "module",
6
6
  "keywords": [