pi-lens 3.1.2 → 3.2.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/CHANGELOG.md +55 -0
- package/README.md +16 -12
- package/clients/ast-grep-client.js +8 -1
- package/clients/ast-grep-client.ts +9 -1
- package/clients/biome-client.js +51 -38
- package/clients/biome-client.ts +60 -58
- package/clients/dependency-checker.js +30 -1
- package/clients/dependency-checker.ts +35 -1
- package/clients/dispatch/__tests__/runner-registration.test.ts +286 -282
- package/clients/dispatch/bus-dispatcher.js +15 -14
- package/clients/dispatch/bus-dispatcher.ts +32 -25
- package/clients/dispatch/dispatcher.js +18 -25
- package/clients/dispatch/dispatcher.test.ts +2 -1
- package/clients/dispatch/dispatcher.ts +17 -28
- package/clients/dispatch/plan.js +77 -32
- package/clients/dispatch/plan.ts +78 -32
- package/clients/dispatch/runners/ast-grep-napi.js +36 -376
- package/clients/dispatch/runners/ast-grep-napi.ts +60 -433
- package/clients/dispatch/runners/index.js +8 -4
- package/clients/dispatch/runners/index.ts +8 -4
- package/clients/dispatch/runners/lsp.js +65 -0
- package/clients/dispatch/runners/lsp.ts +125 -0
- package/clients/dispatch/runners/oxlint.js +2 -2
- package/clients/dispatch/runners/oxlint.ts +2 -2
- package/clients/dispatch/runners/pyright.js +24 -8
- package/clients/dispatch/runners/pyright.ts +28 -14
- package/clients/dispatch/runners/rust-clippy.js +2 -2
- package/clients/dispatch/runners/rust-clippy.ts +2 -4
- package/clients/dispatch/runners/tree-sitter.js +14 -2
- package/clients/dispatch/runners/tree-sitter.ts +15 -2
- package/clients/dispatch/runners/ts-lsp.js +3 -3
- package/clients/dispatch/runners/ts-lsp.ts +8 -5
- package/clients/dispatch/runners/yaml-rule-parser.js +292 -0
- package/clients/dispatch/runners/yaml-rule-parser.ts +338 -0
- package/clients/dispatch/types.js +3 -0
- package/clients/dispatch/types.ts +3 -0
- package/clients/formatters.js +67 -14
- package/clients/formatters.ts +68 -15
- package/clients/installer/index.js +78 -10
- package/clients/installer/index.ts +519 -426
- package/clients/jscpd-client.js +28 -0
- package/clients/jscpd-client.ts +41 -3
- package/clients/knip-client.js +30 -1
- package/clients/knip-client.ts +34 -2
- package/clients/lsp/__tests__/client.test.ts +64 -41
- package/clients/lsp/__tests__/config.test.ts +25 -17
- package/clients/lsp/__tests__/launch.test.ts +108 -43
- package/clients/lsp/__tests__/service.test.ts +76 -48
- package/clients/lsp/client.js +87 -2
- package/clients/lsp/client.ts +150 -6
- package/clients/lsp/config.js +8 -11
- package/clients/lsp/config.ts +24 -21
- package/clients/lsp/index.js +69 -0
- package/clients/lsp/index.ts +82 -0
- package/clients/lsp/interactive-install.js +19 -8
- package/clients/lsp/interactive-install.ts +52 -27
- package/clients/lsp/launch.js +182 -32
- package/clients/lsp/launch.ts +241 -38
- package/clients/lsp/path-utils.js +3 -46
- package/clients/lsp/path-utils.ts +11 -51
- package/clients/lsp/server.js +93 -71
- package/clients/lsp/server.ts +173 -131
- package/clients/path-utils.js +142 -0
- package/clients/path-utils.ts +153 -0
- package/clients/ruff-client.js +33 -4
- package/clients/ruff-client.ts +44 -13
- package/clients/safe-spawn.js +3 -1
- package/clients/safe-spawn.ts +3 -1
- package/clients/services/effect-integration.js +11 -7
- package/clients/services/effect-integration.ts +34 -26
- package/clients/sg-runner.js +51 -9
- package/clients/sg-runner.ts +58 -15
- package/clients/tree-sitter-client.js +12 -0
- package/clients/tree-sitter-client.ts +12 -0
- package/clients/typescript-client.js +6 -2
- package/clients/typescript-client.ts +9 -2
- package/commands/booboo.js +2 -4
- package/commands/booboo.ts +2 -4
- package/index.ts +377 -93
- package/package.json +2 -1
- package/rules/tree-sitter-queries/tsx/no-nested-links.yml +45 -0
- package/rules/tree-sitter-queries/typescript/constructor-super.yml +55 -0
- package/rules/tree-sitter-queries/typescript/debugger.yml +1 -1
- package/rules/tree-sitter-queries/typescript/no-dupe-class-members.yml +47 -0
- package/tsconfig.json +1 -1
- package/clients/__tests__/file-time.test.js +0 -216
- package/clients/__tests__/format-service.test.js +0 -245
- package/clients/__tests__/formatters.test.js +0 -271
- package/clients/agent-behavior-client.test.js +0 -94
- package/clients/ast-grep-client.test.js +0 -129
- package/clients/ast-grep-client.test.ts +0 -155
- package/clients/biome-client.test.js +0 -144
- package/clients/cache-manager.test.js +0 -197
- package/clients/complexity-client.test.js +0 -234
- package/clients/dependency-checker.test.js +0 -60
- package/clients/dispatch/__tests__/autofix-integration.test.js +0 -245
- package/clients/dispatch/__tests__/runner-registration.test.js +0 -236
- package/clients/dispatch/dispatcher.edge.test.js +0 -82
- package/clients/dispatch/dispatcher.format.test.js +0 -46
- package/clients/dispatch/dispatcher.inline.test.js +0 -74
- package/clients/dispatch/dispatcher.test.js +0 -115
- package/clients/dispatch/runners/architect.test.js +0 -138
- package/clients/dispatch/runners/ast-grep-napi.test.js +0 -106
- package/clients/dispatch/runners/oxlint.test.js +0 -230
- package/clients/dispatch/runners/pyright.test.js +0 -98
- package/clients/dispatch/runners/python-slop.test.js +0 -203
- package/clients/dispatch/runners/scan_codebase.test.js +0 -89
- package/clients/dispatch/runners/shellcheck.test.js +0 -98
- package/clients/dispatch/runners/spellcheck.test.js +0 -158
- package/clients/dispatch/runners/ts-slop.test.js +0 -180
- package/clients/dispatch/runners/ts-slop.test.ts +0 -230
- package/clients/dogfood.test.js +0 -201
- package/clients/file-kinds.test.js +0 -169
- package/clients/go-client.test.js +0 -127
- package/clients/jscpd-client.test.js +0 -127
- package/clients/knip-client.test.js +0 -112
- package/clients/lsp/__tests__/client.test.js +0 -325
- package/clients/lsp/__tests__/config.test.js +0 -166
- package/clients/lsp/__tests__/error-recovery.test.js +0 -213
- package/clients/lsp/__tests__/integration.test.js +0 -127
- package/clients/lsp/__tests__/launch.test.js +0 -260
- package/clients/lsp/__tests__/server.test.js +0 -259
- package/clients/lsp/__tests__/service.test.js +0 -417
- package/clients/metrics-client.test.js +0 -141
- package/clients/ruff-client.test.js +0 -132
- package/clients/rust-client.test.js +0 -108
- package/clients/sanitize.test.js +0 -177
- package/clients/secrets-scanner.test.js +0 -100
- package/clients/services/__tests__/effect-integration.test.js +0 -86
- package/clients/test-runner-client.test.js +0 -192
- package/clients/todo-scanner.test.js +0 -301
- package/clients/type-coverage-client.test.js +0 -105
- package/clients/typescript-client.codefix.test.js +0 -157
- package/clients/typescript-client.test.js +0 -105
- package/commands/clients/ast-grep-client.js +0 -250
- package/commands/clients/ast-grep-parser.js +0 -86
- package/commands/clients/ast-grep-rule-manager.js +0 -91
- package/commands/clients/ast-grep-types.js +0 -9
- package/commands/clients/biome-client.js +0 -380
- package/commands/clients/complexity-client.js +0 -667
- package/commands/clients/file-kinds.js +0 -177
- package/commands/clients/file-utils.js +0 -40
- package/commands/clients/jscpd-client.js +0 -169
- package/commands/clients/knip-client.js +0 -211
- package/commands/clients/ruff-client.js +0 -297
- package/commands/clients/safe-spawn.js +0 -88
- package/commands/clients/scan-utils.js +0 -83
- package/commands/clients/sg-runner.js +0 -190
- package/commands/clients/types.js +0 -11
- package/commands/clients/typescript-client.js +0 -505
- package/commands/rate.test.js +0 -119
- package/rules/ast-grep-rules/rules/no-dangerously-set-inner-html.yml +0 -13
- package/rules/ast-grep-rules/rules/no-debugger.yml +0 -12
- package/rules/ast-grep-rules/rules/no-eval.yml +0 -13
package/clients/lsp/client.js
CHANGED
|
@@ -150,8 +150,10 @@ export async function createLSPClient(options) {
|
|
|
150
150
|
},
|
|
151
151
|
async change(filePath, content) {
|
|
152
152
|
const uri = pathToFileURL(filePath).href;
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
// Normalize path for Windows case-insensitive lookup
|
|
154
|
+
const normalizedPath = normalizeMapKey(filePath);
|
|
155
|
+
const version = (documentVersions.get(normalizedPath) ?? 0) + 1;
|
|
156
|
+
documentVersions.set(normalizedPath, version);
|
|
155
157
|
await connection.sendNotification("textDocument/didChange", {
|
|
156
158
|
textDocument: { uri, version },
|
|
157
159
|
contentChanges: [{ text: content }],
|
|
@@ -163,6 +165,10 @@ export async function createLSPClient(options) {
|
|
|
163
165
|
const normalizedPath = normalizeMapKey(filePath);
|
|
164
166
|
return diagnostics.get(normalizedPath) ?? [];
|
|
165
167
|
},
|
|
168
|
+
getAllDiagnostics() {
|
|
169
|
+
// Return copy of all tracked diagnostics (for cascade checking)
|
|
170
|
+
return new Map(diagnostics);
|
|
171
|
+
},
|
|
166
172
|
async waitForDiagnostics(filePath, timeoutMs = 10000) {
|
|
167
173
|
const normalizedPath = normalizeMapKey(filePath);
|
|
168
174
|
if (diagnostics.has(normalizedPath))
|
|
@@ -192,6 +198,85 @@ export async function createLSPClient(options) {
|
|
|
192
198
|
}, timeoutMs);
|
|
193
199
|
});
|
|
194
200
|
},
|
|
201
|
+
async definition(filePath, line, character) {
|
|
202
|
+
const uri = pathToFileURL(filePath).href;
|
|
203
|
+
try {
|
|
204
|
+
const result = await connection.sendRequest("textDocument/definition", {
|
|
205
|
+
textDocument: { uri },
|
|
206
|
+
position: { line, character },
|
|
207
|
+
});
|
|
208
|
+
if (!result)
|
|
209
|
+
return [];
|
|
210
|
+
return Array.isArray(result) ? result : [result];
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
async references(filePath, line, character, includeDeclaration = true) {
|
|
217
|
+
const uri = pathToFileURL(filePath).href;
|
|
218
|
+
try {
|
|
219
|
+
const result = await connection.sendRequest("textDocument/references", {
|
|
220
|
+
textDocument: { uri },
|
|
221
|
+
position: { line, character },
|
|
222
|
+
context: { includeDeclaration },
|
|
223
|
+
});
|
|
224
|
+
return Array.isArray(result) ? result : [];
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
async hover(filePath, line, character) {
|
|
231
|
+
const uri = pathToFileURL(filePath).href;
|
|
232
|
+
try {
|
|
233
|
+
return (await connection.sendRequest("textDocument/hover", {
|
|
234
|
+
textDocument: { uri },
|
|
235
|
+
position: { line, character },
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
async documentSymbol(filePath) {
|
|
243
|
+
const uri = pathToFileURL(filePath).href;
|
|
244
|
+
try {
|
|
245
|
+
const result = await connection.sendRequest("textDocument/documentSymbol", {
|
|
246
|
+
textDocument: { uri },
|
|
247
|
+
});
|
|
248
|
+
return Array.isArray(result) ? result : [];
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
async workspaceSymbol(query) {
|
|
255
|
+
try {
|
|
256
|
+
const result = await connection.sendRequest("workspace/symbol", {
|
|
257
|
+
query,
|
|
258
|
+
});
|
|
259
|
+
return Array.isArray(result) ? result : [];
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
async implementation(filePath, line, character) {
|
|
266
|
+
const uri = pathToFileURL(filePath).href;
|
|
267
|
+
try {
|
|
268
|
+
const result = await connection.sendRequest("textDocument/implementation", {
|
|
269
|
+
textDocument: { uri },
|
|
270
|
+
position: { line, character },
|
|
271
|
+
});
|
|
272
|
+
if (!result)
|
|
273
|
+
return [];
|
|
274
|
+
return Array.isArray(result) ? result : [result];
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
},
|
|
195
280
|
async shutdown() {
|
|
196
281
|
// Clear pending timers
|
|
197
282
|
for (const timer of pendingDiagnostics.values()) {
|
package/clients/lsp/client.ts
CHANGED
|
@@ -32,6 +32,32 @@ export interface LSPDiagnostic {
|
|
|
32
32
|
source?: string;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
export interface LSPLocation {
|
|
36
|
+
uri: string;
|
|
37
|
+
range: {
|
|
38
|
+
start: { line: number; character: number };
|
|
39
|
+
end: { line: number; character: number };
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface LSPHover {
|
|
44
|
+
contents:
|
|
45
|
+
| string
|
|
46
|
+
| { kind: string; value: string }
|
|
47
|
+
| Array<string | { language: string; value: string }>;
|
|
48
|
+
range?: LSPLocation["range"];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface LSPSymbol {
|
|
52
|
+
name: string;
|
|
53
|
+
kind: number;
|
|
54
|
+
location?: LSPLocation;
|
|
55
|
+
range?: LSPLocation["range"];
|
|
56
|
+
selectionRange?: LSPLocation["range"];
|
|
57
|
+
detail?: string;
|
|
58
|
+
children?: LSPSymbol[];
|
|
59
|
+
}
|
|
60
|
+
|
|
35
61
|
export interface LSPClientInfo {
|
|
36
62
|
serverId: string;
|
|
37
63
|
root: string;
|
|
@@ -42,6 +68,37 @@ export interface LSPClientInfo {
|
|
|
42
68
|
};
|
|
43
69
|
getDiagnostics(filePath: string): LSPDiagnostic[];
|
|
44
70
|
waitForDiagnostics(filePath: string, timeoutMs?: number): Promise<void>;
|
|
71
|
+
/** Get all tracked diagnostics (for cascade checking) */
|
|
72
|
+
getAllDiagnostics(): Map<string, LSPDiagnostic[]>;
|
|
73
|
+
/** Go to definition — returns Location[] */
|
|
74
|
+
definition(
|
|
75
|
+
filePath: string,
|
|
76
|
+
line: number,
|
|
77
|
+
character: number,
|
|
78
|
+
): Promise<LSPLocation[]>;
|
|
79
|
+
/** Find all references */
|
|
80
|
+
references(
|
|
81
|
+
filePath: string,
|
|
82
|
+
line: number,
|
|
83
|
+
character: number,
|
|
84
|
+
includeDeclaration?: boolean,
|
|
85
|
+
): Promise<LSPLocation[]>;
|
|
86
|
+
/** Hover info at position */
|
|
87
|
+
hover(
|
|
88
|
+
filePath: string,
|
|
89
|
+
line: number,
|
|
90
|
+
character: number,
|
|
91
|
+
): Promise<LSPHover | null>;
|
|
92
|
+
/** Symbols in a document */
|
|
93
|
+
documentSymbol(filePath: string): Promise<LSPSymbol[]>;
|
|
94
|
+
/** Workspace-wide symbol search */
|
|
95
|
+
workspaceSymbol(query: string): Promise<LSPSymbol[]>;
|
|
96
|
+
/** Go to implementation */
|
|
97
|
+
implementation(
|
|
98
|
+
filePath: string,
|
|
99
|
+
line: number,
|
|
100
|
+
character: number,
|
|
101
|
+
): Promise<LSPLocation[]>;
|
|
45
102
|
shutdown(): Promise<void>;
|
|
46
103
|
}
|
|
47
104
|
|
|
@@ -150,10 +207,7 @@ export async function createLSPClient(options: {
|
|
|
150
207
|
workDoneProgress: true,
|
|
151
208
|
},
|
|
152
209
|
workspace: {
|
|
153
|
-
workspaceFolders:
|
|
154
|
-
supported: true,
|
|
155
|
-
changeNotifications: true,
|
|
156
|
-
},
|
|
210
|
+
workspaceFolders: true, // Simple boolean for broader compatibility
|
|
157
211
|
configuration: true,
|
|
158
212
|
didChangeWatchedFiles: {
|
|
159
213
|
dynamicRegistration: true,
|
|
@@ -222,8 +276,10 @@ export async function createLSPClient(options: {
|
|
|
222
276
|
|
|
223
277
|
async change(filePath, content) {
|
|
224
278
|
const uri = pathToFileURL(filePath).href;
|
|
225
|
-
|
|
226
|
-
|
|
279
|
+
// Normalize path for Windows case-insensitive lookup
|
|
280
|
+
const normalizedPath = normalizeMapKey(filePath);
|
|
281
|
+
const version = (documentVersions.get(normalizedPath) ?? 0) + 1;
|
|
282
|
+
documentVersions.set(normalizedPath, version);
|
|
227
283
|
|
|
228
284
|
await connection.sendNotification("textDocument/didChange", {
|
|
229
285
|
textDocument: { uri, version },
|
|
@@ -238,6 +294,11 @@ export async function createLSPClient(options: {
|
|
|
238
294
|
return diagnostics.get(normalizedPath) ?? [];
|
|
239
295
|
},
|
|
240
296
|
|
|
297
|
+
getAllDiagnostics() {
|
|
298
|
+
// Return copy of all tracked diagnostics (for cascade checking)
|
|
299
|
+
return new Map(diagnostics);
|
|
300
|
+
},
|
|
301
|
+
|
|
241
302
|
async waitForDiagnostics(filePath, timeoutMs = 10000) {
|
|
242
303
|
const normalizedPath = normalizeMapKey(filePath);
|
|
243
304
|
if (diagnostics.has(normalizedPath)) return;
|
|
@@ -270,6 +331,89 @@ export async function createLSPClient(options: {
|
|
|
270
331
|
});
|
|
271
332
|
},
|
|
272
333
|
|
|
334
|
+
async definition(filePath, line, character) {
|
|
335
|
+
const uri = pathToFileURL(filePath).href;
|
|
336
|
+
try {
|
|
337
|
+
const result = await connection.sendRequest("textDocument/definition", {
|
|
338
|
+
textDocument: { uri },
|
|
339
|
+
position: { line, character },
|
|
340
|
+
});
|
|
341
|
+
if (!result) return [];
|
|
342
|
+
return Array.isArray(result) ? result : [result];
|
|
343
|
+
} catch {
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
async references(filePath, line, character, includeDeclaration = true) {
|
|
349
|
+
const uri = pathToFileURL(filePath).href;
|
|
350
|
+
try {
|
|
351
|
+
const result = await connection.sendRequest("textDocument/references", {
|
|
352
|
+
textDocument: { uri },
|
|
353
|
+
position: { line, character },
|
|
354
|
+
context: { includeDeclaration },
|
|
355
|
+
});
|
|
356
|
+
return Array.isArray(result) ? result : [];
|
|
357
|
+
} catch {
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
async hover(filePath, line, character) {
|
|
363
|
+
const uri = pathToFileURL(filePath).href;
|
|
364
|
+
try {
|
|
365
|
+
return (await connection.sendRequest("textDocument/hover", {
|
|
366
|
+
textDocument: { uri },
|
|
367
|
+
position: { line, character },
|
|
368
|
+
})) as LSPHover | null;
|
|
369
|
+
} catch {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
async documentSymbol(filePath) {
|
|
375
|
+
const uri = pathToFileURL(filePath).href;
|
|
376
|
+
try {
|
|
377
|
+
const result = await connection.sendRequest(
|
|
378
|
+
"textDocument/documentSymbol",
|
|
379
|
+
{
|
|
380
|
+
textDocument: { uri },
|
|
381
|
+
},
|
|
382
|
+
);
|
|
383
|
+
return Array.isArray(result) ? result : [];
|
|
384
|
+
} catch {
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
async workspaceSymbol(query) {
|
|
390
|
+
try {
|
|
391
|
+
const result = await connection.sendRequest("workspace/symbol", {
|
|
392
|
+
query,
|
|
393
|
+
});
|
|
394
|
+
return Array.isArray(result) ? result : [];
|
|
395
|
+
} catch {
|
|
396
|
+
return [];
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
async implementation(filePath, line, character) {
|
|
401
|
+
const uri = pathToFileURL(filePath).href;
|
|
402
|
+
try {
|
|
403
|
+
const result = await connection.sendRequest(
|
|
404
|
+
"textDocument/implementation",
|
|
405
|
+
{
|
|
406
|
+
textDocument: { uri },
|
|
407
|
+
position: { line, character },
|
|
408
|
+
},
|
|
409
|
+
);
|
|
410
|
+
if (!result) return [];
|
|
411
|
+
return Array.isArray(result) ? result : [result];
|
|
412
|
+
} catch {
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
|
|
273
417
|
async shutdown() {
|
|
274
418
|
// Clear pending timers
|
|
275
419
|
for (const timer of pendingDiagnostics.values()) {
|
package/clients/lsp/config.js
CHANGED
|
@@ -18,18 +18,14 @@
|
|
|
18
18
|
* }
|
|
19
19
|
* }
|
|
20
20
|
*/
|
|
21
|
-
import fs from "fs/promises";
|
|
22
|
-
import path from "path";
|
|
23
|
-
import { fileURLToPath } from "url";
|
|
24
|
-
import { LSP_SERVERS, createRootDetector } from "./server.js";
|
|
21
|
+
import fs from "node:fs/promises";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
25
24
|
import { launchLSP } from "./launch.js";
|
|
25
|
+
import { createRootDetector, LSP_SERVERS, } from "./server.js";
|
|
26
26
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
27
|
// --- Config Loading ---
|
|
28
|
-
const CONFIG_PATHS = [
|
|
29
|
-
".pi-lens/lsp.json",
|
|
30
|
-
".pi-lens.json",
|
|
31
|
-
"pi-lsp.json",
|
|
32
|
-
];
|
|
28
|
+
const CONFIG_PATHS = [".pi-lens/lsp.json", ".pi-lens.json", "pi-lsp.json"];
|
|
33
29
|
/**
|
|
34
30
|
* Load LSP configuration from file
|
|
35
31
|
*/
|
|
@@ -61,7 +57,7 @@ export function createCustomServer(config, id) {
|
|
|
61
57
|
? createRootDetector(config.rootMarkers)
|
|
62
58
|
: async () => process.cwd(),
|
|
63
59
|
async spawn(root) {
|
|
64
|
-
const proc = launchLSP(config.command, config.args ?? ["--stdio"], {
|
|
60
|
+
const proc = await launchLSP(config.command, config.args ?? ["--stdio"], {
|
|
65
61
|
cwd: root,
|
|
66
62
|
env: config.env ? { ...process.env, ...config.env } : process.env,
|
|
67
63
|
});
|
|
@@ -99,7 +95,7 @@ export async function initLSPConfig(cwd) {
|
|
|
99
95
|
*/
|
|
100
96
|
export function getAllServers() {
|
|
101
97
|
const all = [...LSP_SERVERS, ...customServers];
|
|
102
|
-
return all.filter(s => !disabledServerIds.has(s.id));
|
|
98
|
+
return all.filter((s) => !disabledServerIds.has(s.id));
|
|
103
99
|
}
|
|
104
100
|
/**
|
|
105
101
|
* Check if a server is disabled
|
|
@@ -107,6 +103,7 @@ export function getAllServers() {
|
|
|
107
103
|
export function isServerDisabled(serverId) {
|
|
108
104
|
return disabledServerIds.has(serverId);
|
|
109
105
|
}
|
|
106
|
+
// --- Override getServersForFile to include custom servers
|
|
110
107
|
export function getServersForFileWithConfig(filePath) {
|
|
111
108
|
const ext = path.extname(filePath).toLowerCase();
|
|
112
109
|
return getAllServers().filter((server) => server.extensions.includes(ext));
|
package/clients/lsp/config.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LSP Configuration for pi-lens
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Allows users to define custom LSP servers via configuration.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* Config file: .pi-lens/lsp.json
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* Example:
|
|
9
9
|
* {
|
|
10
10
|
* "servers": {
|
|
@@ -19,11 +19,15 @@
|
|
|
19
19
|
* }
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import fs from "fs/promises";
|
|
23
|
-
import path from "path";
|
|
24
|
-
import { fileURLToPath } from "url";
|
|
25
|
-
import { LSP_SERVERS, type LSPServerInfo, createRootDetector } from "./server.js";
|
|
22
|
+
import fs from "node:fs/promises";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
26
25
|
import { launchLSP } from "./launch.js";
|
|
26
|
+
import {
|
|
27
|
+
createRootDetector,
|
|
28
|
+
LSP_SERVERS,
|
|
29
|
+
type LSPServerInfo,
|
|
30
|
+
} from "./server.js";
|
|
27
31
|
|
|
28
32
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
29
33
|
|
|
@@ -45,11 +49,7 @@ export interface LSPConfig {
|
|
|
45
49
|
|
|
46
50
|
// --- Config Loading ---
|
|
47
51
|
|
|
48
|
-
const CONFIG_PATHS = [
|
|
49
|
-
".pi-lens/lsp.json",
|
|
50
|
-
".pi-lens.json",
|
|
51
|
-
"pi-lsp.json",
|
|
52
|
-
];
|
|
52
|
+
const CONFIG_PATHS = [".pi-lens/lsp.json", ".pi-lens.json", "pi-lsp.json"];
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Load LSP configuration from file
|
|
@@ -74,16 +74,19 @@ export async function loadLSPConfig(cwd: string): Promise<LSPConfig> {
|
|
|
74
74
|
/**
|
|
75
75
|
* Create LSPServerInfo from user configuration
|
|
76
76
|
*/
|
|
77
|
-
export function createCustomServer(
|
|
77
|
+
export function createCustomServer(
|
|
78
|
+
config: CustomServerConfig,
|
|
79
|
+
id: string,
|
|
80
|
+
): LSPServerInfo {
|
|
78
81
|
return {
|
|
79
82
|
id,
|
|
80
83
|
name: config.name,
|
|
81
84
|
extensions: config.extensions,
|
|
82
|
-
root: config.rootMarkers
|
|
85
|
+
root: config.rootMarkers
|
|
83
86
|
? createRootDetector(config.rootMarkers)
|
|
84
87
|
: async () => process.cwd(),
|
|
85
88
|
async spawn(root) {
|
|
86
|
-
const proc = launchLSP(config.command, config.args ?? ["--stdio"], {
|
|
89
|
+
const proc = await launchLSP(config.command, config.args ?? ["--stdio"], {
|
|
87
90
|
cwd: root,
|
|
88
91
|
env: config.env ? { ...process.env, ...config.env } : process.env,
|
|
89
92
|
});
|
|
@@ -102,18 +105,20 @@ let disabledServerIds: Set<string> = new Set();
|
|
|
102
105
|
*/
|
|
103
106
|
export async function initLSPConfig(cwd: string): Promise<void> {
|
|
104
107
|
const config = await loadLSPConfig(cwd);
|
|
105
|
-
|
|
108
|
+
|
|
106
109
|
// Clear previous custom servers
|
|
107
110
|
customServers = [];
|
|
108
111
|
disabledServerIds = new Set(config.disabledServers ?? []);
|
|
109
|
-
|
|
112
|
+
|
|
110
113
|
// Register custom servers from config
|
|
111
114
|
if (config.servers) {
|
|
112
115
|
for (const [id, serverConfig] of Object.entries(config.servers)) {
|
|
113
116
|
try {
|
|
114
117
|
const server = createCustomServer(serverConfig, id);
|
|
115
118
|
customServers.push(server);
|
|
116
|
-
console.error(
|
|
119
|
+
console.error(
|
|
120
|
+
`[lsp-config] Registered custom server: ${id} (${serverConfig.name})`,
|
|
121
|
+
);
|
|
117
122
|
} catch (err) {
|
|
118
123
|
console.error(`[lsp-config] Failed to register server ${id}:`, err);
|
|
119
124
|
}
|
|
@@ -126,7 +131,7 @@ export async function initLSPConfig(cwd: string): Promise<void> {
|
|
|
126
131
|
*/
|
|
127
132
|
export function getAllServers(): LSPServerInfo[] {
|
|
128
133
|
const all = [...LSP_SERVERS, ...customServers];
|
|
129
|
-
return all.filter(s => !disabledServerIds.has(s.id));
|
|
134
|
+
return all.filter((s) => !disabledServerIds.has(s.id));
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
/**
|
|
@@ -138,8 +143,6 @@ export function isServerDisabled(serverId: string): boolean {
|
|
|
138
143
|
|
|
139
144
|
// --- Override getServersForFile to include custom servers
|
|
140
145
|
|
|
141
|
-
import { getServersForFile as getBuiltinServersForFile } from "./server.js";
|
|
142
|
-
|
|
143
146
|
export function getServersForFileWithConfig(filePath: string): LSPServerInfo[] {
|
|
144
147
|
const ext = path.extname(filePath).toLowerCase();
|
|
145
148
|
return getAllServers().filter((server) => server.extensions.includes(ext));
|
package/clients/lsp/index.js
CHANGED
|
@@ -136,6 +136,75 @@ export class LSPService {
|
|
|
136
136
|
await spawned.client.waitForDiagnostics(filePath, 3000);
|
|
137
137
|
return spawned.client.getDiagnostics(filePath);
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Navigation: go to definition
|
|
141
|
+
*/
|
|
142
|
+
async definition(filePath, line, character) {
|
|
143
|
+
const spawned = await this.getClientForFile(filePath);
|
|
144
|
+
if (!spawned)
|
|
145
|
+
return [];
|
|
146
|
+
return spawned.client.definition(filePath, line, character);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Navigation: find all references
|
|
150
|
+
*/
|
|
151
|
+
async references(filePath, line, character, includeDeclaration = true) {
|
|
152
|
+
const spawned = await this.getClientForFile(filePath);
|
|
153
|
+
if (!spawned)
|
|
154
|
+
return [];
|
|
155
|
+
return spawned.client.references(filePath, line, character, includeDeclaration);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Navigation: hover info
|
|
159
|
+
*/
|
|
160
|
+
async hover(filePath, line, character) {
|
|
161
|
+
const spawned = await this.getClientForFile(filePath);
|
|
162
|
+
if (!spawned)
|
|
163
|
+
return null;
|
|
164
|
+
return spawned.client.hover(filePath, line, character);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Navigation: symbols in document
|
|
168
|
+
*/
|
|
169
|
+
async documentSymbol(filePath) {
|
|
170
|
+
const spawned = await this.getClientForFile(filePath);
|
|
171
|
+
if (!spawned)
|
|
172
|
+
return [];
|
|
173
|
+
return spawned.client.documentSymbol(filePath);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Navigation: workspace-wide symbol search
|
|
177
|
+
*/
|
|
178
|
+
async workspaceSymbol(query) {
|
|
179
|
+
// Use the first active client for workspace-level queries
|
|
180
|
+
const clients = Array.from(this.state.clients.values());
|
|
181
|
+
if (clients.length === 0)
|
|
182
|
+
return [];
|
|
183
|
+
return clients[0].workspaceSymbol(query);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Navigation: go to implementation
|
|
187
|
+
*/
|
|
188
|
+
async implementation(filePath, line, character) {
|
|
189
|
+
const spawned = await this.getClientForFile(filePath);
|
|
190
|
+
if (!spawned)
|
|
191
|
+
return [];
|
|
192
|
+
return spawned.client.implementation(filePath, line, character);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get all diagnostics across all tracked files (for cascade checking)
|
|
196
|
+
*/
|
|
197
|
+
async getAllDiagnostics() {
|
|
198
|
+
const all = new Map();
|
|
199
|
+
for (const [_key, client] of this.state.clients) {
|
|
200
|
+
const clientDiags = client.getAllDiagnostics();
|
|
201
|
+
for (const [filePath, diags] of clientDiags) {
|
|
202
|
+
const existing = all.get(filePath) ?? [];
|
|
203
|
+
all.set(filePath, [...existing, ...diags]);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return all;
|
|
207
|
+
}
|
|
139
208
|
/**
|
|
140
209
|
* Check if LSP is available for a file
|
|
141
210
|
*/
|
package/clients/lsp/index.ts
CHANGED
|
@@ -177,6 +177,88 @@ export class LSPService {
|
|
|
177
177
|
return spawned.client.getDiagnostics(filePath);
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Navigation: go to definition
|
|
182
|
+
*/
|
|
183
|
+
async definition(filePath: string, line: number, character: number) {
|
|
184
|
+
const spawned = await this.getClientForFile(filePath);
|
|
185
|
+
if (!spawned) return [];
|
|
186
|
+
return spawned.client.definition(filePath, line, character);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Navigation: find all references
|
|
191
|
+
*/
|
|
192
|
+
async references(
|
|
193
|
+
filePath: string,
|
|
194
|
+
line: number,
|
|
195
|
+
character: number,
|
|
196
|
+
includeDeclaration = true,
|
|
197
|
+
) {
|
|
198
|
+
const spawned = await this.getClientForFile(filePath);
|
|
199
|
+
if (!spawned) return [];
|
|
200
|
+
return spawned.client.references(
|
|
201
|
+
filePath,
|
|
202
|
+
line,
|
|
203
|
+
character,
|
|
204
|
+
includeDeclaration,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Navigation: hover info
|
|
210
|
+
*/
|
|
211
|
+
async hover(filePath: string, line: number, character: number) {
|
|
212
|
+
const spawned = await this.getClientForFile(filePath);
|
|
213
|
+
if (!spawned) return null;
|
|
214
|
+
return spawned.client.hover(filePath, line, character);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Navigation: symbols in document
|
|
219
|
+
*/
|
|
220
|
+
async documentSymbol(filePath: string) {
|
|
221
|
+
const spawned = await this.getClientForFile(filePath);
|
|
222
|
+
if (!spawned) return [];
|
|
223
|
+
return spawned.client.documentSymbol(filePath);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Navigation: workspace-wide symbol search
|
|
228
|
+
*/
|
|
229
|
+
async workspaceSymbol(query: string) {
|
|
230
|
+
// Use the first active client for workspace-level queries
|
|
231
|
+
const clients = Array.from(this.state.clients.values());
|
|
232
|
+
if (clients.length === 0) return [];
|
|
233
|
+
return clients[0].workspaceSymbol(query);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Navigation: go to implementation
|
|
238
|
+
*/
|
|
239
|
+
async implementation(filePath: string, line: number, character: number) {
|
|
240
|
+
const spawned = await this.getClientForFile(filePath);
|
|
241
|
+
if (!spawned) return [];
|
|
242
|
+
return spawned.client.implementation(filePath, line, character);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get all diagnostics across all tracked files (for cascade checking)
|
|
247
|
+
*/
|
|
248
|
+
async getAllDiagnostics(): Promise<
|
|
249
|
+
Map<string, import("./client.js").LSPDiagnostic[]>
|
|
250
|
+
> {
|
|
251
|
+
const all = new Map<string, import("./client.js").LSPDiagnostic[]>();
|
|
252
|
+
for (const [_key, client] of this.state.clients) {
|
|
253
|
+
const clientDiags = client.getAllDiagnostics();
|
|
254
|
+
for (const [filePath, diags] of clientDiags) {
|
|
255
|
+
const existing = all.get(filePath) ?? [];
|
|
256
|
+
all.set(filePath, [...existing, ...diags]);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return all;
|
|
260
|
+
}
|
|
261
|
+
|
|
180
262
|
/**
|
|
181
263
|
* Check if LSP is available for a file
|
|
182
264
|
*/
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* - User choice caching per project
|
|
10
10
|
* - Only prompts for "common" languages (Go, Rust, YAML, JSON, Bash)
|
|
11
11
|
*/
|
|
12
|
-
import
|
|
13
|
-
import * as
|
|
14
|
-
import
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
13
|
+
import * as fs from "node:fs/promises";
|
|
14
|
+
import * as path from "node:path";
|
|
15
15
|
// Languages that support interactive auto-install prompt
|
|
16
16
|
const COMMON_LANGUAGES = {
|
|
17
17
|
go: {
|
|
@@ -33,7 +33,7 @@ const COMMON_LANGUAGES = {
|
|
|
33
33
|
packageName: "yaml-language-server",
|
|
34
34
|
},
|
|
35
35
|
json: {
|
|
36
|
-
toolId: "vscode-json-
|
|
36
|
+
toolId: "vscode-json-language-server",
|
|
37
37
|
toolName: "JSON Language Server",
|
|
38
38
|
installCommand: "npm install -g vscode-langservers-extracted",
|
|
39
39
|
packageName: "vscode-langservers-extracted",
|
|
@@ -123,8 +123,8 @@ function promptUser(timeoutMs) {
|
|
|
123
123
|
*/
|
|
124
124
|
function isAutoInstallEnabled() {
|
|
125
125
|
// Check environment variable or process arguments
|
|
126
|
-
return process.env.PI_LENS_AUTO_INSTALL === "1" ||
|
|
127
|
-
process.argv.includes("--auto-install");
|
|
126
|
+
return (process.env.PI_LENS_AUTO_INSTALL === "1" ||
|
|
127
|
+
process.argv.includes("--auto-install"));
|
|
128
128
|
}
|
|
129
129
|
/**
|
|
130
130
|
* Attempt to install a tool
|
|
@@ -173,9 +173,20 @@ export async function promptForInstall(language, cwd) {
|
|
|
173
173
|
const thirtyDays = 30 * 24 * 60 * 60 * 1000;
|
|
174
174
|
if (Date.now() - cached.timestamp < thirtyDays) {
|
|
175
175
|
if (cached.choice === "yes" || cached.choice === "auto") {
|
|
176
|
-
|
|
176
|
+
// Verify binary actually exists before trusting cache
|
|
177
|
+
try {
|
|
178
|
+
const { execSync } = await import("node:child_process");
|
|
179
|
+
execSync(`which ${config.toolId}`, { stdio: "ignore" });
|
|
180
|
+
return true; // Binary exists, cache is valid
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Binary not found, invalidate cache and continue to install
|
|
184
|
+
console.error(`[pi-lens] Cached ${config.toolId} not found, re-installing...`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
return false; // User previously declined
|
|
177
189
|
}
|
|
178
|
-
return false;
|
|
179
190
|
}
|
|
180
191
|
}
|
|
181
192
|
// Check auto-install flag
|