decorated-pi 0.1.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/LICENSE +21 -0
- package/README.md +218 -0
- package/extensions/extend-model.ts +410 -0
- package/extensions/guidance.ts +21 -0
- package/extensions/index.ts +24 -0
- package/extensions/lsp/client.ts +525 -0
- package/extensions/lsp/env.ts +12 -0
- package/extensions/lsp/format.ts +349 -0
- package/extensions/lsp/index.ts +14 -0
- package/extensions/lsp/prompt.ts +39 -0
- package/extensions/lsp/server-manager.ts +303 -0
- package/extensions/lsp/servers.ts +229 -0
- package/extensions/lsp/tools.ts +530 -0
- package/extensions/lsp/trust.ts +39 -0
- package/extensions/safety.ts +370 -0
- package/extensions/session-title.ts +40 -0
- package/extensions/settings.ts +62 -0
- package/extensions/slash.ts +67 -0
- package/extensions/smart-at.ts +220 -0
- package/extensions/subdir-agents.ts +121 -0
- package/index.ts +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* decorated-pi — Essential utilities for pi
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { setupSafety } from "./safety";
|
|
7
|
+
import { setupExtendModel } from "./extend-model";
|
|
8
|
+
import { setupSlash } from "./slash";
|
|
9
|
+
import { setupSubdirAgents } from "./subdir-agents";
|
|
10
|
+
import { setupSessionTitle } from "./session-title";
|
|
11
|
+
import { setupGuidance } from "./guidance";
|
|
12
|
+
import { setupLsp } from "./lsp/index";
|
|
13
|
+
import { setupSmartAt } from "./smart-at";
|
|
14
|
+
|
|
15
|
+
export default function (pi: ExtensionAPI) {
|
|
16
|
+
setupSafety(pi);
|
|
17
|
+
setupExtendModel(pi);
|
|
18
|
+
setupSlash(pi);
|
|
19
|
+
setupSubdirAgents(pi);
|
|
20
|
+
setupSessionTitle(pi);
|
|
21
|
+
setupGuidance(pi);
|
|
22
|
+
setupLsp(pi);
|
|
23
|
+
setupSmartAt(pi);
|
|
24
|
+
}
|
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import { spawn, ChildProcess } from "node:child_process";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { create_child_process_env } from "./env.js";
|
|
5
|
+
|
|
6
|
+
export interface LspClientOptions {
|
|
7
|
+
command: string;
|
|
8
|
+
args: string[];
|
|
9
|
+
root_uri: string;
|
|
10
|
+
language_id_for_uri: (uri: string) => string | undefined;
|
|
11
|
+
request_timeout_ms?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface LspPosition {
|
|
15
|
+
line: number;
|
|
16
|
+
character: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface LspRange {
|
|
20
|
+
start: LspPosition;
|
|
21
|
+
end: LspPosition;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface LspLocation {
|
|
25
|
+
uri: string;
|
|
26
|
+
range: LspRange;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface LspDiagnostic {
|
|
30
|
+
range: LspRange;
|
|
31
|
+
severity?: number;
|
|
32
|
+
code?: unknown;
|
|
33
|
+
source?: string;
|
|
34
|
+
message: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface LspHover {
|
|
38
|
+
contents: unknown;
|
|
39
|
+
range?: LspRange;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type LspDocumentSymbol = {
|
|
43
|
+
name: string;
|
|
44
|
+
kind: number;
|
|
45
|
+
range: LspRange;
|
|
46
|
+
selectionRange: LspRange;
|
|
47
|
+
containerName?: string;
|
|
48
|
+
detail?: string;
|
|
49
|
+
children?: LspDocumentSymbol[];
|
|
50
|
+
uri?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type PendingRequest = {
|
|
54
|
+
resolve: (value: unknown) => void;
|
|
55
|
+
reject: (error: Error) => void;
|
|
56
|
+
timer: NodeJS.Timeout;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export class LspClientStartError extends Error {
|
|
60
|
+
command: string;
|
|
61
|
+
args: string[];
|
|
62
|
+
code?: string;
|
|
63
|
+
|
|
64
|
+
constructor(
|
|
65
|
+
message: string,
|
|
66
|
+
options: { command: string; args: string[]; cause?: Error; code?: string }
|
|
67
|
+
) {
|
|
68
|
+
super(message, options.cause ? { cause: options.cause } : undefined);
|
|
69
|
+
this.name = "LspClientStartError";
|
|
70
|
+
this.command = options.command;
|
|
71
|
+
this.args = options.args;
|
|
72
|
+
this.code = options.code;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface OpenDoc {
|
|
77
|
+
version: number;
|
|
78
|
+
text: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class LspClient extends EventEmitter {
|
|
82
|
+
#proc: ChildProcess | null = null;
|
|
83
|
+
#options: LspClientOptions;
|
|
84
|
+
#next_id = 1;
|
|
85
|
+
#pending = new Map<number, PendingRequest>();
|
|
86
|
+
#buffer = Buffer.alloc(0);
|
|
87
|
+
#initialized = false;
|
|
88
|
+
#open_docs = new Map<string, OpenDoc>();
|
|
89
|
+
#diagnostics_by_uri = new Map<string, LspDiagnostic[]>();
|
|
90
|
+
#diagnostic_waiters = new Set<() => void>();
|
|
91
|
+
|
|
92
|
+
constructor(options: LspClientOptions) {
|
|
93
|
+
super();
|
|
94
|
+
this.#options = options;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async start(): Promise<void> {
|
|
98
|
+
this.#proc = spawn(this.#options.command, this.#options.args, {
|
|
99
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
100
|
+
env: create_child_process_env(),
|
|
101
|
+
});
|
|
102
|
+
const proc = this.#proc;
|
|
103
|
+
|
|
104
|
+
let start_reject: ((e: Error) => void) | null = null;
|
|
105
|
+
const start_failure = new Promise<never>((_, reject) => {
|
|
106
|
+
start_reject = reject;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const reject_start = (error: Error): boolean => {
|
|
110
|
+
if (!start_reject) return false;
|
|
111
|
+
const reject = start_reject;
|
|
112
|
+
start_reject = null;
|
|
113
|
+
reject(error);
|
|
114
|
+
return true;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const start_error = (
|
|
118
|
+
message: string,
|
|
119
|
+
cause?: Error,
|
|
120
|
+
code?: string
|
|
121
|
+
) =>
|
|
122
|
+
new LspClientStartError(message, {
|
|
123
|
+
command: this.#options.command,
|
|
124
|
+
args: this.#options.args,
|
|
125
|
+
cause,
|
|
126
|
+
code,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
proc.on("error", (err) => {
|
|
130
|
+
const wrapped = start_error(
|
|
131
|
+
`Failed to spawn ${this.#options.command}`,
|
|
132
|
+
err,
|
|
133
|
+
error_code(err)
|
|
134
|
+
);
|
|
135
|
+
if (!reject_start(wrapped)) {
|
|
136
|
+
this.#emit_error(wrapped);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
proc.on("close", () => {
|
|
141
|
+
if (!this.#initialized) {
|
|
142
|
+
reject_start(
|
|
143
|
+
start_error(
|
|
144
|
+
`LSP server ${this.#options.command} closed before initialization`
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
for (const pending of this.#pending.values()) {
|
|
149
|
+
clearTimeout(pending.timer);
|
|
150
|
+
pending.reject(new Error("LSP server closed"));
|
|
151
|
+
}
|
|
152
|
+
this.#pending.clear();
|
|
153
|
+
this.#initialized = false;
|
|
154
|
+
this.#proc = null;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
proc.stderr?.on("data", () => {
|
|
158
|
+
// Discard stderr; many servers are chatty.
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
proc.stdout?.on("data", (chunk: Buffer) => {
|
|
162
|
+
this.#buffer = Buffer.concat([this.#buffer, chunk]);
|
|
163
|
+
this.#drain_buffer();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
await Promise.race([
|
|
168
|
+
this.#request("initialize", {
|
|
169
|
+
processId: process.pid,
|
|
170
|
+
rootUri: this.#options.root_uri,
|
|
171
|
+
capabilities: {
|
|
172
|
+
textDocument: {
|
|
173
|
+
publishDiagnostics: { relatedInformation: true },
|
|
174
|
+
hover: { contentFormat: ["markdown", "plaintext"] },
|
|
175
|
+
definition: { linkSupport: false },
|
|
176
|
+
references: {},
|
|
177
|
+
documentSymbol: {
|
|
178
|
+
hierarchicalDocumentSymbolSupport: true,
|
|
179
|
+
},
|
|
180
|
+
rename: { prepareSupport: true },
|
|
181
|
+
},
|
|
182
|
+
workspace: { workspaceFolders: true, symbol: {} },
|
|
183
|
+
},
|
|
184
|
+
workspaceFolders: [
|
|
185
|
+
{ uri: this.#options.root_uri, name: "workspace" },
|
|
186
|
+
],
|
|
187
|
+
}),
|
|
188
|
+
start_failure,
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
this.#notify("initialized", {});
|
|
192
|
+
this.#initialized = true;
|
|
193
|
+
start_reject = null;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
await this.stop();
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
is_ready(): boolean {
|
|
201
|
+
return this.#initialized;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async ensure_document_open(uri: string, text: string): Promise<void> {
|
|
205
|
+
const existing = this.#open_docs.get(uri);
|
|
206
|
+
if (existing) {
|
|
207
|
+
if (existing.text === text) return;
|
|
208
|
+
const next_version = existing.version + 1;
|
|
209
|
+
this.#open_docs.set(uri, { version: next_version, text });
|
|
210
|
+
this.#diagnostics_by_uri.delete(uri);
|
|
211
|
+
this.#notify("textDocument/didChange", {
|
|
212
|
+
textDocument: { uri, version: next_version },
|
|
213
|
+
contentChanges: [{ text }],
|
|
214
|
+
});
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const language_id =
|
|
218
|
+
this.#options.language_id_for_uri(uri) ?? "plaintext";
|
|
219
|
+
this.#open_docs.set(uri, { version: 1, text });
|
|
220
|
+
this.#diagnostics_by_uri.delete(uri);
|
|
221
|
+
this.#notify("textDocument/didOpen", {
|
|
222
|
+
textDocument: { uri, languageId: language_id, version: 1, text },
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async hover(
|
|
227
|
+
uri: string,
|
|
228
|
+
position: LspPosition
|
|
229
|
+
): Promise<LspHover | null> {
|
|
230
|
+
const result = (await this.#request("textDocument/hover", {
|
|
231
|
+
textDocument: { uri },
|
|
232
|
+
position,
|
|
233
|
+
})) as LspHover | null;
|
|
234
|
+
return result ?? null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async definition(
|
|
238
|
+
uri: string,
|
|
239
|
+
position: LspPosition
|
|
240
|
+
): Promise<LspLocation[]> {
|
|
241
|
+
const result = await this.#request("textDocument/definition", {
|
|
242
|
+
textDocument: { uri },
|
|
243
|
+
position,
|
|
244
|
+
});
|
|
245
|
+
return normalize_location_result(result);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async references(
|
|
249
|
+
uri: string,
|
|
250
|
+
position: LspPosition,
|
|
251
|
+
include_declaration?: boolean
|
|
252
|
+
): Promise<LspLocation[]> {
|
|
253
|
+
const result = (await this.#request("textDocument/references", {
|
|
254
|
+
textDocument: { uri },
|
|
255
|
+
position,
|
|
256
|
+
context: { includeDeclaration: include_declaration },
|
|
257
|
+
})) as LspLocation[] | null;
|
|
258
|
+
return result ?? [];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async document_symbols(uri: string): Promise<LspDocumentSymbol[]> {
|
|
262
|
+
const result = await this.#request("textDocument/documentSymbol", {
|
|
263
|
+
textDocument: { uri },
|
|
264
|
+
});
|
|
265
|
+
return normalize_document_symbol_result(result);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async rename(
|
|
269
|
+
uri: string,
|
|
270
|
+
position: LspPosition,
|
|
271
|
+
newName: string
|
|
272
|
+
): Promise<Record<string, { oldText: string; newText: string }>> {
|
|
273
|
+
const result = (await this.#request("textDocument/rename", {
|
|
274
|
+
textDocument: { uri },
|
|
275
|
+
position,
|
|
276
|
+
newName,
|
|
277
|
+
})) as any;
|
|
278
|
+
|
|
279
|
+
// Normalize WorkspaceEdit to a simple record
|
|
280
|
+
const edits: Record<string, { oldText: string; newText: string }> = {};
|
|
281
|
+
if (result?.changes) {
|
|
282
|
+
for (const [uri, changes] of Object.entries(result.changes)) {
|
|
283
|
+
const path = file_url_to_path(uri);
|
|
284
|
+
for (const change of changes as any[]) {
|
|
285
|
+
const existing = edits[path];
|
|
286
|
+
if (existing) {
|
|
287
|
+
existing.newText += change.newText;
|
|
288
|
+
} else {
|
|
289
|
+
edits[path] = {
|
|
290
|
+
oldText: change.range ? `[${change.range.start.line}:${change.range.start.character}-${change.range.end.line}:${change.range.end.character}]` : "",
|
|
291
|
+
newText: change.newText ?? "",
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return edits;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
get_diagnostics(uri: string): LspDiagnostic[] {
|
|
301
|
+
return this.#diagnostics_by_uri.get(uri) ?? [];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async wait_for_diagnostics(
|
|
305
|
+
uri: string,
|
|
306
|
+
timeout_ms: number = 1500
|
|
307
|
+
): Promise<LspDiagnostic[]> {
|
|
308
|
+
if (this.#diagnostics_by_uri.has(uri)) {
|
|
309
|
+
return this.get_diagnostics(uri);
|
|
310
|
+
}
|
|
311
|
+
return new Promise((resolve) => {
|
|
312
|
+
let active = true;
|
|
313
|
+
const cleanup = () => {
|
|
314
|
+
if (!active) return;
|
|
315
|
+
active = false;
|
|
316
|
+
this.off("diagnostics", handler);
|
|
317
|
+
clearTimeout(timer);
|
|
318
|
+
this.#diagnostic_waiters.delete(cleanup);
|
|
319
|
+
resolve(this.get_diagnostics(uri));
|
|
320
|
+
};
|
|
321
|
+
const handler = (event_uri: string) => {
|
|
322
|
+
if (event_uri !== uri) return;
|
|
323
|
+
cleanup();
|
|
324
|
+
};
|
|
325
|
+
const timer = setTimeout(cleanup, timeout_ms);
|
|
326
|
+
this.on("diagnostics", handler);
|
|
327
|
+
this.#diagnostic_waiters.add(cleanup);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async stop(): Promise<void> {
|
|
332
|
+
if (this.#initialized) {
|
|
333
|
+
try {
|
|
334
|
+
await this.#request("shutdown", null, 1000);
|
|
335
|
+
this.#notify("exit", null);
|
|
336
|
+
} catch {
|
|
337
|
+
// Server may already be dead; proceed.
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
for (const pending of this.#pending.values()) {
|
|
341
|
+
clearTimeout(pending.timer);
|
|
342
|
+
pending.reject(new Error("LSP client stopped"));
|
|
343
|
+
}
|
|
344
|
+
this.#pending.clear();
|
|
345
|
+
for (const cleanup of Array.from(this.#diagnostic_waiters)) {
|
|
346
|
+
cleanup();
|
|
347
|
+
}
|
|
348
|
+
if (this.#proc) {
|
|
349
|
+
this.#proc.kill();
|
|
350
|
+
this.#proc = null;
|
|
351
|
+
}
|
|
352
|
+
this.#initialized = false;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
#request(
|
|
356
|
+
method: string,
|
|
357
|
+
params: unknown,
|
|
358
|
+
timeout_override?: number
|
|
359
|
+
): Promise<unknown> {
|
|
360
|
+
return new Promise((resolve, reject) => {
|
|
361
|
+
const id = this.#next_id++;
|
|
362
|
+
const timeout_ms =
|
|
363
|
+
timeout_override ?? this.#options.request_timeout_ms ?? 30_000;
|
|
364
|
+
const timer = setTimeout(() => {
|
|
365
|
+
if (this.#pending.has(id)) {
|
|
366
|
+
this.#pending.delete(id);
|
|
367
|
+
reject(new Error(`LSP request ${method} timed out`));
|
|
368
|
+
}
|
|
369
|
+
}, timeout_ms);
|
|
370
|
+
this.#pending.set(id, { resolve, reject, timer });
|
|
371
|
+
try {
|
|
372
|
+
this.#send({ jsonrpc: "2.0", id, method, params });
|
|
373
|
+
} catch (error) {
|
|
374
|
+
clearTimeout(timer);
|
|
375
|
+
this.#pending.delete(id);
|
|
376
|
+
reject(error);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
#notify(method: string, params: unknown): void {
|
|
382
|
+
this.#send({ jsonrpc: "2.0", method, params });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
#send(message: Record<string, unknown>): void {
|
|
386
|
+
if (!this.#proc?.stdin?.writable) {
|
|
387
|
+
throw new Error("LSP server not connected");
|
|
388
|
+
}
|
|
389
|
+
const body = Buffer.from(JSON.stringify(message), "utf8");
|
|
390
|
+
const header = Buffer.from(
|
|
391
|
+
`Content-Length: ${body.length}\r\n\r\n`,
|
|
392
|
+
"ascii"
|
|
393
|
+
);
|
|
394
|
+
this.#proc.stdin.write(Buffer.concat([header, body]));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
#emit_error(error: Error): void {
|
|
398
|
+
if (this.listenerCount("error") > 0) {
|
|
399
|
+
this.emit("error", error);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
#drain_buffer(): void {
|
|
404
|
+
while (true) {
|
|
405
|
+
const header_end = this.#buffer.indexOf("\r\n\r\n");
|
|
406
|
+
if (header_end === -1) return;
|
|
407
|
+
const header = this.#buffer.subarray(0, header_end).toString("ascii");
|
|
408
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
409
|
+
if (!match) {
|
|
410
|
+
this.#buffer = this.#buffer.subarray(header_end + 4);
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
const length = Number(match[1]);
|
|
414
|
+
const body_start = header_end + 4;
|
|
415
|
+
if (this.#buffer.length < body_start + length) return;
|
|
416
|
+
const body = this.#buffer.subarray(body_start, body_start + length);
|
|
417
|
+
this.#buffer = this.#buffer.subarray(body_start + length);
|
|
418
|
+
try {
|
|
419
|
+
this.#handle_message(JSON.parse(body.toString("utf8")));
|
|
420
|
+
} catch (error) {
|
|
421
|
+
this.#emit_error(error as Error);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
#handle_message(message: Record<string, unknown>): void {
|
|
427
|
+
const numeric_id =
|
|
428
|
+
typeof message.id === "number"
|
|
429
|
+
? message.id
|
|
430
|
+
: typeof message.id === "string" && /^-?\d+$/.test(message.id)
|
|
431
|
+
? Number(message.id)
|
|
432
|
+
: null;
|
|
433
|
+
|
|
434
|
+
if (numeric_id != null && this.#pending.has(numeric_id)) {
|
|
435
|
+
const pending = this.#pending.get(numeric_id)!;
|
|
436
|
+
this.#pending.delete(numeric_id);
|
|
437
|
+
clearTimeout(pending.timer);
|
|
438
|
+
if (message.error) {
|
|
439
|
+
const err = message.error as Record<string, unknown>;
|
|
440
|
+
pending.reject(
|
|
441
|
+
new Error(`LSP error ${err.code}: ${err.message}`)
|
|
442
|
+
);
|
|
443
|
+
} else {
|
|
444
|
+
pending.resolve(message.result);
|
|
445
|
+
}
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (
|
|
450
|
+
message.method === "textDocument/publishDiagnostics" &&
|
|
451
|
+
message.params
|
|
452
|
+
) {
|
|
453
|
+
const params = message.params as {
|
|
454
|
+
uri: string;
|
|
455
|
+
diagnostics: LspDiagnostic[];
|
|
456
|
+
};
|
|
457
|
+
this.#diagnostics_by_uri.set(params.uri, params.diagnostics);
|
|
458
|
+
this.emit("diagnostics", params.uri);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Respond to server-to-client requests we don't implement
|
|
463
|
+
if (message.method && message.id != null) {
|
|
464
|
+
this.#send({ jsonrpc: "2.0", id: message.id, result: null });
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function normalize_location_result(
|
|
470
|
+
result: unknown
|
|
471
|
+
): LspLocation[] {
|
|
472
|
+
if (!result) return [];
|
|
473
|
+
const entries = Array.isArray(result) ? result : [result];
|
|
474
|
+
return entries.map((entry: any) => {
|
|
475
|
+
if ("uri" in entry) return entry;
|
|
476
|
+
return {
|
|
477
|
+
uri: entry.targetUri,
|
|
478
|
+
range: entry.targetSelectionRange ?? entry.targetRange,
|
|
479
|
+
};
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function normalize_document_symbol_result(
|
|
484
|
+
result: unknown
|
|
485
|
+
): LspDocumentSymbol[] {
|
|
486
|
+
if (!result) return [];
|
|
487
|
+
if (
|
|
488
|
+
(result as any[]).length === 0 ||
|
|
489
|
+
("range" in (result as any[])[0] && "selectionRange" in (result as any[])[0])
|
|
490
|
+
) {
|
|
491
|
+
return result as LspDocumentSymbol[];
|
|
492
|
+
}
|
|
493
|
+
const symbol_info = result as any[];
|
|
494
|
+
return symbol_info.map((entry) => ({
|
|
495
|
+
name: entry.name,
|
|
496
|
+
kind: entry.kind,
|
|
497
|
+
range: entry.location.range,
|
|
498
|
+
selectionRange: entry.location.range,
|
|
499
|
+
containerName: entry.containerName,
|
|
500
|
+
uri: entry.location.uri,
|
|
501
|
+
}));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function file_path_to_uri(file_path: string): string {
|
|
505
|
+
return pathToFileURL(file_path).href;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function file_url_to_path(uri: string): string {
|
|
509
|
+
try {
|
|
510
|
+
return uri.startsWith("file:")
|
|
511
|
+
? new URL(uri).pathname
|
|
512
|
+
: uri;
|
|
513
|
+
} catch {
|
|
514
|
+
return uri;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function error_code(error: unknown): string | undefined {
|
|
519
|
+
return typeof error === "object" &&
|
|
520
|
+
error !== null &&
|
|
521
|
+
"code" in error &&
|
|
522
|
+
typeof (error as Record<string, unknown>).code === "string"
|
|
523
|
+
? (error as Record<string, string>).code
|
|
524
|
+
: undefined;
|
|
525
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { create_child_process_env as create_shared_child_process_env } from "@spences10/pi-child-env";
|
|
2
|
+
|
|
3
|
+
export function create_child_process_env(
|
|
4
|
+
explicit_env: Record<string, string> = {},
|
|
5
|
+
source_env: NodeJS.ProcessEnv = process.env
|
|
6
|
+
): NodeJS.ProcessEnv {
|
|
7
|
+
return create_shared_child_process_env({
|
|
8
|
+
profile: "lsp",
|
|
9
|
+
explicit_env,
|
|
10
|
+
source_env,
|
|
11
|
+
});
|
|
12
|
+
}
|