vscode-pi-companion 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.
- package/.vscodeignore +4 -2
- package/extensions/index.ts +179 -147
- package/package.json +14 -2
- package/src/extension.ts +29 -0
package/.vscodeignore
CHANGED
package/extensions/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PI Companion Extension
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Connects to the VS Code extension via SSE for real-time context updates.
|
|
5
|
+
* No polling, instant updates, no tool calls visible.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
@@ -12,11 +13,78 @@ import * as http from "http";
|
|
|
12
13
|
import { execSync } from "child_process";
|
|
13
14
|
import { Type } from "@sinclair/typebox";
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
function isLocalPackageInstall(): boolean {
|
|
17
|
+
const filename = __filename;
|
|
18
|
+
return !filename.includes(`${path.sep}.pi${path.sep}agent${path.sep}git${path.sep}`)
|
|
19
|
+
&& !filename.includes(`${path.sep}.pi${path.sep}git${path.sep}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Check if running in VS Code integrated terminal
|
|
23
|
+
function isRunningInVSCode(): boolean {
|
|
24
|
+
return process.env.TERM_PROGRAM === 'vscode';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Install VS Code extension from marketplace
|
|
28
|
+
function installFromMarketplace(ctx: ExtensionContext): void {
|
|
29
|
+
try {
|
|
30
|
+
execSync('code --install-extension ravshansbox.vscode-pi-companion --force', { stdio: 'pipe' });
|
|
31
|
+
ctx.ui.notify("PI Companion: VS Code extension installed. Reload window to activate.", "info");
|
|
32
|
+
} catch {
|
|
33
|
+
// Silently ignore
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if extension is installed
|
|
38
|
+
function isExtensionInstalled(): boolean {
|
|
39
|
+
try {
|
|
40
|
+
const result = execSync('code --list-extensions', { encoding: 'utf8' });
|
|
41
|
+
return result.includes('ravshansbox.vscode-pi-companion');
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Install/update VS Code extension if running in VS Code terminal
|
|
48
|
+
async function installVSCodeExtension(ctx: ExtensionContext): Promise<void> {
|
|
49
|
+
if (!isRunningInVSCode()) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const isInstalled = isExtensionInstalled();
|
|
54
|
+
|
|
55
|
+
if (isInstalled) {
|
|
56
|
+
// Already installed - check for updates, but skip for local package installs
|
|
57
|
+
if (!isLocalPackageInstall()) {
|
|
58
|
+
const latestVersion = await getLatestVersion();
|
|
59
|
+
if (latestVersion) {
|
|
60
|
+
console.log(`[PI Companion] Update available: v${latestVersion}`);
|
|
61
|
+
console.log(`[PI Companion] Run: pi install git:github.com/ravshansbox/vscode-pi-companion`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Not installed - install from marketplace
|
|
68
|
+
installFromMarketplace(ctx);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get latest version from GitHub
|
|
72
|
+
async function getLatestVersion(): Promise<string | null> {
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch('https://api.github.com/repos/ravshansbox/vscode-pi-companion/releases/latest', {
|
|
75
|
+
headers: { 'Accept': 'application/vnd.github.v3+json' }
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const release = await response.json() as { tag_name?: string };
|
|
83
|
+
return release.tag_name?.replace('v', '') || null;
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
20
88
|
|
|
21
89
|
interface IdeContext {
|
|
22
90
|
workspaceState: {
|
|
@@ -35,12 +103,6 @@ interface IdeContext {
|
|
|
35
103
|
};
|
|
36
104
|
}
|
|
37
105
|
|
|
38
|
-
interface ConnectionInfo {
|
|
39
|
-
port: number;
|
|
40
|
-
workspacePath: string;
|
|
41
|
-
authToken: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
106
|
let currentContext: IdeContext | undefined;
|
|
45
107
|
let currentCtx: ExtensionContext | undefined;
|
|
46
108
|
let lastWidgetKey = "";
|
|
@@ -48,95 +110,34 @@ let widgetDebounceTimer: NodeJS.Timeout | undefined;
|
|
|
48
110
|
let reconnectTimer: NodeJS.Timeout | undefined;
|
|
49
111
|
let sseRequest: http.ClientRequest | undefined;
|
|
50
112
|
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
}
|
|
113
|
+
function getPort(): number | undefined {
|
|
114
|
+
const configDir = path.join(os.tmpdir(), "pi-companion");
|
|
54
115
|
|
|
55
|
-
function isExtensionInstalled(): boolean {
|
|
56
116
|
try {
|
|
57
|
-
const
|
|
58
|
-
return result.includes("ravshansbox.vscode-pi-companion");
|
|
59
|
-
} catch {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function installFromMarketplace(ctx: ExtensionContext): void {
|
|
65
|
-
try {
|
|
66
|
-
execSync("code --install-extension ravshansbox.vscode-pi-companion --force", { stdio: "pipe" });
|
|
67
|
-
ctx.ui.notify("PI Companion: VS Code extension installed. Reload window to activate.", "info");
|
|
68
|
-
} catch {
|
|
69
|
-
ctx.ui.notify("PI Companion: Failed to auto-install VS Code extension.", "warning");
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function ensureVSCodeExtensionInstalled(ctx: ExtensionContext): void {
|
|
74
|
-
if (!isRunningInVSCode()) return;
|
|
75
|
-
if (!isExtensionInstalled()) {
|
|
76
|
-
installFromMarketplace(ctx);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function readLatestConnectionInfo(): ConnectionInfo | undefined {
|
|
81
|
-
try {
|
|
82
|
-
const files = fs.readdirSync(CONNECTION_DIR);
|
|
117
|
+
const files = fs.readdirSync(configDir);
|
|
83
118
|
const connectionFiles = files
|
|
84
119
|
.filter((f) => f.startsWith("connection-") && f.endsWith(".json"))
|
|
85
120
|
.map((f) => ({
|
|
86
|
-
|
|
87
|
-
|
|
121
|
+
name: f,
|
|
122
|
+
path: path.join(configDir, f),
|
|
123
|
+
mtime: fs.statSync(path.join(configDir, f)).mtime.getTime(),
|
|
88
124
|
}))
|
|
89
125
|
.sort((a, b) => b.mtime - a.mtime);
|
|
90
126
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (typeof info.port === "number" && info.port > 0) {
|
|
96
|
-
return info;
|
|
97
|
-
}
|
|
98
|
-
} catch {
|
|
99
|
-
// Ignore malformed files and try the next one.
|
|
100
|
-
}
|
|
127
|
+
if (connectionFiles.length > 0) {
|
|
128
|
+
const content = fs.readFileSync(connectionFiles[0].path, "utf-8");
|
|
129
|
+
const info = JSON.parse(content);
|
|
130
|
+
return info.port;
|
|
101
131
|
}
|
|
102
132
|
} catch {
|
|
103
|
-
// No connection files found
|
|
133
|
+
// No connection files found
|
|
104
134
|
}
|
|
105
135
|
return undefined;
|
|
106
136
|
}
|
|
107
137
|
|
|
108
|
-
function isPortAlive(port: number): Promise<boolean> {
|
|
109
|
-
return new Promise((resolve) => {
|
|
110
|
-
const req = http.get(
|
|
111
|
-
{
|
|
112
|
-
hostname: "127.0.0.1",
|
|
113
|
-
port,
|
|
114
|
-
path: "/context",
|
|
115
|
-
timeout: PORT_VALIDATE_TIMEOUT_MS,
|
|
116
|
-
},
|
|
117
|
-
(res) => {
|
|
118
|
-
res.resume();
|
|
119
|
-
resolve(res.statusCode === 200);
|
|
120
|
-
},
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
req.on("timeout", () => {
|
|
124
|
-
req.destroy();
|
|
125
|
-
resolve(false);
|
|
126
|
-
});
|
|
127
|
-
req.on("error", () => resolve(false));
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function getLivePort(): Promise<number | undefined> {
|
|
132
|
-
const info = readLatestConnectionInfo();
|
|
133
|
-
if (!info) return undefined;
|
|
134
|
-
return (await isPortAlive(info.port)) ? info.port : undefined;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
138
|
function updateWidget(): void {
|
|
138
139
|
if (!currentCtx) return;
|
|
139
|
-
|
|
140
|
+
|
|
140
141
|
if (!currentContext || !currentContext.workspaceState.openFiles.length) {
|
|
141
142
|
currentCtx.ui.setWidget("pi-companion", ["PI Companion: No files open"]);
|
|
142
143
|
return;
|
|
@@ -144,16 +145,25 @@ function updateWidget(): void {
|
|
|
144
145
|
|
|
145
146
|
const { workspaceState } = currentContext;
|
|
146
147
|
const lines: string[] = [];
|
|
148
|
+
|
|
147
149
|
const activeFile = workspaceState.openFiles.find((f) => f.isActive);
|
|
148
150
|
|
|
149
151
|
if (activeFile) {
|
|
150
152
|
const filename = activeFile.path.split("/").pop() || activeFile.path;
|
|
153
|
+
|
|
154
|
+
// Build location info - ONLY show when text is actually selected
|
|
151
155
|
let location = "";
|
|
152
156
|
if (activeFile.selectionStart && activeFile.selectionEnd) {
|
|
153
157
|
const start = activeFile.selectionStart.line;
|
|
154
158
|
const end = activeFile.selectionEnd.line;
|
|
155
|
-
|
|
159
|
+
if (start === end) {
|
|
160
|
+
location = ` L${start}`;
|
|
161
|
+
} else {
|
|
162
|
+
location = ` L${start}:${end}`;
|
|
163
|
+
}
|
|
156
164
|
}
|
|
165
|
+
// Don't show cursor position when nothing is selected
|
|
166
|
+
|
|
157
167
|
lines.push(`📄 ${filename}${location}`);
|
|
158
168
|
}
|
|
159
169
|
|
|
@@ -163,105 +173,129 @@ function updateWidget(): void {
|
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
const widgetKey = lines.join("|");
|
|
166
|
-
|
|
167
|
-
|
|
176
|
+
|
|
177
|
+
// Debounce widget updates to avoid flickering during rapid selection changes
|
|
178
|
+
if (widgetDebounceTimer) {
|
|
179
|
+
clearTimeout(widgetDebounceTimer);
|
|
180
|
+
}
|
|
181
|
+
|
|
168
182
|
widgetDebounceTimer = setTimeout(() => {
|
|
169
183
|
if (widgetKey !== lastWidgetKey) {
|
|
170
184
|
lastWidgetKey = widgetKey;
|
|
171
185
|
currentCtx!.ui.setWidget("pi-companion", lines);
|
|
172
186
|
}
|
|
173
187
|
widgetDebounceTimer = undefined;
|
|
174
|
-
},
|
|
188
|
+
}, 50); // Wait 50ms after last update
|
|
175
189
|
}
|
|
176
190
|
|
|
177
|
-
|
|
178
|
-
const port =
|
|
191
|
+
function connectSSE(): void {
|
|
192
|
+
const port = getPort();
|
|
179
193
|
if (!port) return;
|
|
180
194
|
|
|
195
|
+
// Close existing connection
|
|
181
196
|
if (sseRequest) {
|
|
182
197
|
sseRequest.destroy();
|
|
183
198
|
sseRequest = undefined;
|
|
184
199
|
}
|
|
185
200
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
const url = new URL(`http://127.0.0.1:${port}/context/stream`);
|
|
202
|
+
|
|
203
|
+
sseRequest = http.get({
|
|
204
|
+
hostname: url.hostname,
|
|
205
|
+
port: url.port,
|
|
206
|
+
path: url.pathname,
|
|
207
|
+
}, (res) => {
|
|
208
|
+
let buffer = "";
|
|
209
|
+
|
|
210
|
+
res.on("data", (chunk: string) => {
|
|
211
|
+
buffer += chunk;
|
|
212
|
+
|
|
213
|
+
// Process complete SSE messages
|
|
214
|
+
const lines = buffer.split("\n");
|
|
215
|
+
buffer = lines.pop() || "";
|
|
216
|
+
|
|
217
|
+
for (const line of lines) {
|
|
218
|
+
if (line.startsWith("data: ")) {
|
|
202
219
|
const data = line.slice(6);
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
220
|
+
if (data && data !== "[object Object]") {
|
|
221
|
+
try {
|
|
222
|
+
const parsed = JSON.parse(data);
|
|
223
|
+
currentContext = parsed;
|
|
224
|
+
updateWidget();
|
|
225
|
+
} catch {
|
|
226
|
+
// Ignore parse errors
|
|
227
|
+
}
|
|
209
228
|
}
|
|
210
229
|
}
|
|
211
|
-
}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
212
232
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
);
|
|
233
|
+
res.on("end", () => {
|
|
234
|
+
scheduleReconnect();
|
|
235
|
+
});
|
|
217
236
|
|
|
218
|
-
|
|
237
|
+
res.on("error", () => {
|
|
238
|
+
scheduleReconnect();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
sseRequest.on("error", () => {
|
|
243
|
+
scheduleReconnect();
|
|
244
|
+
});
|
|
219
245
|
}
|
|
220
246
|
|
|
221
247
|
function scheduleReconnect(): void {
|
|
222
248
|
if (reconnectTimer || !currentCtx) return;
|
|
223
249
|
reconnectTimer = setTimeout(() => {
|
|
224
250
|
reconnectTimer = undefined;
|
|
225
|
-
|
|
226
|
-
},
|
|
251
|
+
connectSSE();
|
|
252
|
+
}, 2000);
|
|
227
253
|
}
|
|
228
254
|
|
|
229
255
|
export default function (pi: ExtensionAPI) {
|
|
230
|
-
|
|
256
|
+
// Inject IDE context before each prompt
|
|
257
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
231
258
|
if (!currentContext) return;
|
|
232
|
-
|
|
259
|
+
|
|
233
260
|
const activeFile = currentContext.workspaceState.openFiles.find((f) => f.isActive);
|
|
234
261
|
if (!activeFile) return;
|
|
235
262
|
|
|
236
263
|
let contextText = "";
|
|
237
|
-
|
|
264
|
+
|
|
238
265
|
if (activeFile.selectedText) {
|
|
266
|
+
// Inject selected text
|
|
239
267
|
contextText = `Current selection in ${activeFile.path}:\n\`\`\`\n${activeFile.selectedText}\n\`\`\``;
|
|
240
268
|
} else if (activeFile.content) {
|
|
269
|
+
// Inject full file content for small files, or first 100 lines
|
|
241
270
|
const lines = activeFile.content.split("\n");
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
271
|
+
if (lines.length <= 100) {
|
|
272
|
+
contextText = `Current file ${activeFile.path}:\n\`\`\`\n${activeFile.content}\n\`\`\``;
|
|
273
|
+
} else {
|
|
274
|
+
contextText = `Current file ${activeFile.path} (first 100 lines):\n\`\`\`\n${lines.slice(0, 100).join("\n")}\n\`\`\``;
|
|
275
|
+
}
|
|
245
276
|
}
|
|
246
277
|
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
278
|
+
if (contextText) {
|
|
279
|
+
return {
|
|
280
|
+
message: {
|
|
281
|
+
customType: "pi-companion",
|
|
282
|
+
content: contextText,
|
|
283
|
+
display: false, // Don't show in chat history
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
255
287
|
});
|
|
256
288
|
|
|
257
289
|
pi.on("session_start", async (_event, ctx) => {
|
|
258
290
|
currentCtx = ctx;
|
|
259
|
-
ensureVSCodeExtensionInstalled(ctx);
|
|
260
291
|
|
|
261
|
-
|
|
292
|
+
// Auto-install/update VS Code extension if needed
|
|
293
|
+
installVSCodeExtension(ctx);
|
|
294
|
+
|
|
295
|
+
const port = getPort();
|
|
262
296
|
if (port) {
|
|
263
297
|
ctx.ui.notify("PI Companion: Connected to VS Code", "info");
|
|
264
|
-
|
|
298
|
+
connectSSE();
|
|
265
299
|
} else {
|
|
266
300
|
ctx.ui.notify("PI Companion: VS Code companion server not running. Run 'PI Companion: Start Server' in VS Code.", "warning");
|
|
267
301
|
}
|
|
@@ -278,6 +312,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
278
312
|
}
|
|
279
313
|
});
|
|
280
314
|
|
|
315
|
+
// Register tools
|
|
281
316
|
pi.registerTool({
|
|
282
317
|
name: "get_ide_context",
|
|
283
318
|
label: "Get IDE Context",
|
|
@@ -311,15 +346,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
311
346
|
return { content: [{ type: "text", text: "No active file" }], details: {} };
|
|
312
347
|
}
|
|
313
348
|
return {
|
|
314
|
-
content: [{
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
content: activeFile.content,
|
|
321
|
-
}, null, 2),
|
|
322
|
-
}],
|
|
349
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
350
|
+
path: activeFile.path,
|
|
351
|
+
cursor: activeFile.cursor,
|
|
352
|
+
selection: activeFile.selectedText,
|
|
353
|
+
content: activeFile.content,
|
|
354
|
+
}, null, 2) }],
|
|
323
355
|
details: {},
|
|
324
356
|
};
|
|
325
357
|
},
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "PI Companion",
|
|
4
4
|
"description": "Share VS Code context with pi coding agent",
|
|
5
5
|
"repository": "https://github.com/ravshansbox/vscode-pi-companion",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.2",
|
|
7
7
|
"publisher": "ravshansbox",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"pi-package"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"AI",
|
|
16
16
|
"Other"
|
|
17
17
|
],
|
|
18
|
-
"main": "./
|
|
18
|
+
"main": "./out/extension.js",
|
|
19
19
|
"scripts": {
|
|
20
20
|
"compile": "tsc -p ./",
|
|
21
21
|
"watch": "tsc -watch -p ./",
|
|
@@ -59,6 +59,10 @@
|
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
"commands": [
|
|
62
|
+
{
|
|
63
|
+
"command": "pi-companion.run",
|
|
64
|
+
"title": "PI Companion: Run PI"
|
|
65
|
+
},
|
|
62
66
|
{
|
|
63
67
|
"command": "pi-companion.start",
|
|
64
68
|
"title": "PI Companion: Start Server"
|
|
@@ -71,6 +75,14 @@
|
|
|
71
75
|
"command": "pi-companion.status",
|
|
72
76
|
"title": "PI Companion: Show Status"
|
|
73
77
|
}
|
|
78
|
+
],
|
|
79
|
+
"keybindings": [
|
|
80
|
+
{
|
|
81
|
+
"command": "pi-companion.run",
|
|
82
|
+
"key": "cmd+escape",
|
|
83
|
+
"mac": "cmd+escape",
|
|
84
|
+
"when": "editorTextFocus"
|
|
85
|
+
}
|
|
74
86
|
]
|
|
75
87
|
}
|
|
76
88
|
}
|
package/src/extension.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { IDEServer } from './ide-server';
|
|
|
3
3
|
|
|
4
4
|
let ideServer: IDEServer | undefined;
|
|
5
5
|
let outputChannel: vscode.OutputChannel;
|
|
6
|
+
let statusBarItem: vscode.StatusBarItem;
|
|
7
|
+
|
|
6
8
|
export function activate(context: vscode.ExtensionContext) {
|
|
7
9
|
outputChannel = vscode.window.createOutputChannel('PI Companion');
|
|
8
10
|
log('Extension activated');
|
|
@@ -17,8 +19,23 @@ export function activate(context: vscode.ExtensionContext) {
|
|
|
17
19
|
log(`Failed to start server: ${err}`);
|
|
18
20
|
});
|
|
19
21
|
|
|
22
|
+
// Create status bar button
|
|
23
|
+
statusBarItem = vscode.window.createStatusBarItem(
|
|
24
|
+
vscode.StatusBarAlignment.Right,
|
|
25
|
+
100
|
|
26
|
+
);
|
|
27
|
+
statusBarItem.text = '$(terminal) Run PI';
|
|
28
|
+
statusBarItem.tooltip = 'Run PI in terminal';
|
|
29
|
+
statusBarItem.command = 'pi-companion.run';
|
|
30
|
+
statusBarItem.show();
|
|
31
|
+
|
|
20
32
|
// Register commands
|
|
21
33
|
context.subscriptions.push(
|
|
34
|
+
statusBarItem,
|
|
35
|
+
|
|
36
|
+
vscode.commands.registerCommand('pi-companion.run', () => {
|
|
37
|
+
runPiInTerminal();
|
|
38
|
+
}),
|
|
22
39
|
|
|
23
40
|
vscode.commands.registerCommand('pi-companion.start', async () => {
|
|
24
41
|
if (ideServer?.isRunning()) {
|
|
@@ -44,9 +61,21 @@ export function activate(context: vscode.ExtensionContext) {
|
|
|
44
61
|
);
|
|
45
62
|
}
|
|
46
63
|
|
|
64
|
+
function runPiInTerminal() {
|
|
65
|
+
// Create new terminal so shell rc files (including direnv hook) are sourced
|
|
66
|
+
const terminal = vscode.window.createTerminal({
|
|
67
|
+
name: 'PI',
|
|
68
|
+
isTransient: true
|
|
69
|
+
});
|
|
70
|
+
terminal.show();
|
|
71
|
+
// Ensure direnv is loaded, then run pi
|
|
72
|
+
terminal.sendText('eval "$(direnv export json 2>/dev/null)" 2>/dev/null; pi');
|
|
73
|
+
}
|
|
74
|
+
|
|
47
75
|
export function deactivate() {
|
|
48
76
|
log('Extension deactivated');
|
|
49
77
|
ideServer?.stop();
|
|
78
|
+
statusBarItem?.dispose();
|
|
50
79
|
}
|
|
51
80
|
|
|
52
81
|
function log(message: string) {
|