pi-open-tui 0.3.8 → 0.3.9

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/README.md CHANGED
@@ -19,7 +19,7 @@ A polished terminal interface for the [Pi](https://pi.dev) coding agent. It brin
19
19
 
20
20
  ## Requirements
21
21
 
22
- - Pi 0.80 or later
22
+ - Pi 0.85 or later
23
23
  - A terminal with UTF-8 and color support
24
24
  - A [Nerd Font](https://www.nerdfonts.com/font-downloads) for the full icon set (optional; ASCII icons are built in)
25
25
 
package/README.zh-CN.md CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  ## 环境要求
21
21
 
22
- - Pi 0.80 或更高版本
22
+ - Pi 0.85 或更高版本
23
23
  - 支持 UTF-8 和彩色输出的终端
24
24
  - 使用完整图标集时需要 [Nerd Font](https://www.nerdfonts.com/font-downloads)(可选;内置 ASCII 图标)
25
25
 
@@ -4,7 +4,7 @@ import {
4
4
  type ExtensionContext,
5
5
  type KeybindingsManager,
6
6
  } from "@earendil-works/pi-coding-agent";
7
- import type { EditorTheme, TUI } from "@earendil-works/pi-tui";
7
+ import type { EditorTheme, TuiMouseEvent, TuiMouseEventResult, TUI } from "@earendil-works/pi-tui";
8
8
  import { CURSOR_MARKER, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
9
9
  import type { CursorStyle } from "./config.ts";
10
10
  import {
@@ -24,6 +24,8 @@ const CURSOR_STYLE_SEQUENCES: Partial<Record<CursorStyle, string>> = {
24
24
  underline: "\x1b[4 q",
25
25
  };
26
26
  const DEFAULT_CURSOR_STYLE_SEQUENCE = "\x1b[0 q";
27
+ const EDITOR_FRAME_LEFT_INSET = 2;
28
+ const EDITOR_FRAME_HORIZONTAL_CHROME = EDITOR_FRAME_LEFT_INSET * 2;
27
29
 
28
30
  interface WorkingStatusIndicator {
29
31
  renderInBorder(width: number): string;
@@ -221,6 +223,16 @@ export class OpenTuiEditor extends CustomEditor {
221
223
  super.setPaddingX(0);
222
224
  }
223
225
 
226
+ override handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
227
+ if (event.width < EDITOR_FRAME_HORIZONTAL_CHROME) return super.handleMouse(event);
228
+
229
+ return super.handleMouse({
230
+ ...event,
231
+ x: event.x - EDITOR_FRAME_LEFT_INSET,
232
+ width: event.width - EDITOR_FRAME_HORIZONTAL_CHROME,
233
+ });
234
+ }
235
+
224
236
  setWorkingStatusIndicator(indicator: WorkingStatusIndicator | undefined): void {
225
237
  this.embeddedWorkingStatusIndicator = indicator;
226
238
  this.tui.requestRender();
@@ -262,12 +274,12 @@ export class OpenTuiEditor extends CustomEditor {
262
274
  }
263
275
 
264
276
  render(width: number): string[] {
265
- if (width < 4) return this.renderBase(width);
277
+ if (width < EDITOR_FRAME_HORIZONTAL_CHROME) return this.renderBase(width);
266
278
 
267
279
  const rail = this.getRail();
268
280
  const borderPaint = this.getBorder;
269
281
  // ponytail: 1-char rail + 1-char gap on each side = 4 chars of chrome.
270
- const innerWidth = Math.max(0, width - 4);
282
+ const innerWidth = Math.max(0, width - EDITOR_FRAME_HORIZONTAL_CHROME);
271
283
  const baseLines = this.renderBase(innerWidth);
272
284
  const bottomIdx = findBottomBorderIndex(baseLines);
273
285
 
@@ -43,23 +43,28 @@ export function truncateBranch(branch: string, maxLen: number): string {
43
43
  }
44
44
 
45
45
  export function truncatePath(path: string, maxLen: number): string {
46
- if (path.length <= maxLen) return path;
46
+ // `maxLen` is a terminal-column budget, so every comparison here must use
47
+ // display width. Comparing String.length let wide (CJK) paths stay wider than
48
+ // the budget: for a basename of 4 characters but 8 columns, `truncatePath`
49
+ // returned it unchanged, so fitSegmentsByPriority's `while (totalW() > maxW)`
50
+ // loop never made progress and livelocked the renderer at 100% CPU.
51
+ if (visibleWidth(path) <= maxLen) return path;
47
52
  if (maxLen <= 3) return "...".slice(0, maxLen);
48
53
  const sepChar = path.includes("/") ? "/" : "\\";
49
54
  const parts = path.split(/[\\/]/);
50
- if (parts.length <= 2) return path.slice(0, maxLen - 3) + "...";
55
+ if (parts.length <= 2) return truncateToWidth(path, maxLen, "...");
51
56
  // Keep first segment (e.g. ~) and as many trailing segments as fit.
52
57
  const tail: string[] = [];
53
58
  let tailLen = 0;
54
59
  for (let i = parts.length - 1; i >= 1; i--) {
55
60
  const seg = parts[i]!;
56
- if (tailLen + seg.length + 4 > maxLen) break;
61
+ if (tailLen + visibleWidth(seg) + 4 > maxLen) break;
57
62
  tail.unshift(seg);
58
- tailLen += seg.length + 1;
63
+ tailLen += visibleWidth(seg) + 1;
59
64
  }
60
65
  const head = parts[0]!;
61
66
  const result = `${head}${sepChar}...${sepChar}${tail.join(sepChar)}`;
62
- return result.length > maxLen ? result.slice(0, maxLen - 3) + "..." : result;
67
+ return visibleWidth(result) > maxLen ? truncateToWidth(result, maxLen, "...") : result;
63
68
  }
64
69
 
65
70
  export function finiteOrZero(value: unknown): number {
@@ -158,7 +163,25 @@ export function fitSegmentsByPriority(
158
163
  if (totalW() <= maxW) break;
159
164
  }
160
165
  }
166
+ // A segment whose `truncate` cannot actually shrink would otherwise spin here
167
+ // forever and block the render timer. Require each pass to make progress, and
168
+ // drop the worst segment when it does not, so layout always terminates.
169
+ let lastTotal = Infinity;
161
170
  while (totalW() > maxW) {
171
+ const nowTotal = totalW();
172
+ if (nowTotal >= lastTotal) {
173
+ let worst = -1;
174
+ for (let i = 0; i < items.length; i++) {
175
+ if (items[i].text !== "" && (worst === -1 || items[i].priority < items[worst].priority)) {
176
+ worst = i;
177
+ }
178
+ }
179
+ if (worst === -1) break;
180
+ items[worst].text = "";
181
+ items[worst].w = 0;
182
+ continue;
183
+ }
184
+ lastTotal = nowTotal;
162
185
  let target = -1;
163
186
  for (let i = 0; i < items.length; i++) {
164
187
  if (items[i].text !== "" && (target === -1 || items[i].priority < items[target].priority)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-open-tui",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "A polished TUI for Pi coding agent: animated logo header, Starship-style footer, rounded editor with model metadata, and prompt-box user messages.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -35,9 +35,9 @@
35
35
  "image": "https://raw.githubusercontent.com/OldSuns/pi-open-tui/main/assets/preview_dashboard_1.png"
36
36
  },
37
37
  "peerDependencies": {
38
- "@earendil-works/pi-coding-agent": ">=0.81",
39
- "@earendil-works/pi-ai": ">=0.81",
40
- "@earendil-works/pi-tui": ">=0.81"
38
+ "@earendil-works/pi-coding-agent": ">=0.85",
39
+ "@earendil-works/pi-ai": ">=0.85",
40
+ "@earendil-works/pi-tui": ">=0.85"
41
41
  },
42
42
  "peerDependenciesMeta": {
43
43
  "@earendil-works/pi-coding-agent": {