pi-cc-extensions 0.8.21 → 0.8.22

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.
@@ -29,12 +29,6 @@ import {
29
29
  wrapTextWithAnsi,
30
30
  } from "@earendil-works/pi-tui";
31
31
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
32
- import {
33
- bottomDialogBounds,
34
- isDialogCloseClick,
35
- renderDialogHeader,
36
- renderDialogTopBorder,
37
- } from "./closable-dialog.ts";
38
32
  import { homedir } from "node:os";
39
33
  import { join } from "node:path";
40
34
 
@@ -1992,8 +1986,6 @@ async function showCcstylePanel(ctx: any, compactStyle: CompactStyleHooks): Prom
1992
1986
  ];
1993
1987
 
1994
1988
  let activeSection = 0;
1995
- let lastWidth = 0;
1996
- let lastHeight = 0;
1997
1989
  const settingsTheme = getSettingsListTheme();
1998
1990
  const lists = sections.map(
1999
1991
  (section) =>
@@ -2030,15 +2022,8 @@ async function showCcstylePanel(ctx: any, compactStyle: CompactStyleHooks): Prom
2030
2022
  while (listBody.length > 0 && listBody[listBody.length - 1] === "") listBody.pop();
2031
2023
 
2032
2024
  // Frame: top rule · tabs · mid rule · settings · mid rule · footer · bottom rule
2033
- const lines = [
2034
- renderDialogTopBorder(safeWidth, (text) => theme.fg("border", text)),
2035
- renderDialogHeader(
2036
- " CC Style",
2037
- safeWidth,
2038
- (text) => theme.fg("border", text),
2039
- (text) => theme.fg("toolTitle", text),
2040
- (text) => theme.fg("muted", text),
2041
- ),
2025
+ return [
2026
+ rule,
2042
2027
  renderSectionTabBar(theme, sections, activeSection, safeWidth),
2043
2028
  rule,
2044
2029
  ...listBody,
@@ -2052,22 +2037,11 @@ async function showCcstylePanel(ctx: any, compactStyle: CompactStyleHooks): Prom
2052
2037
  ),
2053
2038
  rule,
2054
2039
  ];
2055
- lastWidth = safeWidth;
2056
- lastHeight = lines.length;
2057
- return lines;
2058
2040
  },
2059
2041
  invalidate() {
2060
2042
  for (const list of lists) list.invalidate();
2061
2043
  },
