pi-x-ide 1.11.1 → 1.12.0

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/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-x-ide",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "description": "Pi extension package for IDE selection context integration.",
5
5
  "files": [
6
6
  "dist",
@@ -1,6 +1,13 @@
1
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent" with { "resolution-mode": "import" };
1
+ import type { ExtensionContext, ThemeColor } from "@earendil-works/pi-coding-agent" with {
2
+ "resolution-mode": "import"
3
+ };
2
4
  import type { PiIdeRuntime } from "./state";
5
+ interface StatusSegment {
6
+ text: string;
7
+ color: ThemeColor;
8
+ }
3
9
  export declare function updateIdeUi(runtime: PiIdeRuntime, ctx?: ExtensionContext | undefined): void;
4
10
  export declare function clearIdeUi(runtime: PiIdeRuntime, ctx?: ExtensionContext | undefined): void;
5
- export declare function buildStatusLine(runtime: PiIdeRuntime, cwd?: string): string;
11
+ export declare function buildStatusSegments(runtime: PiIdeRuntime, cwd?: string, spinnerFrame?: string): StatusSegment[];
6
12
  export declare function buildWidget(runtime: PiIdeRuntime, cwd?: string): string[] | undefined;
13
+ export {};
package/dist/src/pi/ui.js CHANGED
@@ -2,57 +2,112 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.updateIdeUi = updateIdeUi;
4
4
  exports.clearIdeUi = clearIdeUi;
5
- exports.buildStatusLine = buildStatusLine;
5
+ exports.buildStatusSegments = buildStatusSegments;
6
6
  exports.buildWidget = buildWidget;
7
7
  const format_1 = require("../shared/format");
8
8
  const paths_1 = require("../shared/paths");
9
+ const IDE_ICON = "⧉";
10
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
11
+ const SPINNER_INTERVAL_MS = 90;
9
12
  function updateIdeUi(runtime, ctx = runtime.ctx) {
10
13
  if (!ctx?.hasUI)
11
14
  return;
12
- ctx.ui.setWidget("pi-x-ide", (_tui, theme) => ({
13
- render(width) {
14
- const status = buildStatusLine(runtime, ctx.cwd);
15
- const text = truncatePlainStatus(status, width);
16
- const pad = " ".repeat(Math.max(0, width - text.length));
17
- const color = isPendingEditorContext(runtime) ? "success" : "dim";
18
- return [pad + theme.fg(color, text)];
19
- },
20
- invalidate() { },
21
- }), { placement: "aboveEditor" });
15
+ ctx.ui.setWidget("pi-x-ide", (tui, theme) => {
16
+ let frame = 0;
17
+ let timer;
18
+ // Drive the connecting spinner: tick a frame and request a redraw only while connecting.
19
+ const animate = (active) => {
20
+ if (active && !timer) {
21
+ timer = setInterval(() => {
22
+ frame = (frame + 1) % SPINNER_FRAMES.length;
23
+ tui.requestRender();
24
+ }, SPINNER_INTERVAL_MS);
25
+ timer.unref?.();
26
+ }
27
+ else if (!active && timer) {
28
+ clearInterval(timer);
29
+ timer = undefined;
30
+ }
31
+ };
32
+ return {
33
+ render(width) {
34
+ const connecting = runtime.enabled && runtime.connectionStatus === "connecting";
35
+ animate(connecting);
36
+ const segments = buildStatusSegments(runtime, ctx.cwd, SPINNER_FRAMES[frame]);
37
+ if (segments.length === 0 || width <= 0)
38
+ return [];
39
+ const visible = truncateSegments(segments, width);
40
+ const plainLength = visible.reduce((sum, segment) => sum + segment.text.length, 0);
41
+ const pad = " ".repeat(Math.max(0, width - plainLength));
42
+ return [pad + visible.map((segment) => theme.fg(segment.color, segment.text)).join("")];
43
+ },
44
+ invalidate() { },
45
+ dispose() {
46
+ animate(false);
47
+ },
48
+ };
49
+ }, { placement: "aboveEditor" });
22
50
  }
23
51
  function clearIdeUi(runtime, ctx = runtime.ctx) {
24
52
  if (!ctx?.hasUI)
25
53
  return;
26
54
  ctx.ui.setWidget("pi-x-ide", undefined);
27
55
  }
28
- function truncatePlainStatus(text, width) {
29
- if (width <= 0)
30
- return "";
31
- if (text.length <= width)
32
- return text;
56
+ // Truncates colored segments to the given width, appending an ellipsis when content overflows.
57
+ function truncateSegments(segments, width) {
58
+ const total = segments.reduce((sum, segment) => sum + segment.text.length, 0);
59
+ if (total <= width)
60
+ return segments;
61
+ const fallbackColor = segments[0]?.color ?? "dim";
33
62
  if (width <= 3)
34
- return ".".repeat(width);
35
- return `${text.slice(0, width - 3)}...`;
63
+ return [{ text: ".".repeat(width), color: fallbackColor }];
64
+ const limit = width - 3;
65
+ const visible = [];
66
+ let used = 0;
67
+ for (const segment of segments) {
68
+ if (used >= limit)
69
+ break;
70
+ const room = limit - used;
71
+ if (segment.text.length <= room) {
72
+ visible.push(segment);
73
+ used += segment.text.length;
74
+ }
75
+ else {
76
+ visible.push({ text: segment.text.slice(0, room), color: segment.color });
77
+ used = limit;
78
+ }
79
+ }
80
+ visible.push({ text: "...", color: visible[visible.length - 1]?.color ?? fallbackColor });
81
+ return visible;
36
82
  }
37
- function isPendingEditorContext(runtime) {
38
- return !!runtime.latestSelection && runtime.attachState === "pending";
83
+ function buildStatusSegments(runtime, cwd, spinnerFrame = SPINNER_FRAMES[0]) {
84
+ if (!runtime.enabled)
85
+ return [];
86
+ switch (runtime.connectionStatus) {
87
+ case "connecting":
88
+ return [{ text: `${IDE_ICON} ${spinnerFrame}`, color: "dim" }];
89
+ case "error":
90
+ return [
91
+ { text: `${IDE_ICON} `, color: "dim" },
92
+ { text: "✕", color: "error" },
93
+ ];
94
+ case "connected":
95
+ return buildConnectedSegments(runtime, cwd);
96
+ default:
97
+ return [];
98
+ }
39
99
  }
40
- function buildStatusLine(runtime, cwd) {
41
- if (!runtime.enabled || runtime.connectionStatus === "disabled")
42
- return "Pi x IDE: off";
43
- if (runtime.connectionStatus === "connecting")
44
- return "Pi x IDE: connecting";
45
- if (runtime.connectionStatus === "error")
46
- return `Pi x IDE: error${runtime.connectionMessage ? ` ${runtime.connectionMessage}` : ""}`;
47
- if (runtime.connectionStatus !== "connected")
48
- return "Pi x IDE: disconnected";
49
- const ide = runtime.connectedServer?.ide ?? runtime.currentCandidate?.lock.ide ?? "ide";
100
+ function buildConnectedSegments(runtime, cwd) {
50
101
  const selection = runtime.latestSelection;
51
102
  if (!selection)
52
- return `${ide} ✓`;
103
+ return [{ text: `${IDE_ICON} ✓`, color: "dim" }];
53
104
  const rel = (0, paths_1.toRelativeDisplayPath)(selection.filePath, selection.workspaceFolder, cwd);
54
105
  const range = (0, format_1.describeRanges)(selection.ranges);
55
- return `${ide} ${rel}${range === "open file" ? "" : range} ${runtime.attachState}`;
106
+ // Pending selections are still queued for the next prompt (⇡, success); sent ones are attached (✓, dim).
107
+ const pending = runtime.attachState === "pending";
108
+ const marker = pending ? "⇡" : "✓";
109
+ const color = pending ? "success" : "dim";
110
+ return [{ text: `${IDE_ICON} ${marker} ${rel}${range === "open file" ? "" : range}`, color }];
56
111
  }
57
112
  function buildWidget(runtime, cwd) {
58
113
  if (!runtime.enabled || runtime.connectionStatus === "disabled")
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../src/pi/ui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../src/pi/ui.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-x-ide",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "description": "Pi extension package for IDE selection context integration.",
5
5
  "files": [
6
6
  "dist",
package/src/pi/ui.ts CHANGED
@@ -1,23 +1,59 @@
1
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent" with { "resolution-mode": "import" };
1
+ import type { ExtensionContext, ThemeColor } from "@earendil-works/pi-coding-agent" with {
2
+ "resolution-mode": "import",
3
+ };
2
4
  import { describeRanges } from "../shared/format";
3
5
  import { toRelativeDisplayPath } from "../shared/paths";
4
6
  import type { PiIdeRuntime } from "./state";
5
7
 
8
+ const IDE_ICON = "⧉";
9
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
10
+ const SPINNER_INTERVAL_MS = 90;
11
+
12
+ interface StatusSegment {
13
+ text: string;
14
+ color: ThemeColor;
15
+ }
16
+
6
17
  export function updateIdeUi(runtime: PiIdeRuntime, ctx: ExtensionContext | undefined = runtime.ctx): void {
7
18
  if (!ctx?.hasUI) return;
8
19
 
9
20
  ctx.ui.setWidget(
10
21
  "pi-x-ide",
11
- (_tui, theme) => ({
12
- render(width: number): string[] {
13
- const status = buildStatusLine(runtime, ctx.cwd);
14
- const text = truncatePlainStatus(status, width);
15
- const pad = " ".repeat(Math.max(0, width - text.length));
16
- const color = isPendingEditorContext(runtime) ? "success" : "dim";
17
- return [pad + theme.fg(color, text)];
18
- },
19
- invalidate() {},
20
- }),
22
+ (tui, theme) => {
23
+ let frame = 0;
24
+ let timer: ReturnType<typeof setInterval> | undefined;
25
+
26
+ // Drive the connecting spinner: tick a frame and request a redraw only while connecting.
27
+ const animate = (active: boolean): void => {
28
+ if (active && !timer) {
29
+ timer = setInterval(() => {
30
+ frame = (frame + 1) % SPINNER_FRAMES.length;
31
+ tui.requestRender();
32
+ }, SPINNER_INTERVAL_MS);
33
+ timer.unref?.();
34
+ } else if (!active && timer) {
35
+ clearInterval(timer);
36
+ timer = undefined;
37
+ }
38
+ };
39
+
40
+ return {
41
+ render(width: number): string[] {
42
+ const connecting = runtime.enabled && runtime.connectionStatus === "connecting";
43
+ animate(connecting);
44
+ const segments = buildStatusSegments(runtime, ctx.cwd, SPINNER_FRAMES[frame]);
45
+ if (segments.length === 0 || width <= 0) return [];
46
+ const visible = truncateSegments(segments, width);
47
+ const plainLength = visible.reduce((sum, segment) => sum + segment.text.length, 0);
48
+ const pad = " ".repeat(Math.max(0, width - plainLength));
49
+ return [pad + visible.map((segment) => theme.fg(segment.color, segment.text)).join("")];
50
+ },
51
+ invalidate() {},
52
+ dispose() {
53
+ animate(false);
54
+ },
55
+ };
56
+ },
21
57
  { placement: "aboveEditor" },
22
58
  );
23
59
  }
@@ -27,30 +63,64 @@ export function clearIdeUi(runtime: PiIdeRuntime, ctx: ExtensionContext | undefi
27
63
  ctx.ui.setWidget("pi-x-ide", undefined);
28
64
  }
29
65
 
30
- function truncatePlainStatus(text: string, width: number): string {
31
- if (width <= 0) return "";
32
- if (text.length <= width) return text;
33
- if (width <= 3) return ".".repeat(width);
34
- return `${text.slice(0, width - 3)}...`;
35
- }
66
+ // Truncates colored segments to the given width, appending an ellipsis when content overflows.
67
+ function truncateSegments(segments: StatusSegment[], width: number): StatusSegment[] {
68
+ const total = segments.reduce((sum, segment) => sum + segment.text.length, 0);
69
+ if (total <= width) return segments;
70
+ const fallbackColor = segments[0]?.color ?? "dim";
71
+ if (width <= 3) return [{ text: ".".repeat(width), color: fallbackColor }];
36
72
 
37
- function isPendingEditorContext(runtime: PiIdeRuntime): boolean {
38
- return !!runtime.latestSelection && runtime.attachState === "pending";
73
+ const limit = width - 3;
74
+ const visible: StatusSegment[] = [];
75
+ let used = 0;
76
+ for (const segment of segments) {
77
+ if (used >= limit) break;
78
+ const room = limit - used;
79
+ if (segment.text.length <= room) {
80
+ visible.push(segment);
81
+ used += segment.text.length;
82
+ } else {
83
+ visible.push({ text: segment.text.slice(0, room), color: segment.color });
84
+ used = limit;
85
+ }
86
+ }
87
+ visible.push({ text: "...", color: visible[visible.length - 1]?.color ?? fallbackColor });
88
+ return visible;
39
89
  }
40
90
 
41
- export function buildStatusLine(runtime: PiIdeRuntime, cwd?: string): string {
42
- if (!runtime.enabled || runtime.connectionStatus === "disabled") return "Pi x IDE: off";
43
- if (runtime.connectionStatus === "connecting") return "Pi x IDE: connecting";
44
- if (runtime.connectionStatus === "error")
45
- return `Pi x IDE: error${runtime.connectionMessage ? ` ${runtime.connectionMessage}` : ""}`;
46
- if (runtime.connectionStatus !== "connected") return "Pi x IDE: disconnected";
91
+ export function buildStatusSegments(
92
+ runtime: PiIdeRuntime,
93
+ cwd?: string,
94
+ spinnerFrame = SPINNER_FRAMES[0],
95
+ ): StatusSegment[] {
96
+ if (!runtime.enabled) return [];
47
97
 
48
- const ide = runtime.connectedServer?.ide ?? runtime.currentCandidate?.lock.ide ?? "ide";
98
+ switch (runtime.connectionStatus) {
99
+ case "connecting":
100
+ return [{ text: `${IDE_ICON} ${spinnerFrame}`, color: "dim" }];
101
+ case "error":
102
+ return [
103
+ { text: `${IDE_ICON} `, color: "dim" },
104
+ { text: "✕", color: "error" },
105
+ ];
106
+ case "connected":
107
+ return buildConnectedSegments(runtime, cwd);
108
+ default:
109
+ return [];
110
+ }
111
+ }
112
+
113
+ function buildConnectedSegments(runtime: PiIdeRuntime, cwd?: string): StatusSegment[] {
49
114
  const selection = runtime.latestSelection;
50
- if (!selection) return `${ide} ✓`;
115
+ if (!selection) return [{ text: `${IDE_ICON} ✓`, color: "dim" }];
116
+
51
117
  const rel = toRelativeDisplayPath(selection.filePath, selection.workspaceFolder, cwd);
52
118
  const range = describeRanges(selection.ranges);
53
- return `${ide} ${rel}${range === "open file" ? "" : range} ${runtime.attachState}`;
119
+ // Pending selections are still queued for the next prompt (⇡, success); sent ones are attached (✓, dim).
120
+ const pending = runtime.attachState === "pending";
121
+ const marker = pending ? "⇡" : "✓";
122
+ const color: ThemeColor = pending ? "success" : "dim";
123
+ return [{ text: `${IDE_ICON} ${marker} ${rel}${range === "open file" ? "" : range}`, color }];
54
124
  }
55
125
 
56
126
  export function buildWidget(runtime: PiIdeRuntime, cwd?: string): string[] | undefined {