gsd-pi 2.8.3 → 2.9.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/README.md +2 -1
- package/dist/cli.js +5 -0
- package/dist/loader.js +1 -1
- package/dist/update-check.d.ts +24 -0
- package/dist/update-check.js +93 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.d.ts +46 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.js +758 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/client.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.d.ts +23 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.js +267 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/config.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.d.ts +17 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.js +101 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/edits.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.d.ts +15 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.js +46 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/helpers.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.d.ts +35 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.js +709 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/index.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +308 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.d.ts +34 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.js +136 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.d.ts +262 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.js +64 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/types.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.d.ts +50 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.js +574 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/lsp/utils.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.d.ts +13 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.js +4 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +10 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +2 -2
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +46 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/client.ts +880 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/config.ts +325 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/defaults.json +456 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/edits.ts +109 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/helpers.ts +54 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/index.ts +943 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +407 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lsp.md +33 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/lspmux.ts +199 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/types.ts +421 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/lsp/utils.ts +682 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/index.ts +10 -0
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +2 -2
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +59 -2
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +46 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js +758 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts +23 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.js +267 -0
- package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts +17 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.js +101 -0
- package/packages/pi-coding-agent/dist/core/lsp/edits.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.d.ts +15 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.js +46 -0
- package/packages/pi-coding-agent/dist/core/lsp/helpers.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js +709 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +308 -0
- package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts +34 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js +136 -0
- package/packages/pi-coding-agent/dist/core/lsp/lspmux.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +262 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js +64 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +50 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +574 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +13 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +4 -0
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +10 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +2 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +46 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/lsp/client.ts +880 -0
- package/packages/pi-coding-agent/src/core/lsp/config.ts +325 -0
- package/packages/pi-coding-agent/src/core/lsp/defaults.json +456 -0
- package/packages/pi-coding-agent/src/core/lsp/edits.ts +109 -0
- package/packages/pi-coding-agent/src/core/lsp/helpers.ts +54 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +943 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +407 -0
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +33 -0
- package/packages/pi-coding-agent/src/core/lsp/lspmux.ts +199 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +421 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +682 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/tools/index.ts +10 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +59 -2
- package/src/resources/extensions/ask-user-questions.ts +2 -2
- package/src/resources/extensions/bg-shell/index.ts +34 -37
- package/src/resources/extensions/browser-tools/core.d.ts +205 -0
- package/src/resources/extensions/browser-tools/index.ts +2 -2
- package/src/resources/extensions/browser-tools/refs.ts +1 -1
- package/src/resources/extensions/browser-tools/tools/session.ts +1 -1
- package/src/resources/extensions/context7/index.ts +2 -2
- package/src/resources/extensions/get-secrets-from-user.ts +3 -2
- package/src/resources/extensions/google-search/index.ts +1 -1
- package/src/resources/extensions/gsd/auto.ts +41 -4
- package/src/resources/extensions/gsd/commands.ts +218 -3
- package/src/resources/extensions/gsd/doctor.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +116 -4
- package/src/resources/extensions/gsd/guided-flow.ts +19 -9
- package/src/resources/extensions/gsd/index.ts +17 -7
- package/src/resources/extensions/gsd/preferences.ts +1 -1
- package/src/resources/extensions/gsd/tests/git-service.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/migrate-command.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +10 -10
- package/src/resources/extensions/gsd/tests/next-milestone-id.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +352 -0
- package/src/resources/extensions/gsd/types.ts +1 -0
- package/src/resources/extensions/gsd/worktree.ts +20 -1
- package/src/resources/extensions/mac-tools/index.ts +1 -1
- package/src/resources/extensions/search-the-web/format.ts +1 -1
- package/src/resources/extensions/search-the-web/index.ts +5 -5
- package/src/resources/extensions/search-the-web/tool-fetch-page.ts +7 -7
- package/src/resources/extensions/search-the-web/tool-llm-context.ts +11 -11
- package/src/resources/extensions/search-the-web/tool-search.ts +10 -10
- package/src/resources/extensions/shared/interview-ui.ts +2 -2
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test for the LSP tool port.
|
|
3
|
+
*
|
|
4
|
+
* Spins up typescript-language-server against a temp TypeScript project
|
|
5
|
+
* and exercises: initialize, didOpen, hover, definition, references,
|
|
6
|
+
* documentSymbol, diagnostics, and shutdown.
|
|
7
|
+
*
|
|
8
|
+
* Run: node --experimental-strip-types --test src/core/lsp/lsp-integration.test.ts
|
|
9
|
+
* (from packages/pi-coding-agent/)
|
|
10
|
+
*/
|
|
11
|
+
import test from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
import * as os from "node:os";
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Helpers — lightweight JSON-RPC over stdio (no dependency on our LSP code)
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
interface JsonRpcRequest {
|
|
23
|
+
jsonrpc: "2.0";
|
|
24
|
+
id: number;
|
|
25
|
+
method: string;
|
|
26
|
+
params: unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface JsonRpcNotification {
|
|
30
|
+
jsonrpc: "2.0";
|
|
31
|
+
method: string;
|
|
32
|
+
params?: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface JsonRpcResponse {
|
|
36
|
+
jsonrpc: "2.0";
|
|
37
|
+
id?: number;
|
|
38
|
+
result?: unknown;
|
|
39
|
+
error?: { code: number; message: string };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function encodeMessage(msg: JsonRpcRequest | JsonRpcNotification | JsonRpcResponse): string {
|
|
43
|
+
const body = JSON.stringify(msg);
|
|
44
|
+
return `Content-Length: ${Buffer.byteLength(body, "utf-8")}\r\n\r\n${body}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Minimal LSP harness: spawns a language server, sends requests, collects responses.
|
|
49
|
+
*/
|
|
50
|
+
class LspHarness {
|
|
51
|
+
private proc;
|
|
52
|
+
private nextId = 1;
|
|
53
|
+
private buffer = Buffer.alloc(0);
|
|
54
|
+
private pending = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
|
|
55
|
+
private notifications: Array<{ method: string; params: unknown }> = [];
|
|
56
|
+
|
|
57
|
+
constructor(command: string, args: string[], cwd: string) {
|
|
58
|
+
this.proc = spawn(command, args, {
|
|
59
|
+
cwd,
|
|
60
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
this.proc.stdout!.on("data", (chunk: Buffer) => {
|
|
64
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
65
|
+
this.drain();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this.proc.stderr!.on("data", (chunk: Buffer) => {
|
|
69
|
+
// Swallow stderr (server logs)
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private drain(): void {
|
|
74
|
+
while (true) {
|
|
75
|
+
const headerEnd = this.findHeaderEnd();
|
|
76
|
+
if (headerEnd === -1) return;
|
|
77
|
+
|
|
78
|
+
const headerText = this.buffer.subarray(0, headerEnd).toString("utf-8");
|
|
79
|
+
const match = headerText.match(/Content-Length:\s*(\d+)/i);
|
|
80
|
+
if (!match) return;
|
|
81
|
+
|
|
82
|
+
const contentLength = parseInt(match[1], 10);
|
|
83
|
+
const messageStart = headerEnd + 4; // past \r\n\r\n
|
|
84
|
+
const messageEnd = messageStart + contentLength;
|
|
85
|
+
if (this.buffer.length < messageEnd) return;
|
|
86
|
+
|
|
87
|
+
const body = this.buffer.subarray(messageStart, messageEnd).toString("utf-8");
|
|
88
|
+
this.buffer = Buffer.from(this.buffer.subarray(messageEnd));
|
|
89
|
+
|
|
90
|
+
const msg = JSON.parse(body) as JsonRpcResponse & { method?: string; params?: unknown };
|
|
91
|
+
|
|
92
|
+
if (msg.id !== undefined && this.pending.has(msg.id)) {
|
|
93
|
+
const p = this.pending.get(msg.id)!;
|
|
94
|
+
this.pending.delete(msg.id);
|
|
95
|
+
if (msg.error) {
|
|
96
|
+
p.reject(new Error(`LSP error ${msg.error.code}: ${msg.error.message}`));
|
|
97
|
+
} else {
|
|
98
|
+
p.resolve(msg.result);
|
|
99
|
+
}
|
|
100
|
+
} else if (msg.method) {
|
|
101
|
+
// Server request or notification
|
|
102
|
+
this.notifications.push({ method: msg.method, params: msg.params });
|
|
103
|
+
// Auto-respond to server requests that have an id
|
|
104
|
+
if (msg.id !== undefined) {
|
|
105
|
+
this.respond(msg.id, null);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private findHeaderEnd(): number {
|
|
112
|
+
for (let i = 0; i < this.buffer.length - 3; i++) {
|
|
113
|
+
if (
|
|
114
|
+
this.buffer[i] === 13 &&
|
|
115
|
+
this.buffer[i + 1] === 10 &&
|
|
116
|
+
this.buffer[i + 2] === 13 &&
|
|
117
|
+
this.buffer[i + 3] === 10
|
|
118
|
+
) {
|
|
119
|
+
return i;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return -1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private respond(id: number, result: unknown): void {
|
|
126
|
+
const msg: JsonRpcResponse = { jsonrpc: "2.0", id, result };
|
|
127
|
+
this.proc.stdin!.write(encodeMessage(msg));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async request(method: string, params: unknown, timeoutMs = 15000): Promise<unknown> {
|
|
131
|
+
const id = this.nextId++;
|
|
132
|
+
const msg: JsonRpcRequest = { jsonrpc: "2.0", id, method, params };
|
|
133
|
+
this.proc.stdin!.write(encodeMessage(msg));
|
|
134
|
+
|
|
135
|
+
return new Promise<unknown>((resolve, reject) => {
|
|
136
|
+
const timer = setTimeout(() => {
|
|
137
|
+
this.pending.delete(id);
|
|
138
|
+
reject(new Error(`Request ${method} timed out after ${timeoutMs}ms`));
|
|
139
|
+
}, timeoutMs);
|
|
140
|
+
|
|
141
|
+
this.pending.set(id, {
|
|
142
|
+
resolve: (v) => {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
resolve(v);
|
|
145
|
+
},
|
|
146
|
+
reject: (e) => {
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
reject(e);
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
notify(method: string, params: unknown): void {
|
|
155
|
+
const msg: JsonRpcNotification = { jsonrpc: "2.0", method, params };
|
|
156
|
+
this.proc.stdin!.write(encodeMessage(msg));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
getNotifications(method?: string): Array<{ method: string; params: unknown }> {
|
|
160
|
+
if (!method) return this.notifications;
|
|
161
|
+
return this.notifications.filter((n) => n.method === method);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async shutdown(): Promise<void> {
|
|
165
|
+
try {
|
|
166
|
+
await this.request("shutdown", null, 5000);
|
|
167
|
+
this.notify("exit", null);
|
|
168
|
+
} catch {
|
|
169
|
+
// Best effort
|
|
170
|
+
}
|
|
171
|
+
this.proc.kill();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Test fixtures
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
function createTempProject(): { dir: string; cleanup: () => void } {
|
|
180
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "lsp-test-"));
|
|
181
|
+
|
|
182
|
+
// tsconfig.json
|
|
183
|
+
fs.writeFileSync(
|
|
184
|
+
path.join(dir, "tsconfig.json"),
|
|
185
|
+
JSON.stringify(
|
|
186
|
+
{
|
|
187
|
+
compilerOptions: {
|
|
188
|
+
target: "ES2022",
|
|
189
|
+
module: "commonjs",
|
|
190
|
+
strict: true,
|
|
191
|
+
outDir: "./dist",
|
|
192
|
+
rootDir: "./src",
|
|
193
|
+
},
|
|
194
|
+
include: ["src/**/*.ts"],
|
|
195
|
+
},
|
|
196
|
+
null,
|
|
197
|
+
2,
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// package.json
|
|
202
|
+
fs.writeFileSync(
|
|
203
|
+
path.join(dir, "package.json"),
|
|
204
|
+
JSON.stringify({ name: "lsp-test-project", version: "1.0.0" }, null, 2),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
fs.mkdirSync(path.join(dir, "src"));
|
|
208
|
+
|
|
209
|
+
// src/math.ts — module with exported functions
|
|
210
|
+
fs.writeFileSync(
|
|
211
|
+
path.join(dir, "src", "math.ts"),
|
|
212
|
+
`export function add(a: number, b: number): number {
|
|
213
|
+
return a + b;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function subtract(a: number, b: number): number {
|
|
217
|
+
return a - b;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface Calculator {
|
|
221
|
+
add(a: number, b: number): number;
|
|
222
|
+
subtract(a: number, b: number): number;
|
|
223
|
+
}
|
|
224
|
+
`,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// src/main.ts — imports from math, has a type error
|
|
228
|
+
fs.writeFileSync(
|
|
229
|
+
path.join(dir, "src", "main.ts"),
|
|
230
|
+
`import { add, subtract, Calculator } from "./math";
|
|
231
|
+
|
|
232
|
+
const result: number = add(1, 2);
|
|
233
|
+
const diff: number = subtract(5, 3);
|
|
234
|
+
|
|
235
|
+
// Intentional type error: string assigned to number
|
|
236
|
+
const bad: number = "not a number";
|
|
237
|
+
|
|
238
|
+
export function compute(calc: Calculator): number {
|
|
239
|
+
return calc.add(1, 2) + calc.subtract(5, 3);
|
|
240
|
+
}
|
|
241
|
+
`,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
dir,
|
|
246
|
+
cleanup: () => fs.rmSync(dir, { recursive: true, force: true }),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function fileToUri(filePath: string): string {
|
|
251
|
+
return `file://${path.resolve(filePath)}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// Tests
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
test("LSP integration: typescript-language-server", async (t) => {
|
|
259
|
+
const { dir, cleanup } = createTempProject();
|
|
260
|
+
const mainPath = path.join(dir, "src", "main.ts");
|
|
261
|
+
const mathPath = path.join(dir, "src", "math.ts");
|
|
262
|
+
const mainUri = fileToUri(mainPath);
|
|
263
|
+
const mathUri = fileToUri(mathPath);
|
|
264
|
+
|
|
265
|
+
const lsp = new LspHarness("typescript-language-server", ["--stdio"], dir);
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// ---- Initialize ----
|
|
269
|
+
await t.test("initialize handshake", async () => {
|
|
270
|
+
const result = (await lsp.request("initialize", {
|
|
271
|
+
processId: process.pid,
|
|
272
|
+
rootUri: fileToUri(dir),
|
|
273
|
+
rootPath: dir,
|
|
274
|
+
capabilities: {
|
|
275
|
+
textDocument: {
|
|
276
|
+
hover: { contentFormat: ["markdown", "plaintext"] },
|
|
277
|
+
definition: { linkSupport: true },
|
|
278
|
+
references: {},
|
|
279
|
+
documentSymbol: { hierarchicalDocumentSymbolSupport: true },
|
|
280
|
+
publishDiagnostics: { relatedInformation: true },
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
workspaceFolders: [{ uri: fileToUri(dir), name: "test" }],
|
|
284
|
+
})) as { capabilities?: Record<string, unknown> };
|
|
285
|
+
|
|
286
|
+
assert.ok(result, "initialize should return a result");
|
|
287
|
+
assert.ok(result.capabilities, "result should have capabilities");
|
|
288
|
+
assert.ok(result.capabilities.hoverProvider !== undefined, "should support hover");
|
|
289
|
+
assert.ok(result.capabilities.definitionProvider !== undefined, "should support definition");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
lsp.notify("initialized", {});
|
|
293
|
+
|
|
294
|
+
// Open both files
|
|
295
|
+
const mainContent = fs.readFileSync(mainPath, "utf-8");
|
|
296
|
+
const mathContent = fs.readFileSync(mathPath, "utf-8");
|
|
297
|
+
|
|
298
|
+
lsp.notify("textDocument/didOpen", {
|
|
299
|
+
textDocument: { uri: mainUri, languageId: "typescript", version: 1, text: mainContent },
|
|
300
|
+
});
|
|
301
|
+
lsp.notify("textDocument/didOpen", {
|
|
302
|
+
textDocument: { uri: mathUri, languageId: "typescript", version: 1, text: mathContent },
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Give the server time to index
|
|
306
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
307
|
+
|
|
308
|
+
// ---- Hover ----
|
|
309
|
+
await t.test("hover on 'add' call", async () => {
|
|
310
|
+
const result = (await lsp.request("textDocument/hover", {
|
|
311
|
+
textDocument: { uri: mainUri },
|
|
312
|
+
position: { line: 2, character: 24 }, // on 'add' in "add(1, 2)"
|
|
313
|
+
})) as { contents?: unknown } | null;
|
|
314
|
+
|
|
315
|
+
assert.ok(result, "hover should return a result");
|
|
316
|
+
assert.ok(result.contents, "hover should have contents");
|
|
317
|
+
const text = JSON.stringify(result.contents);
|
|
318
|
+
assert.ok(
|
|
319
|
+
text.includes("add") || text.includes("number"),
|
|
320
|
+
`hover text should mention 'add' or 'number', got: ${text.slice(0, 200)}`,
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// ---- Go to Definition ----
|
|
325
|
+
await t.test("go to definition of 'add'", async () => {
|
|
326
|
+
const result = (await lsp.request("textDocument/definition", {
|
|
327
|
+
textDocument: { uri: mainUri },
|
|
328
|
+
position: { line: 2, character: 24 }, // on 'add'
|
|
329
|
+
})) as unknown;
|
|
330
|
+
|
|
331
|
+
assert.ok(result, "definition should return a result");
|
|
332
|
+
const locations = Array.isArray(result) ? result : [result];
|
|
333
|
+
assert.ok(locations.length > 0, "should find at least one definition");
|
|
334
|
+
// Response can be Location (uri) or LocationLink (targetUri)
|
|
335
|
+
const loc = locations[0] as Record<string, unknown>;
|
|
336
|
+
const uri = (loc.uri ?? loc.targetUri) as string;
|
|
337
|
+
assert.ok(uri, `definition should have uri or targetUri, got keys: ${Object.keys(loc).join(", ")}`);
|
|
338
|
+
assert.ok(
|
|
339
|
+
uri.includes("math.ts"),
|
|
340
|
+
`definition should point to math.ts, got: ${uri}`,
|
|
341
|
+
);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// ---- References ----
|
|
345
|
+
await t.test("find references of 'add'", async () => {
|
|
346
|
+
const result = (await lsp.request("textDocument/references", {
|
|
347
|
+
textDocument: { uri: mathUri },
|
|
348
|
+
position: { line: 0, character: 16 }, // on 'add' definition
|
|
349
|
+
context: { includeDeclaration: true },
|
|
350
|
+
})) as Array<{ uri: string; range: unknown }> | null;
|
|
351
|
+
|
|
352
|
+
assert.ok(result, "references should return a result");
|
|
353
|
+
assert.ok(result.length >= 2, `should find at least 2 references (decl + usage), got ${result.length}`);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// ---- Document Symbols ----
|
|
357
|
+
await t.test("document symbols in math.ts", async () => {
|
|
358
|
+
const result = (await lsp.request("textDocument/documentSymbol", {
|
|
359
|
+
textDocument: { uri: mathUri },
|
|
360
|
+
})) as Array<{ name: string; kind: number }> | null;
|
|
361
|
+
|
|
362
|
+
assert.ok(result, "documentSymbol should return a result");
|
|
363
|
+
assert.ok(result.length >= 2, `should find at least 2 symbols, got ${result.length}`);
|
|
364
|
+
const names = result.map((s) => s.name);
|
|
365
|
+
assert.ok(names.includes("add"), `symbols should include 'add', got: ${names.join(", ")}`);
|
|
366
|
+
assert.ok(names.includes("subtract"), `symbols should include 'subtract', got: ${names.join(", ")}`);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// ---- Diagnostics (published via notification) ----
|
|
370
|
+
await t.test("diagnostics for type error", async () => {
|
|
371
|
+
// Wait a bit more for diagnostics to arrive
|
|
372
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
373
|
+
|
|
374
|
+
const diagNotifications = lsp.getNotifications("textDocument/publishDiagnostics");
|
|
375
|
+
const mainDiags = diagNotifications.filter(
|
|
376
|
+
(n) => (n.params as { uri: string }).uri === mainUri,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
assert.ok(mainDiags.length > 0, "should receive diagnostics for main.ts");
|
|
380
|
+
|
|
381
|
+
const lastDiag = mainDiags[mainDiags.length - 1];
|
|
382
|
+
const diagnostics = (lastDiag.params as { diagnostics: Array<{ message: string; range: unknown }> })
|
|
383
|
+
.diagnostics;
|
|
384
|
+
|
|
385
|
+
// Should catch the type error: string assigned to number
|
|
386
|
+
const typeError = diagnostics.find(
|
|
387
|
+
(d) => d.message.includes("not assignable") || d.message.includes("Type"),
|
|
388
|
+
);
|
|
389
|
+
assert.ok(
|
|
390
|
+
typeError,
|
|
391
|
+
`should find type error diagnostic, got: ${diagnostics.map((d) => d.message).join("; ")}`,
|
|
392
|
+
);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// ---- Shutdown ----
|
|
396
|
+
await t.test("clean shutdown", async () => {
|
|
397
|
+
// Should not throw
|
|
398
|
+
await lsp.shutdown();
|
|
399
|
+
});
|
|
400
|
+
} catch (err) {
|
|
401
|
+
await lsp.shutdown().catch(() => {});
|
|
402
|
+
cleanup();
|
|
403
|
+
throw err;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
cleanup();
|
|
407
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Interacts with Language Server Protocol servers for code intelligence.
|
|
2
|
+
|
|
3
|
+
<operations>
|
|
4
|
+
- `diagnostics`: Get errors/warnings for file, glob, or entire workspace (no file)
|
|
5
|
+
- `definition`: Go to symbol definition → file path + position + 3-line source context
|
|
6
|
+
- `type_definition`: Go to symbol type definition → file path + position + 3-line source context
|
|
7
|
+
- `implementation`: Find concrete implementations → file path + position + 3-line source context
|
|
8
|
+
- `references`: Find references → locations with 3-line source context (first 50), remaining location-only
|
|
9
|
+
- `hover`: Get type info and documentation → type signature + docs
|
|
10
|
+
- `symbols`: List symbols in file, or search workspace (with query, no file)
|
|
11
|
+
- `rename`: Rename symbol across codebase → preview or apply edits
|
|
12
|
+
- `code_actions`: List available quick-fixes/refactors/import actions; apply one when `apply: true` and `query` matches title or index
|
|
13
|
+
- `status`: Show active language servers
|
|
14
|
+
- `reload`: Restart the language server
|
|
15
|
+
</operations>
|
|
16
|
+
|
|
17
|
+
<parameters>
|
|
18
|
+
- `file`: File path; for diagnostics it may be a glob pattern (e.g., `src/**/*.ts`)
|
|
19
|
+
- `line`: 1-indexed line number for position-based actions
|
|
20
|
+
- `symbol`: Substring on the target line used to resolve column automatically
|
|
21
|
+
- `occurrence`: 1-indexed match index when `symbol` appears multiple times on the same line
|
|
22
|
+
- `query`: Symbol search query, code-action kind filter (list mode), or code-action selector (apply mode)
|
|
23
|
+
- `new_name`: Required for rename
|
|
24
|
+
- `apply`: Apply edits for rename/code_actions (default true for rename, list mode for code_actions unless explicitly true)
|
|
25
|
+
- `timeout`: Request timeout in seconds (clamped to 5-60, default 20)
|
|
26
|
+
</parameters>
|
|
27
|
+
|
|
28
|
+
<caution>
|
|
29
|
+
- Requires running LSP server for target language
|
|
30
|
+
- Some operations require file to be saved to disk
|
|
31
|
+
- Diagnostics glob mode samples up to 20 files per request to avoid long-running stalls on broad patterns
|
|
32
|
+
- When `symbol` is provided for position-based actions, missing symbols or out-of-bounds `occurrence` values return an explicit error instead of silently falling back
|
|
33
|
+
</caution>
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { execSync, spawn } from "node:child_process";
|
|
2
|
+
import * as fsPromises from "node:fs/promises";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* lspmux integration for LSP server multiplexing.
|
|
8
|
+
*
|
|
9
|
+
* When lspmux is available and running, this module wraps supported LSP server
|
|
10
|
+
* commands to use lspmux client mode, enabling server instance sharing across
|
|
11
|
+
* multiple editor windows.
|
|
12
|
+
*
|
|
13
|
+
* Integration is transparent: if lspmux is unavailable, falls back to direct spawning.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Types
|
|
18
|
+
// =============================================================================
|
|
19
|
+
|
|
20
|
+
interface LspmuxConfig {
|
|
21
|
+
instance_timeout?: number;
|
|
22
|
+
gc_interval?: number;
|
|
23
|
+
listen?: [string, number] | string;
|
|
24
|
+
connect?: [string, number] | string;
|
|
25
|
+
log_filters?: string;
|
|
26
|
+
pass_environment?: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface LspmuxState {
|
|
30
|
+
available: boolean;
|
|
31
|
+
running: boolean;
|
|
32
|
+
binaryPath: string | null;
|
|
33
|
+
config: LspmuxConfig | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Constants
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
const DEFAULT_SUPPORTED_SERVERS = new Set([
|
|
41
|
+
"rust-analyzer",
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const LIVENESS_TIMEOUT_MS = 1000;
|
|
45
|
+
const STATE_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// Helpers
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
function which(command: string): string | null {
|
|
52
|
+
try {
|
|
53
|
+
return execSync(`which ${command}`, { encoding: "utf-8" }).trim() || null;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// Config Path
|
|
61
|
+
// =============================================================================
|
|
62
|
+
|
|
63
|
+
function getConfigPath(): string {
|
|
64
|
+
const home = os.homedir();
|
|
65
|
+
switch (os.platform()) {
|
|
66
|
+
case "win32":
|
|
67
|
+
return path.join(process.env.APPDATA ?? path.join(home, "AppData", "Roaming"), "lspmux", "config.toml");
|
|
68
|
+
case "darwin":
|
|
69
|
+
return path.join(home, "Library", "Application Support", "lspmux", "config.toml");
|
|
70
|
+
default:
|
|
71
|
+
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(home, ".config"), "lspmux", "config.toml");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// =============================================================================
|
|
76
|
+
// State Management
|
|
77
|
+
// =============================================================================
|
|
78
|
+
|
|
79
|
+
let cachedState: LspmuxState | null = null;
|
|
80
|
+
let cacheTimestamp = 0;
|
|
81
|
+
|
|
82
|
+
async function parseConfig(): Promise<LspmuxConfig | null> {
|
|
83
|
+
try {
|
|
84
|
+
const configPath = getConfigPath();
|
|
85
|
+
// lspmux config uses TOML, but since we're stripping TOML support,
|
|
86
|
+
// attempt a simple key=value parse for the config file.
|
|
87
|
+
// If the config file exists but can't be parsed, return null.
|
|
88
|
+
try {
|
|
89
|
+
await fsPromises.access(configPath);
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
// Config exists but we can't parse TOML without a dependency.
|
|
94
|
+
// Return an empty config object to indicate the file exists.
|
|
95
|
+
return {} as LspmuxConfig;
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function checkServerRunning(binaryPath: string): Promise<boolean> {
|
|
102
|
+
try {
|
|
103
|
+
const proc = spawn(binaryPath, ["status"], {
|
|
104
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const exited = await Promise.race([
|
|
108
|
+
new Promise<number>((resolve) => {
|
|
109
|
+
proc.on("exit", (code: number | null) => resolve(code ?? 1));
|
|
110
|
+
}),
|
|
111
|
+
new Promise<null>(resolve => setTimeout(() => resolve(null), LIVENESS_TIMEOUT_MS)),
|
|
112
|
+
]);
|
|
113
|
+
|
|
114
|
+
if (exited === null) {
|
|
115
|
+
proc.kill();
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return exited === 0;
|
|
120
|
+
} catch {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function detectLspmux(): Promise<LspmuxState> {
|
|
126
|
+
const now = Date.now();
|
|
127
|
+
if (cachedState && now - cacheTimestamp < STATE_CACHE_TTL_MS) {
|
|
128
|
+
return cachedState;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (process.env.PI_DISABLE_LSPMUX === "1" || process.env.GSD_DISABLE_LSPMUX === "1") {
|
|
132
|
+
cachedState = { available: false, running: false, binaryPath: null, config: null };
|
|
133
|
+
cacheTimestamp = now;
|
|
134
|
+
return cachedState;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const binaryPath = which("lspmux");
|
|
138
|
+
if (!binaryPath) {
|
|
139
|
+
cachedState = { available: false, running: false, binaryPath: null, config: null };
|
|
140
|
+
cacheTimestamp = now;
|
|
141
|
+
return cachedState;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const [config, running] = await Promise.all([parseConfig(), checkServerRunning(binaryPath)]);
|
|
145
|
+
|
|
146
|
+
cachedState = { available: true, running, binaryPath, config };
|
|
147
|
+
cacheTimestamp = now;
|
|
148
|
+
|
|
149
|
+
return cachedState;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// =============================================================================
|
|
153
|
+
// Command Wrapping
|
|
154
|
+
// =============================================================================
|
|
155
|
+
|
|
156
|
+
export function isLspmuxSupported(command: string): boolean {
|
|
157
|
+
const baseName = command.split("/").pop() ?? command;
|
|
158
|
+
return DEFAULT_SUPPORTED_SERVERS.has(baseName);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface LspmuxWrappedCommand {
|
|
162
|
+
command: string;
|
|
163
|
+
args: string[];
|
|
164
|
+
env?: Record<string, string>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function wrapWithLspmux(
|
|
168
|
+
originalCommand: string,
|
|
169
|
+
originalArgs: string[] | undefined,
|
|
170
|
+
state: LspmuxState,
|
|
171
|
+
): LspmuxWrappedCommand {
|
|
172
|
+
if (!state.available || !state.running || !state.binaryPath) {
|
|
173
|
+
return { command: originalCommand, args: originalArgs ?? [] };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!isLspmuxSupported(originalCommand)) {
|
|
177
|
+
return { command: originalCommand, args: originalArgs ?? [] };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const baseName = originalCommand.split("/").pop() ?? originalCommand;
|
|
181
|
+
const isDefaultRustAnalyzer = baseName === "rust-analyzer" && originalCommand === "rust-analyzer";
|
|
182
|
+
const hasArgs = originalArgs && originalArgs.length > 0;
|
|
183
|
+
|
|
184
|
+
if (isDefaultRustAnalyzer && !hasArgs) {
|
|
185
|
+
return { command: state.binaryPath, args: [] };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const args = hasArgs ? ["client", "--", ...originalArgs] : ["client"];
|
|
189
|
+
return {
|
|
190
|
+
command: state.binaryPath,
|
|
191
|
+
args,
|
|
192
|
+
env: { LSPMUX_SERVER: originalCommand },
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function getLspmuxCommand(command: string, args?: string[]): Promise<LspmuxWrappedCommand> {
|
|
197
|
+
const state = await detectLspmux();
|
|
198
|
+
return wrapWithLspmux(command, args, state);
|
|
199
|
+
}
|