2062
2044
  handleInput(data: string) {
2063
- if (
2064
- !excludeSubmenuOpen &&
2065
- tui.terminal &&
2066
- isDialogCloseClick(data, bottomDialogBounds(tui.terminal, lastWidth, lastHeight))
2067
- ) {
2068
- done();
2069
- return;
2070
- }
2071
2045
  if (!excludeSubmenuOpen && isForwardTabKey(data)) {
2072
2046
  switchSection(1);
2073
2047
  tui.requestRender();
@@ -2118,15 +2092,22 @@ function toolCallArgument(args: any): { key: string; value: string } | undefined
2118
2092
  : undefined;
2119
2093
  }
2120
2094
 
2121
- function tailToWidth(text: string, width: number): string {
2095
+ export function middleTruncateToWidth(text: string, width: number): string {
2122
2096
  if (visibleWidth(text) <= width) return text;
2123
2097
  if (width <= 1) return "…";
2124
- let tail = "";
2125
- for (const char of Array.from(text).reverse()) {
2126
- if (visibleWidth(`…${char}${tail}`) > width) break;
2127
- tail = char + tail;
2098
+ const chars = Array.from(text);
2099
+ const leftWidth = Math.ceil((width - 1) / 2);
2100
+ let left = "";
2101
+ let right = "";
2102
+ for (const char of chars) {
2103
+ if (visibleWidth(left + char) > leftWidth) break;
2104
+ left += char;
2105
+ }
2106
+ for (const char of chars.reverse()) {
2107
+ if (visibleWidth(left + "…" + char + right) > width) break;
2108
+ right = char + right;
2128
2109
  }
2129
- return `…${tail}`;
2110
+ return `${left}…${right}`;
2130
2111
  }
2131
2112
 
2132
2113
  export function shouldRenderRichDiff(
@@ -2170,11 +2151,7 @@ function createCcstyleTool(
2170
2151
  return {
2171
2152
  render(width: number) {
2172
2153
  const argumentWidth = Math.max(1, width - visibleWidth(`${rawIcon} ${label}()`));
2173
- const shown = argument
2174
- ? argument.key === "path"
2175
- ? tailToWidth(argument.value, argumentWidth)
2176
- : truncateToWidth(argument.value, argumentWidth, "…")
2177
- : undefined;
2154
+ const shown = argument ? middleTruncateToWidth(argument.value, argumentWidth) : undefined;
2178
2155
  const call = shown === undefined ? label : `${label}(${shown})`;
2179
2156
  // Leave one cell unused so the host shell never performs a second,
2180
2157
  // unthemed truncation after this renderer has applied toolTitle.
@@ -10,12 +10,6 @@ import {
10
10
  visibleWidth,
11
11
  wrapTextWithAnsi,
12
12
  } from "@earendil-works/pi-tui";
13
- import {
14
- centeredDialogBounds,
15
- isDialogCloseClick,
16
- renderDialogHeader,
17
- renderDialogTopBorder,
18
- } from "./closable-dialog.ts";
19
13
 
20
14
  export type ContextPart = {
21
15
  label: string;
@@ -71,8 +65,6 @@ export async function showTextPreview(
71
65
  let scrollOffset = 0;
72
66
  let pageSize = 1;
73
67
  let totalLines = 1;
74
- let lastWidth = 0;
75
- let lastHeight = 0;
76
68
  const wrappedContent = createWrappedTextCache(content);
77
69
 
78
70
  const scrollTo = (nextOffset: number): void => {
@@ -85,10 +77,6 @@ export async function showTextPreview(
85
77
  wrappedContent.invalidate();
86
78
  },
87
79
  handleInput(data: string) {
88
- if (isDialogCloseClick(data, centeredDialogBounds(tui.terminal, lastWidth, lastHeight))) {
89
- done(undefined);
90
- return;
91
- }
92
80
  if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
93
81
  done(undefined);
94
82
  return;
@@ -150,24 +138,15 @@ export async function showTextPreview(
150
138
  const end = Math.min(totalLines, scrollOffset + pageSize);
151
139
  const status = `${start}-${end} / ${totalLines} lines · ↑↓ PgUp/PgDn Home/End · Esc close`;
152
140
 
153
- const lines = [
154
- renderDialogTopBorder(width, border),
155
- renderDialogHeader(
156
- ` ${title}`,
157
- width,
158
- border,
159
- (text) => theme.bold(theme.fg("accent", text)),
160
- (text) => theme.fg("muted", text),
161
- ),
141
+ return [
142
+ border(`╭${"─".repeat(inner)}╮`),
143
+ `${border("│")}${padLine(` ${theme.bold(theme.fg("accent", title))}`)}${border("│")}`,
162
144
  `${border("├")}${border("─".repeat(inner))}${border("┤")}`,
163
145
  ...bodyRows,
164
146
  `${border("├")}${border("─".repeat(inner))}${border("┤")}`,
165
147
  `${border("│")}${padLine(theme.fg("dim", ` ${status}`))}${border("│")}`,
166
148
  border(`╰${"─".repeat(inner)}╯`),
167
149
  ];
168
- lastWidth = width;
169
- lastHeight = lines.length;
170
- return lines;
171
150
  },
172
151
  };
173
152
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-cc-extensions",
3
- "version": "0.8.21",
3
+ "version": "0.8.22",
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": [
@@ -1,57 +0,0 @@
1
- import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
2
-
3
- export type DialogBounds = { left: number; top: number; width: number };
4
-
5
- export function renderDialogTopBorder(width: number, color: (text: string) => string): string {
6
- const safeWidth = Math.max(4, Math.floor(width));
7
- return color(`╭${"─".repeat(safeWidth - 2)}╮`);
8
- }
9
-
10
- /** Render title and a larger [ × ] target on the row below the top border. */
11
- export function renderDialogHeader(
12
- title: string,
13
- width: number,
14
- color: (text: string) => string,
15
- titleColor: (text: string) => string,
16
- closeColor: (text: string) => string,
17
- ): string {
18
- const safeWidth = Math.max(8, Math.floor(width));
19
- const innerWidth = safeWidth - 2;
20
- const button = "[ × ]";
21
- const titleWidth = Math.max(0, innerWidth - visibleWidth(button) - 2);
22
- const shownTitle = truncateToWidth(title, titleWidth, "…");
23
- const gap = " ".repeat(Math.max(1, innerWidth - visibleWidth(shownTitle) - visibleWidth(button)));
24
- return `${color("│")}${titleColor(shownTitle)}${gap}${closeColor(button)}${color("│")}`;
25
- }
26
-
27
- /** Match a left-button press anywhere on the [ × ] target. */
28
- export function isDialogCloseClick(data: string, bounds: DialogBounds): boolean {
29
- const match = data.match(/^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/);
30
- if (!match || match[4] !== "M") return false;
31
- const code = Number(match[1]);
32
- if ((code & 3) !== 0 || (code & 32) !== 0) return false;
33
- const col = Number(match[2]);
34
- const firstButtonCol = bounds.left + bounds.width - 7;
35
- const lastButtonCol = bounds.left + bounds.width - 3;
36
- return col >= firstButtonCol && col <= lastButtonCol && Number(match[3]) === bounds.top + 1;
37
- }
38
-
39
- export function centeredDialogBounds(
40
- terminal: { columns: number; rows: number },
41
- width: number,
42
- height: number,
43
- ): DialogBounds {
44
- return {
45
- left: Math.floor((terminal.columns - width) / 2) + 1,
46
- top: Math.floor((terminal.rows - height) / 2) + 1,
47
- width,
48
- };
49
- }
50
-
51
- export function bottomDialogBounds(
52
- terminal: { rows: number },
53
- width: number,
54
- height: number,
55
- ): DialogBounds {
56
- return { left: 1, top: Math.max(1, terminal.rows - height + 1), width };
57
- }