pybao-cli 1.3.87 → 1.3.88
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/REPL-ORTTZIHU.js +47 -0
- package/dist/{acp-NMISJFTQ.js → acp-N5LUMCFW.js} +29 -29
- package/dist/{agentsValidate-G7H7CUZU.js → agentsValidate-WU4DXFHE.js} +7 -7
- package/dist/{ask-E7XSHQAN.js → ask-DW3ZOE75.js} +28 -28
- package/dist/{autoUpdater-EAH2H7AP.js → autoUpdater-KPSUPSW2.js} +3 -3
- package/dist/{chunk-HVMJPWC6.js → chunk-3VLZYTZF.js} +3 -3
- package/dist/{chunk-BHBHS3MY.js → chunk-474VRNOS.js} +3 -3
- package/dist/{chunk-N6ETO4PE.js → chunk-6563LX5Q.js} +4 -4
- package/dist/{chunk-HWIU6J2T.js → chunk-6NN7AYG5.js} +1035 -87
- package/dist/chunk-6NN7AYG5.js.map +7 -0
- package/dist/{chunk-F47KO7J2.js → chunk-7VIXLUAA.js} +1 -1
- package/dist/{chunk-BBZ2VZ5J.js → chunk-A33NVFXN.js} +3 -3
- package/dist/{chunk-MRU4VN5R.js → chunk-AKWG66R7.js} +2 -2
- package/dist/{chunk-XD5KXOSG.js → chunk-ALGAURMU.js} +2 -2
- package/dist/{chunk-UF2ARFEB.js → chunk-B3FVUMDX.js} +2 -2
- package/dist/{chunk-HHTFRFKN.js → chunk-BSX2GZFE.js} +2 -2
- package/dist/{chunk-KEALJIDY.js → chunk-E7KPOGEP.js} +208 -16
- package/dist/chunk-E7KPOGEP.js.map +7 -0
- package/dist/{chunk-T2HQBH46.js → chunk-FAZTRK5X.js} +3 -3
- package/dist/{chunk-KUIDN7FP.js → chunk-IKZIBMB2.js} +1 -1
- package/dist/{chunk-LSJILN2S.js → chunk-JGBEZWTT.js} +2 -2
- package/dist/{chunk-L62OCVZH.js → chunk-JWBNA7JZ.js} +16 -16
- package/dist/{chunk-VBJP55XA.js → chunk-K63HFXPF.js} +1 -1
- package/dist/{chunk-3RXA6IWG.js → chunk-KHYPI3CQ.js} +1 -1
- package/dist/{chunk-EQMKAITH.js → chunk-LUTA67LS.js} +1 -1
- package/dist/{chunk-H6NBGOQG.js → chunk-MV23FSF3.js} +1 -1
- package/dist/{chunk-H6NBGOQG.js.map → chunk-MV23FSF3.js.map} +1 -1
- package/dist/{chunk-7IKX4543.js → chunk-NDMBTSPF.js} +2 -2
- package/dist/{chunk-VAR6IG72.js → chunk-NZ4EJPCM.js} +1 -1
- package/dist/{chunk-WCX3ELKY.js → chunk-OK54YOWI.js} +89 -40
- package/dist/{chunk-WCX3ELKY.js.map → chunk-OK54YOWI.js.map} +2 -2
- package/dist/{chunk-Q32HEIH2.js → chunk-PAQMFEWF.js} +4 -4
- package/dist/{chunk-BPN5QY2K.js → chunk-PEJHTEHF.js} +2 -2
- package/dist/{chunk-Q7W7PGSO.js → chunk-PV5Z5CYP.js} +1 -1
- package/dist/{chunk-GEHCREEY.js → chunk-RNFV47CH.js} +3 -3
- package/dist/{chunk-6XDESUHK.js → chunk-RRMDKSTY.js} +1 -1
- package/dist/{chunk-SBF7BOS4.js → chunk-SIK2ANWJ.js} +3 -3
- package/dist/{chunk-4GB3OWJY.js → chunk-TD6X22GV.js} +3 -3
- package/dist/{chunk-KTLUYKAC.js → chunk-XJWHLOEL.js} +1 -1
- package/dist/{cli-VTNXK3N3.js → cli-HVWWEAL7.js} +87 -87
- package/dist/commands-CHRAM5WS.js +51 -0
- package/dist/{config-FBT5ABGI.js → config-Y4MVDPA6.js} +4 -4
- package/dist/{context-WBZK3AZX.js → context-UQPJORNF.js} +5 -5
- package/dist/{customCommands-IFWVN654.js → customCommands-YWYGEKU4.js} +4 -4
- package/dist/{env-5O5DDVID.js → env-F6OX4C6F.js} +2 -2
- package/dist/{file-K3P2ZXOY.js → file-AHNZVEQN.js} +4 -4
- package/dist/index.js +3 -3
- package/dist/{llm-XZHOT5QG.js → llm-EW2YTCZ5.js} +29 -29
- package/dist/{llmLazy-VVQLL2O4.js → llmLazy-5BNSJLTE.js} +1 -1
- package/dist/{loader-JC5XSGNC.js → loader-PLP3P26N.js} +4 -4
- package/dist/lsp-NWJUFY4J.js +33 -0
- package/dist/{lspAnchor-3AQPL2ZZ.js → lspAnchor-DUHDQOTT.js} +6 -6
- package/dist/{mcp-DTKD53FS.js → mcp-EYRRKW6Y.js} +7 -7
- package/dist/{mentionProcessor-JS7EY2LD.js → mentionProcessor-2SM3E3MP.js} +5 -5
- package/dist/{messages-GQMW2ANU.js → messages-S5JHQ3PM.js} +1 -1
- package/dist/{model-VGKT6DJE.js → model-7TNEREQL.js} +5 -5
- package/dist/{openai-TSJZC7MB.js → openai-BOA5DJUN.js} +5 -5
- package/dist/{outputStyles-5VLEN2UN.js → outputStyles-TVAV6JFM.js} +4 -4
- package/dist/{pluginRuntime-XKPZ3BX4.js → pluginRuntime-SPIXKVXE.js} +6 -6
- package/dist/{pluginValidation-ZHCDH3MZ.js → pluginValidation-IMP6FT66.js} +6 -6
- package/dist/prompts-GQLHTZEX.js +53 -0
- package/dist/{pybAgentSessionLoad-5CPNQIXV.js → pybAgentSessionLoad-R55QBFO6.js} +4 -4
- package/dist/{pybAgentSessionResume-VCYFMJCA.js → pybAgentSessionResume-NWYYW7YR.js} +4 -4
- package/dist/{pybAgentStreamJsonSession-DOTUU74W.js → pybAgentStreamJsonSession-LNJMEFT7.js} +1 -1
- package/dist/{pybHooks-W2EX2SUW.js → pybHooks-PAVO67PL.js} +4 -4
- package/dist/query-DG4EGQYV.js +55 -0
- package/dist/{registry-OUOC3F5A.js → registry-34E5OJNF.js} +11 -5
- package/dist/{ripgrep-MEONNP4W.js → ripgrep-KHBNZP7Y.js} +3 -3
- package/dist/{skillMarketplace-34N7IKSR.js → skillMarketplace-WKEXF2PI.js} +3 -3
- package/dist/{state-6NDQ3LIC.js → state-E757NA7C.js} +2 -2
- package/dist/{theme-7K5257BK.js → theme-KZNXBKP2.js} +5 -5
- package/dist/{toolPermissionSettings-2QVLET4R.js → toolPermissionSettings-FYVFSFBN.js} +6 -6
- package/dist/tools-XM6GAEJL.js +52 -0
- package/dist/{userInput-MF4TLCQC.js → userInput-ROFICTJI.js} +30 -30
- package/package.json +1 -1
- package/dist/REPL-J7EKVUMW.js +0 -47
- package/dist/chunk-HWIU6J2T.js.map +0 -7
- package/dist/chunk-KEALJIDY.js.map +0 -7
- package/dist/commands-SNKMFZOR.js +0 -51
- package/dist/lsp-NJNGVY3E.js +0 -17
- package/dist/prompts-MQWEK5T7.js +0 -53
- package/dist/query-OH75KHOB.js +0 -55
- package/dist/tools-6MGNB35T.js +0 -52
- /package/dist/{REPL-J7EKVUMW.js.map → REPL-ORTTZIHU.js.map} +0 -0
- /package/dist/{acp-NMISJFTQ.js.map → acp-N5LUMCFW.js.map} +0 -0
- /package/dist/{agentsValidate-G7H7CUZU.js.map → agentsValidate-WU4DXFHE.js.map} +0 -0
- /package/dist/{ask-E7XSHQAN.js.map → ask-DW3ZOE75.js.map} +0 -0
- /package/dist/{autoUpdater-EAH2H7AP.js.map → autoUpdater-KPSUPSW2.js.map} +0 -0
- /package/dist/{chunk-HVMJPWC6.js.map → chunk-3VLZYTZF.js.map} +0 -0
- /package/dist/{chunk-BHBHS3MY.js.map → chunk-474VRNOS.js.map} +0 -0
- /package/dist/{chunk-N6ETO4PE.js.map → chunk-6563LX5Q.js.map} +0 -0
- /package/dist/{chunk-F47KO7J2.js.map → chunk-7VIXLUAA.js.map} +0 -0
- /package/dist/{chunk-BBZ2VZ5J.js.map → chunk-A33NVFXN.js.map} +0 -0
- /package/dist/{chunk-MRU4VN5R.js.map → chunk-AKWG66R7.js.map} +0 -0
- /package/dist/{chunk-XD5KXOSG.js.map → chunk-ALGAURMU.js.map} +0 -0
- /package/dist/{chunk-UF2ARFEB.js.map → chunk-B3FVUMDX.js.map} +0 -0
- /package/dist/{chunk-HHTFRFKN.js.map → chunk-BSX2GZFE.js.map} +0 -0
- /package/dist/{chunk-T2HQBH46.js.map → chunk-FAZTRK5X.js.map} +0 -0
- /package/dist/{chunk-KUIDN7FP.js.map → chunk-IKZIBMB2.js.map} +0 -0
- /package/dist/{chunk-LSJILN2S.js.map → chunk-JGBEZWTT.js.map} +0 -0
- /package/dist/{chunk-L62OCVZH.js.map → chunk-JWBNA7JZ.js.map} +0 -0
- /package/dist/{chunk-VBJP55XA.js.map → chunk-K63HFXPF.js.map} +0 -0
- /package/dist/{chunk-3RXA6IWG.js.map → chunk-KHYPI3CQ.js.map} +0 -0
- /package/dist/{chunk-EQMKAITH.js.map → chunk-LUTA67LS.js.map} +0 -0
- /package/dist/{chunk-7IKX4543.js.map → chunk-NDMBTSPF.js.map} +0 -0
- /package/dist/{chunk-VAR6IG72.js.map → chunk-NZ4EJPCM.js.map} +0 -0
- /package/dist/{chunk-Q32HEIH2.js.map → chunk-PAQMFEWF.js.map} +0 -0
- /package/dist/{chunk-BPN5QY2K.js.map → chunk-PEJHTEHF.js.map} +0 -0
- /package/dist/{chunk-Q7W7PGSO.js.map → chunk-PV5Z5CYP.js.map} +0 -0
- /package/dist/{chunk-GEHCREEY.js.map → chunk-RNFV47CH.js.map} +0 -0
- /package/dist/{chunk-6XDESUHK.js.map → chunk-RRMDKSTY.js.map} +0 -0
- /package/dist/{chunk-SBF7BOS4.js.map → chunk-SIK2ANWJ.js.map} +0 -0
- /package/dist/{chunk-4GB3OWJY.js.map → chunk-TD6X22GV.js.map} +0 -0
- /package/dist/{chunk-KTLUYKAC.js.map → chunk-XJWHLOEL.js.map} +0 -0
- /package/dist/{cli-VTNXK3N3.js.map → cli-HVWWEAL7.js.map} +0 -0
- /package/dist/{commands-SNKMFZOR.js.map → commands-CHRAM5WS.js.map} +0 -0
- /package/dist/{config-FBT5ABGI.js.map → config-Y4MVDPA6.js.map} +0 -0
- /package/dist/{context-WBZK3AZX.js.map → context-UQPJORNF.js.map} +0 -0
- /package/dist/{customCommands-IFWVN654.js.map → customCommands-YWYGEKU4.js.map} +0 -0
- /package/dist/{env-5O5DDVID.js.map → env-F6OX4C6F.js.map} +0 -0
- /package/dist/{file-K3P2ZXOY.js.map → file-AHNZVEQN.js.map} +0 -0
- /package/dist/{llm-XZHOT5QG.js.map → llm-EW2YTCZ5.js.map} +0 -0
- /package/dist/{llmLazy-VVQLL2O4.js.map → llmLazy-5BNSJLTE.js.map} +0 -0
- /package/dist/{loader-JC5XSGNC.js.map → loader-PLP3P26N.js.map} +0 -0
- /package/dist/{lsp-NJNGVY3E.js.map → lsp-NWJUFY4J.js.map} +0 -0
- /package/dist/{lspAnchor-3AQPL2ZZ.js.map → lspAnchor-DUHDQOTT.js.map} +0 -0
- /package/dist/{mcp-DTKD53FS.js.map → mcp-EYRRKW6Y.js.map} +0 -0
- /package/dist/{mentionProcessor-JS7EY2LD.js.map → mentionProcessor-2SM3E3MP.js.map} +0 -0
- /package/dist/{messages-GQMW2ANU.js.map → messages-S5JHQ3PM.js.map} +0 -0
- /package/dist/{model-VGKT6DJE.js.map → model-7TNEREQL.js.map} +0 -0
- /package/dist/{openai-TSJZC7MB.js.map → openai-BOA5DJUN.js.map} +0 -0
- /package/dist/{outputStyles-5VLEN2UN.js.map → outputStyles-TVAV6JFM.js.map} +0 -0
- /package/dist/{pluginRuntime-XKPZ3BX4.js.map → pluginRuntime-SPIXKVXE.js.map} +0 -0
- /package/dist/{pluginValidation-ZHCDH3MZ.js.map → pluginValidation-IMP6FT66.js.map} +0 -0
- /package/dist/{prompts-MQWEK5T7.js.map → prompts-GQLHTZEX.js.map} +0 -0
- /package/dist/{pybAgentSessionLoad-5CPNQIXV.js.map → pybAgentSessionLoad-R55QBFO6.js.map} +0 -0
- /package/dist/{pybAgentSessionResume-VCYFMJCA.js.map → pybAgentSessionResume-NWYYW7YR.js.map} +0 -0
- /package/dist/{pybAgentStreamJsonSession-DOTUU74W.js.map → pybAgentStreamJsonSession-LNJMEFT7.js.map} +0 -0
- /package/dist/{pybHooks-W2EX2SUW.js.map → pybHooks-PAVO67PL.js.map} +0 -0
- /package/dist/{query-OH75KHOB.js.map → query-DG4EGQYV.js.map} +0 -0
- /package/dist/{registry-OUOC3F5A.js.map → registry-34E5OJNF.js.map} +0 -0
- /package/dist/{ripgrep-MEONNP4W.js.map → ripgrep-KHBNZP7Y.js.map} +0 -0
- /package/dist/{skillMarketplace-34N7IKSR.js.map → skillMarketplace-WKEXF2PI.js.map} +0 -0
- /package/dist/{state-6NDQ3LIC.js.map → state-E757NA7C.js.map} +0 -0
- /package/dist/{theme-7K5257BK.js.map → theme-KZNXBKP2.js.map} +0 -0
- /package/dist/{toolPermissionSettings-2QVLET4R.js.map → toolPermissionSettings-FYVFSFBN.js.map} +0 -0
- /package/dist/{tools-6MGNB35T.js.map → tools-XM6GAEJL.js.map} +0 -0
- /package/dist/{userInput-MF4TLCQC.js.map → userInput-ROFICTJI.js.map} +0 -0
|
@@ -2,20 +2,22 @@ import { createRequire as __pybCreateRequire } from "node:module";
|
|
|
2
2
|
const require = __pybCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
LspServerRegistry,
|
|
5
|
-
findNearestRoot
|
|
6
|
-
|
|
5
|
+
findNearestRoot,
|
|
6
|
+
getInstallNotices
|
|
7
|
+
} from "./chunk-E7KPOGEP.js";
|
|
7
8
|
import {
|
|
8
9
|
levenshtein
|
|
9
10
|
} from "./chunk-UZ34JEUK.js";
|
|
10
11
|
import {
|
|
11
12
|
getCwd
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-IKZIBMB2.js";
|
|
13
14
|
|
|
14
15
|
// src/lsp/index.ts
|
|
15
16
|
import { extname as extname2 } from "path";
|
|
16
17
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
17
|
-
import { readFile as
|
|
18
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
18
19
|
import * as http from "http";
|
|
20
|
+
import { createHash as createHash2 } from "crypto";
|
|
19
21
|
|
|
20
22
|
// src/tools/search/LspTool/client/generic.ts
|
|
21
23
|
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node.js";
|
|
@@ -27,6 +29,7 @@ var DiagnosticsEventBus = class {
|
|
|
27
29
|
lastEventAt = /* @__PURE__ */ new Map();
|
|
28
30
|
debounceTimers = /* @__PURE__ */ new Map();
|
|
29
31
|
waiting = /* @__PURE__ */ new Map();
|
|
32
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
30
33
|
constructor(options) {
|
|
31
34
|
this.debounceMs = options?.debounceMs ?? 150;
|
|
32
35
|
}
|
|
@@ -44,6 +47,12 @@ var DiagnosticsEventBus = class {
|
|
|
44
47
|
for (const resolver of Array.from(waiters)) {
|
|
45
48
|
resolver();
|
|
46
49
|
}
|
|
50
|
+
const subs = this.subscriptions.get(uri);
|
|
51
|
+
if (subs) {
|
|
52
|
+
for (const handler of Array.from(subs)) {
|
|
53
|
+
handler(uri);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
47
56
|
}, this.debounceMs);
|
|
48
57
|
this.debounceTimers.set(uri, timer);
|
|
49
58
|
}
|
|
@@ -73,6 +82,17 @@ var DiagnosticsEventBus = class {
|
|
|
73
82
|
}, timeoutMs);
|
|
74
83
|
});
|
|
75
84
|
}
|
|
85
|
+
subscribe(uri, handler) {
|
|
86
|
+
const subs = this.subscriptions.get(uri) ?? /* @__PURE__ */ new Set();
|
|
87
|
+
subs.add(handler);
|
|
88
|
+
this.subscriptions.set(uri, subs);
|
|
89
|
+
return () => {
|
|
90
|
+
const current = this.subscriptions.get(uri);
|
|
91
|
+
if (!current) return;
|
|
92
|
+
current.delete(handler);
|
|
93
|
+
if (current.size === 0) this.subscriptions.delete(uri);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
76
96
|
};
|
|
77
97
|
var GenericLspClient = class {
|
|
78
98
|
constructor(serverCommand, serverArgs, cwd, rootPath, extraEnv, initializationOptions) {
|
|
@@ -296,7 +316,16 @@ var GenericLspClient = class {
|
|
|
296
316
|
}
|
|
297
317
|
async waitForReadiness(filePath, timeoutMs = 3e3) {
|
|
298
318
|
const normalizedUri = this.normalizeUri(filePath);
|
|
319
|
+
let done = false;
|
|
320
|
+
const unsubscribe = this.diagnosticsBus.subscribe(normalizedUri, () => {
|
|
321
|
+
done = true;
|
|
322
|
+
});
|
|
299
323
|
await this.diagnosticsBus.waitForIdle(normalizedUri, timeoutMs);
|
|
324
|
+
if (done) {
|
|
325
|
+
unsubscribe();
|
|
326
|
+
} else {
|
|
327
|
+
unsubscribe();
|
|
328
|
+
}
|
|
300
329
|
}
|
|
301
330
|
on(event, handler) {
|
|
302
331
|
this.diagnosticEvents.on(event, handler);
|
|
@@ -352,6 +381,12 @@ var LspClientManager = class _LspClientManager {
|
|
|
352
381
|
clients = /* @__PURE__ */ new Map();
|
|
353
382
|
spawning = /* @__PURE__ */ new Map();
|
|
354
383
|
broken = /* @__PURE__ */ new Set();
|
|
384
|
+
failures = /* @__PURE__ */ new Map();
|
|
385
|
+
readiness = /* @__PURE__ */ new Map();
|
|
386
|
+
prewarmByRoot = /* @__PURE__ */ new Map();
|
|
387
|
+
prewarmHits = /* @__PURE__ */ new Map();
|
|
388
|
+
spawnMetrics = /* @__PURE__ */ new Map();
|
|
389
|
+
requestMetrics = /* @__PURE__ */ new Map();
|
|
355
390
|
constructor() {
|
|
356
391
|
}
|
|
357
392
|
static getInstance() {
|
|
@@ -367,6 +402,36 @@ var LspClientManager = class _LspClientManager {
|
|
|
367
402
|
if (serverInfo?.languageId) return serverInfo.languageId;
|
|
368
403
|
return LspServerRegistry.getInstance().getLanguageIdForExtension(ext);
|
|
369
404
|
}
|
|
405
|
+
getPrewarmTtlMs() {
|
|
406
|
+
const override = Number.parseInt(process.env.PYB_LSP_PREWARM_TTL_MS ?? "", 10);
|
|
407
|
+
if (Number.isFinite(override) && override > 0) return override;
|
|
408
|
+
return 3e4;
|
|
409
|
+
}
|
|
410
|
+
getPrewarmTriggerCount() {
|
|
411
|
+
const override = Number.parseInt(process.env.PYB_LSP_PREWARM_TRIGGER_COUNT ?? "", 10);
|
|
412
|
+
if (Number.isFinite(override) && override > 0) return override;
|
|
413
|
+
return 2;
|
|
414
|
+
}
|
|
415
|
+
shouldPrewarm(rootPath) {
|
|
416
|
+
const now = this.now();
|
|
417
|
+
const last = this.prewarmByRoot.get(rootPath);
|
|
418
|
+
const ttlMs = this.getPrewarmTtlMs();
|
|
419
|
+
if (last !== void 0 && now - last < ttlMs) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
const nextHits = (this.prewarmHits.get(rootPath) ?? 0) + 1;
|
|
423
|
+
this.prewarmHits.set(rootPath, nextHits);
|
|
424
|
+
if (nextHits < this.getPrewarmTriggerCount()) {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
this.prewarmHits.delete(rootPath);
|
|
428
|
+
this.prewarmByRoot.set(rootPath, now);
|
|
429
|
+
return true;
|
|
430
|
+
}
|
|
431
|
+
async maybeEnsureWorkspaceClients(rootPath) {
|
|
432
|
+
if (!this.shouldPrewarm(rootPath)) return;
|
|
433
|
+
await this.ensureWorkspaceClients(rootPath);
|
|
434
|
+
}
|
|
370
435
|
async ensureWorkspaceClients(rootPath) {
|
|
371
436
|
try {
|
|
372
437
|
const files = await readdir(rootPath, { withFileTypes: true });
|
|
@@ -464,7 +529,7 @@ var LspClientManager = class _LspClientManager {
|
|
|
464
529
|
const safeCwd = /[^\x00-\x7F]/.test(resolvedRoot) ? process.cwd() : resolvedRoot;
|
|
465
530
|
let initOpts = serverInfo.initializationOptions;
|
|
466
531
|
if (serverInfo.id === "pyright" || serverInfo.id === "ty") {
|
|
467
|
-
const { detectPythonVenv } = await import("./registry-
|
|
532
|
+
const { detectPythonVenv } = await import("./registry-34E5OJNF.js");
|
|
468
533
|
const pythonPath = detectPythonVenv(resolvedRoot);
|
|
469
534
|
if (pythonPath) {
|
|
470
535
|
initOpts = { ...initOpts ?? {}, pythonPath };
|
|
@@ -479,6 +544,8 @@ var LspClientManager = class _LspClientManager {
|
|
|
479
544
|
initOpts
|
|
480
545
|
);
|
|
481
546
|
await nextClient.initialize();
|
|
547
|
+
nextClient.serverId = serverInfo.id;
|
|
548
|
+
nextClient.serverKey = key;
|
|
482
549
|
this.clients.set(key, nextClient);
|
|
483
550
|
return nextClient;
|
|
484
551
|
}
|
|
@@ -489,19 +556,30 @@ var LspClientManager = class _LspClientManager {
|
|
|
489
556
|
this.clients.clear();
|
|
490
557
|
this.broken.clear();
|
|
491
558
|
this.spawning.clear();
|
|
559
|
+
this.failures.clear();
|
|
560
|
+
this.readiness.clear();
|
|
561
|
+
this.prewarmByRoot.clear();
|
|
562
|
+
this.prewarmHits.clear();
|
|
563
|
+
this.spawnMetrics.clear();
|
|
564
|
+
this.requestMetrics.clear();
|
|
492
565
|
}
|
|
493
566
|
async workspaceSymbol(query, rootPath = getCwd(), options) {
|
|
494
567
|
if (this.clients.size === 0) {
|
|
495
|
-
await this.
|
|
568
|
+
await this.maybeEnsureWorkspaceClients(rootPath);
|
|
496
569
|
}
|
|
497
570
|
const results = [];
|
|
498
571
|
for (const client of this.clients.values()) {
|
|
572
|
+
const serverId = client.serverId ?? "unknown";
|
|
573
|
+
this.recordRequestAttempt(serverId, "workspaceSymbol");
|
|
574
|
+
const startAt = this.now();
|
|
499
575
|
try {
|
|
500
576
|
const symbols = await client.workspaceSymbol(query);
|
|
501
577
|
if (symbols && Array.isArray(symbols)) {
|
|
502
578
|
results.push(...symbols);
|
|
503
579
|
}
|
|
580
|
+
this.recordRequestSuccess(serverId, "workspaceSymbol", this.now() - startAt);
|
|
504
581
|
} catch (e) {
|
|
582
|
+
this.recordRequestFailure(serverId, "workspaceSymbol", "error");
|
|
505
583
|
}
|
|
506
584
|
}
|
|
507
585
|
if (results.length === 0 && query.length > 0) {
|
|
@@ -659,11 +737,67 @@ var LspClientManager = class _LspClientManager {
|
|
|
659
737
|
diagnostics.push({ client: key, entries: snapshot });
|
|
660
738
|
}
|
|
661
739
|
}
|
|
740
|
+
const failures = Array.from(this.failures.entries()).map(([client, entry]) => ({
|
|
741
|
+
client,
|
|
742
|
+
reason: entry.reason,
|
|
743
|
+
count: entry.count,
|
|
744
|
+
until: entry.until,
|
|
745
|
+
last: entry.last
|
|
746
|
+
}));
|
|
747
|
+
const readiness = Array.from(this.readiness.entries()).map(([filePath, at]) => ({
|
|
748
|
+
filePath,
|
|
749
|
+
at
|
|
750
|
+
}));
|
|
751
|
+
const prewarms = Array.from(this.prewarmByRoot.entries()).map(([root, at]) => ({
|
|
752
|
+
root,
|
|
753
|
+
at
|
|
754
|
+
}));
|
|
755
|
+
const metrics = Array.from(this.spawnMetrics.entries()).map(([server, entry]) => {
|
|
756
|
+
const { avgLatency, p95Latency } = this.computeLatencyStats(entry.latencies);
|
|
757
|
+
const successRate = entry.attempts === 0 ? 0 : entry.successes / entry.attempts;
|
|
758
|
+
return {
|
|
759
|
+
server,
|
|
760
|
+
attempts: entry.attempts,
|
|
761
|
+
successes: entry.successes,
|
|
762
|
+
failures: entry.failures,
|
|
763
|
+
successRate,
|
|
764
|
+
latencyAvgMs: avgLatency,
|
|
765
|
+
latencyP95Ms: p95Latency,
|
|
766
|
+
failureReasons: { ...entry.failureReasons }
|
|
767
|
+
};
|
|
768
|
+
});
|
|
769
|
+
const regressionFactor = this.getRequestRegressionFactor();
|
|
770
|
+
const requestMetrics = Array.from(this.requestMetrics.values()).map((entry) => {
|
|
771
|
+
const { avgLatency, p95Latency } = this.computeLatencyStats(entry.latencies);
|
|
772
|
+
const successRate = entry.attempts === 0 ? 0 : entry.successes / entry.attempts;
|
|
773
|
+
const baselineAvg = entry.baseline?.avgMs ?? 0;
|
|
774
|
+
const baselineP95 = entry.baseline?.p95Ms ?? 0;
|
|
775
|
+
const regression = entry.baseline !== void 0 && (avgLatency > baselineAvg * regressionFactor || p95Latency > baselineP95 * regressionFactor);
|
|
776
|
+
return {
|
|
777
|
+
server: entry.serverId,
|
|
778
|
+
operation: entry.operation,
|
|
779
|
+
attempts: entry.attempts,
|
|
780
|
+
successes: entry.successes,
|
|
781
|
+
failures: entry.failures,
|
|
782
|
+
successRate,
|
|
783
|
+
latencyAvgMs: avgLatency,
|
|
784
|
+
latencyP95Ms: p95Latency,
|
|
785
|
+
baselineAvgMs: baselineAvg,
|
|
786
|
+
baselineP95Ms: baselineP95,
|
|
787
|
+
regression,
|
|
788
|
+
failureReasons: { ...entry.failureReasons }
|
|
789
|
+
};
|
|
790
|
+
});
|
|
662
791
|
return {
|
|
663
792
|
clients: Array.from(this.clients.keys()),
|
|
664
793
|
spawning: Array.from(this.spawning.keys()),
|
|
665
794
|
broken: Array.from(this.broken.values()),
|
|
666
|
-
diagnostics
|
|
795
|
+
diagnostics,
|
|
796
|
+
failures,
|
|
797
|
+
readiness,
|
|
798
|
+
prewarms,
|
|
799
|
+
metrics,
|
|
800
|
+
requestMetrics
|
|
667
801
|
};
|
|
668
802
|
}
|
|
669
803
|
// New helper to expose waitForReadiness to clients who only have a manager
|
|
@@ -671,6 +805,7 @@ var LspClientManager = class _LspClientManager {
|
|
|
671
805
|
const client = await this.getClientForFile(filePath);
|
|
672
806
|
if (client) {
|
|
673
807
|
await client.waitForReadiness(filePath, timeoutMs);
|
|
808
|
+
this.readiness.set(filePath, this.now());
|
|
674
809
|
}
|
|
675
810
|
}
|
|
676
811
|
matchesServerFilters(filePath, serverInfo) {
|
|
@@ -708,43 +843,173 @@ var LspClientManager = class _LspClientManager {
|
|
|
708
843
|
}
|
|
709
844
|
return defaultRoot;
|
|
710
845
|
}
|
|
846
|
+
now() {
|
|
847
|
+
return Date.now();
|
|
848
|
+
}
|
|
849
|
+
computeLatencyStats(latencies) {
|
|
850
|
+
const latencySamples = [...latencies].sort((a, b) => a - b);
|
|
851
|
+
const avgLatency = latencySamples.length === 0 ? 0 : latencySamples.reduce((sum, value) => sum + value, 0) / latencySamples.length;
|
|
852
|
+
const p95Index = latencySamples.length === 0 ? -1 : Math.ceil(latencySamples.length * 0.95) - 1;
|
|
853
|
+
const p95Latency = p95Index >= 0 ? latencySamples[p95Index] : 0;
|
|
854
|
+
return { avgLatency, p95Latency };
|
|
855
|
+
}
|
|
856
|
+
getMetricsEntry(serverId) {
|
|
857
|
+
let entry = this.spawnMetrics.get(serverId);
|
|
858
|
+
if (!entry) {
|
|
859
|
+
entry = {
|
|
860
|
+
attempts: 0,
|
|
861
|
+
successes: 0,
|
|
862
|
+
failures: 0,
|
|
863
|
+
latencies: [],
|
|
864
|
+
failureReasons: { prepare: 0, spawn: 0 }
|
|
865
|
+
};
|
|
866
|
+
this.spawnMetrics.set(serverId, entry);
|
|
867
|
+
}
|
|
868
|
+
return entry;
|
|
869
|
+
}
|
|
870
|
+
recordAttempt(serverId) {
|
|
871
|
+
const entry = this.getMetricsEntry(serverId);
|
|
872
|
+
entry.attempts += 1;
|
|
873
|
+
}
|
|
874
|
+
recordSuccess(serverId, latencyMs) {
|
|
875
|
+
const entry = this.getMetricsEntry(serverId);
|
|
876
|
+
entry.successes += 1;
|
|
877
|
+
entry.latencies.push(latencyMs);
|
|
878
|
+
if (entry.latencies.length > 50) {
|
|
879
|
+
entry.latencies.shift();
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
recordFailure(serverId, reason) {
|
|
883
|
+
const entry = this.getMetricsEntry(serverId);
|
|
884
|
+
entry.failures += 1;
|
|
885
|
+
entry.failureReasons[reason] += 1;
|
|
886
|
+
}
|
|
887
|
+
getRequestKey(serverId, operation) {
|
|
888
|
+
return `${serverId}::${operation}`;
|
|
889
|
+
}
|
|
890
|
+
getRequestEntry(serverId, operation) {
|
|
891
|
+
const key = this.getRequestKey(serverId, operation);
|
|
892
|
+
let entry = this.requestMetrics.get(key);
|
|
893
|
+
if (!entry) {
|
|
894
|
+
entry = {
|
|
895
|
+
serverId,
|
|
896
|
+
operation,
|
|
897
|
+
attempts: 0,
|
|
898
|
+
successes: 0,
|
|
899
|
+
failures: 0,
|
|
900
|
+
latencies: [],
|
|
901
|
+
failureReasons: { unavailable: 0, error: 0, timeout: 0, invalid: 0 }
|
|
902
|
+
};
|
|
903
|
+
this.requestMetrics.set(key, entry);
|
|
904
|
+
}
|
|
905
|
+
return entry;
|
|
906
|
+
}
|
|
907
|
+
getRequestBaselineSampleSize() {
|
|
908
|
+
return 10;
|
|
909
|
+
}
|
|
910
|
+
getRequestRegressionFactor() {
|
|
911
|
+
return 1.5;
|
|
912
|
+
}
|
|
913
|
+
recordRequestAttempt(serverId, operation) {
|
|
914
|
+
const entry = this.getRequestEntry(serverId, operation);
|
|
915
|
+
entry.attempts += 1;
|
|
916
|
+
}
|
|
917
|
+
recordRequestSuccess(serverId, operation, latencyMs) {
|
|
918
|
+
const entry = this.getRequestEntry(serverId, operation);
|
|
919
|
+
entry.successes += 1;
|
|
920
|
+
entry.latencies.push(latencyMs);
|
|
921
|
+
if (entry.latencies.length > 50) {
|
|
922
|
+
entry.latencies.shift();
|
|
923
|
+
}
|
|
924
|
+
if (!entry.baseline && entry.latencies.length >= this.getRequestBaselineSampleSize()) {
|
|
925
|
+
const { avgLatency, p95Latency } = this.computeLatencyStats(entry.latencies);
|
|
926
|
+
entry.baseline = {
|
|
927
|
+
avgMs: avgLatency,
|
|
928
|
+
p95Ms: p95Latency,
|
|
929
|
+
samples: entry.latencies.length
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
recordRequestFailure(serverId, operation, reason) {
|
|
934
|
+
const entry = this.getRequestEntry(serverId, operation);
|
|
935
|
+
entry.failures += 1;
|
|
936
|
+
entry.failureReasons[reason] += 1;
|
|
937
|
+
}
|
|
938
|
+
getBaseTtlMs(reason) {
|
|
939
|
+
const override = Number.parseInt(process.env.PYB_LSP_BROKEN_TTL_MS ?? "", 10);
|
|
940
|
+
if (Number.isFinite(override) && override > 0) return override;
|
|
941
|
+
return reason === "prepare" ? 5e3 : 1e4;
|
|
942
|
+
}
|
|
943
|
+
markBroken(key, reason) {
|
|
944
|
+
const prev = this.failures.get(key);
|
|
945
|
+
const count = prev ? prev.count + 1 : 1;
|
|
946
|
+
const base = this.getBaseTtlMs(reason);
|
|
947
|
+
const ttl = Math.min(base * Math.pow(2, count - 1), 6e4);
|
|
948
|
+
const until = this.now() + ttl;
|
|
949
|
+
this.failures.set(key, { count, until, reason, last: this.now() });
|
|
950
|
+
this.broken.add(key);
|
|
951
|
+
}
|
|
952
|
+
isWithinBrokenTtl(key) {
|
|
953
|
+
const entry = this.failures.get(key);
|
|
954
|
+
if (!entry) return false;
|
|
955
|
+
const active = this.now() < entry.until;
|
|
956
|
+
if (!active) {
|
|
957
|
+
this.failures.delete(key);
|
|
958
|
+
this.broken.delete(key);
|
|
959
|
+
}
|
|
960
|
+
return active;
|
|
961
|
+
}
|
|
711
962
|
async getClientForServer(filePath, serverInfo, rootPath) {
|
|
712
963
|
if (!this.matchesServerFilters(filePath, serverInfo)) {
|
|
713
964
|
return null;
|
|
714
965
|
}
|
|
715
966
|
const resolvedRoot = rootPath ?? await this.resolveRootForServer(filePath, serverInfo);
|
|
967
|
+
const key = `${resolvedRoot}:${serverInfo.id}`;
|
|
968
|
+
if (this.isWithinBrokenTtl(key)) {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
const existingSpawn = this.spawning.get(key);
|
|
972
|
+
if (existingSpawn) {
|
|
973
|
+
return existingSpawn;
|
|
974
|
+
}
|
|
975
|
+
const existingClient = this.clients.get(key);
|
|
976
|
+
if (existingClient) {
|
|
977
|
+
return existingClient;
|
|
978
|
+
}
|
|
979
|
+
this.recordAttempt(serverInfo.id);
|
|
716
980
|
try {
|
|
717
981
|
const ready = await serverInfo.prepare();
|
|
718
982
|
if (!ready) {
|
|
983
|
+
this.recordFailure(serverInfo.id, "prepare");
|
|
984
|
+
this.markBroken(key, "prepare");
|
|
719
985
|
return null;
|
|
720
986
|
}
|
|
721
987
|
} catch (e) {
|
|
988
|
+
this.recordFailure(serverInfo.id, "prepare");
|
|
989
|
+
this.markBroken(key, "prepare");
|
|
722
990
|
return null;
|
|
723
991
|
}
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
if (!client) {
|
|
734
|
-
const spawnPromise = (async () => {
|
|
735
|
-
try {
|
|
736
|
-
return await this.spawnClient(resolvedRoot, serverInfo, key);
|
|
737
|
-
} catch (error) {
|
|
738
|
-
this.broken.add(key);
|
|
739
|
-
return null;
|
|
740
|
-
} finally {
|
|
741
|
-
this.spawning.delete(key);
|
|
992
|
+
const spawnStart = this.now();
|
|
993
|
+
const spawnPromise = (async () => {
|
|
994
|
+
try {
|
|
995
|
+
const nextClient = await this.spawnClient(resolvedRoot, serverInfo, key);
|
|
996
|
+
if (nextClient) {
|
|
997
|
+
this.recordSuccess(serverInfo.id, this.now() - spawnStart);
|
|
998
|
+
} else {
|
|
999
|
+
this.recordFailure(serverInfo.id, "spawn");
|
|
1000
|
+
this.markBroken(key, "spawn");
|
|
742
1001
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
1002
|
+
return nextClient;
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
this.recordFailure(serverInfo.id, "spawn");
|
|
1005
|
+
this.markBroken(key, "spawn");
|
|
1006
|
+
return null;
|
|
1007
|
+
} finally {
|
|
1008
|
+
this.spawning.delete(key);
|
|
1009
|
+
}
|
|
1010
|
+
})();
|
|
1011
|
+
this.spawning.set(key, spawnPromise);
|
|
1012
|
+
return spawnPromise;
|
|
748
1013
|
}
|
|
749
1014
|
};
|
|
750
1015
|
|
|
@@ -1159,63 +1424,39 @@ var initParser = async () => {
|
|
|
1159
1424
|
// src/utils/tree-sitter/lsp-adapter.ts
|
|
1160
1425
|
import { readFile as readFile2 } from "fs/promises";
|
|
1161
1426
|
var SYMBOL_KIND_MAP2 = {
|
|
1162
|
-
// TypeScript / JavaScript
|
|
1163
1427
|
function_declaration: "Function",
|
|
1428
|
+
function_expression: "Function",
|
|
1429
|
+
arrow_function: "Function",
|
|
1164
1430
|
method_definition: "Method",
|
|
1165
1431
|
class_declaration: "Class",
|
|
1166
1432
|
interface_declaration: "Interface",
|
|
1433
|
+
enum_declaration: "Enum",
|
|
1434
|
+
type_alias_declaration: "Type",
|
|
1167
1435
|
variable_declarator: "Variable",
|
|
1168
1436
|
export_statement: "Module",
|
|
1169
|
-
// Python
|
|
1170
1437
|
function_definition: "Function",
|
|
1171
1438
|
class_definition: "Class",
|
|
1172
|
-
|
|
1173
|
-
// function_definition is shared with Python/others
|
|
1174
|
-
// C#
|
|
1439
|
+
assignment: "Variable",
|
|
1175
1440
|
namespace_declaration: "Namespace",
|
|
1176
1441
|
method_declaration: "Method",
|
|
1177
1442
|
struct_declaration: "Struct",
|
|
1178
|
-
enum_declaration: "Enum",
|
|
1179
1443
|
property_declaration: "Property",
|
|
1180
|
-
// class_declaration shared
|
|
1181
|
-
// interface_declaration shared
|
|
1182
|
-
// Go
|
|
1183
1444
|
type_declaration: "Class",
|
|
1184
|
-
// often struct/interface
|
|
1185
1445
|
field_declaration: "Field",
|
|
1186
|
-
// function_declaration shared
|
|
1187
|
-
// method_declaration shared
|
|
1188
|
-
// Rust
|
|
1189
1446
|
function_item: "Function",
|
|
1190
1447
|
struct_item: "Struct",
|
|
1191
1448
|
enum_item: "Enum",
|
|
1192
1449
|
impl_item: "Class",
|
|
1193
1450
|
trait_item: "Interface",
|
|
1194
1451
|
mod_item: "Module",
|
|
1195
|
-
// Java
|
|
1196
|
-
// class_declaration shared
|
|
1197
|
-
// method_declaration shared
|
|
1198
|
-
// interface_declaration shared
|
|
1199
|
-
// enum_declaration shared
|
|
1200
|
-
// Scala
|
|
1201
1452
|
object_definition: "Class",
|
|
1202
1453
|
trait_definition: "Interface",
|
|
1203
|
-
// class_definition shared with Python
|
|
1204
|
-
// function_definition shared with Python
|
|
1205
|
-
// Ruby
|
|
1206
1454
|
module: "Module"
|
|
1207
|
-
// class shared
|
|
1208
|
-
// method shared
|
|
1209
|
-
// PHP
|
|
1210
|
-
// class_declaration shared
|
|
1211
|
-
// method_declaration shared
|
|
1212
|
-
// function_definition shared
|
|
1213
1455
|
};
|
|
1214
1456
|
var TreeSitterLspAdapter = class {
|
|
1215
1457
|
static async getDocumentSymbols(filePath) {
|
|
1216
1458
|
const langId = ParserRegistry.getLanguage(filePath);
|
|
1217
1459
|
if (!langId) {
|
|
1218
|
-
console.warn(`No language found for file: ${filePath}`);
|
|
1219
1460
|
return [];
|
|
1220
1461
|
}
|
|
1221
1462
|
try {
|
|
@@ -1224,8 +1465,10 @@ var TreeSitterLspAdapter = class {
|
|
|
1224
1465
|
const tree = parser.parse(content);
|
|
1225
1466
|
return this.collectSymbols(tree.rootNode);
|
|
1226
1467
|
} catch (error) {
|
|
1227
|
-
|
|
1228
|
-
|
|
1468
|
+
if (error instanceof Error) {
|
|
1469
|
+
throw new Error(`Tree-sitter parse failed for ${filePath}: ${error.message}`);
|
|
1470
|
+
}
|
|
1471
|
+
throw new Error(`Tree-sitter parse failed for ${filePath}`);
|
|
1229
1472
|
}
|
|
1230
1473
|
}
|
|
1231
1474
|
static collectSymbols(node) {
|
|
@@ -1259,7 +1502,7 @@ var TreeSitterLspAdapter = class {
|
|
|
1259
1502
|
const nameNode = node.childForFieldName("name");
|
|
1260
1503
|
if (nameNode) return nameNode.text;
|
|
1261
1504
|
for (const child of node.namedChildren) {
|
|
1262
|
-
if (child.type === "identifier" || child.type === "type_identifier") {
|
|
1505
|
+
if (child.type === "identifier" || child.type === "type_identifier" || child.type === "property_identifier" || child.type === "field_identifier" || child.type === "scoped_identifier") {
|
|
1263
1506
|
return child.text;
|
|
1264
1507
|
}
|
|
1265
1508
|
}
|
|
@@ -1269,25 +1512,121 @@ var TreeSitterLspAdapter = class {
|
|
|
1269
1512
|
|
|
1270
1513
|
// src/utils/tree-sitter/scope-analyzer.ts
|
|
1271
1514
|
import { Query } from "web-tree-sitter";
|
|
1515
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1516
|
+
import path3 from "path";
|
|
1517
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1518
|
+
import { createHash } from "crypto";
|
|
1272
1519
|
var QUERIES = {
|
|
1273
1520
|
python: `
|
|
1274
1521
|
(function_definition name: (identifier) @name)
|
|
1275
1522
|
(parameters (identifier) @param)
|
|
1276
1523
|
(assignment left: (identifier) @var)
|
|
1524
|
+
(for_statement left: (identifier) @var)
|
|
1277
1525
|
`,
|
|
1278
1526
|
typescript: `
|
|
1279
1527
|
(function_declaration name: (identifier) @name)
|
|
1528
|
+
(method_definition name: (property_identifier) @name)
|
|
1280
1529
|
(variable_declarator name: (identifier) @var)
|
|
1530
|
+
(variable_declarator name: (array_pattern (identifier) @var))
|
|
1531
|
+
(variable_declarator name: (object_pattern (pair_pattern key: (property_identifier) value: (identifier) @var)))
|
|
1532
|
+
(variable_declarator name: (object_pattern (pair_pattern key: (property_identifier) value: (assignment_pattern left: (identifier) @var))))
|
|
1533
|
+
(variable_declarator name: (object_pattern (shorthand_property_identifier_pattern) @var))
|
|
1281
1534
|
(required_parameter pattern: (identifier) @param)
|
|
1535
|
+
(required_parameter pattern: (array_pattern (identifier) @param))
|
|
1536
|
+
(required_parameter pattern: (object_pattern (shorthand_property_identifier_pattern) @param))
|
|
1282
1537
|
(optional_parameter pattern: (identifier) @param)
|
|
1538
|
+
(shorthand_property_identifier_pattern) @var
|
|
1539
|
+
(catch_clause (identifier) @var)
|
|
1540
|
+
(for_in_statement left: (identifier) @var)
|
|
1541
|
+
(for_statement initializer: (lexical_declaration (variable_declarator name: (identifier) @var)))
|
|
1283
1542
|
`
|
|
1284
1543
|
};
|
|
1544
|
+
var SCOPE_NODE_TYPES = {
|
|
1545
|
+
python: /* @__PURE__ */ new Set(["function_definition", "class_definition", "module"]),
|
|
1546
|
+
typescript: /* @__PURE__ */ new Set([
|
|
1547
|
+
"function_declaration",
|
|
1548
|
+
"function",
|
|
1549
|
+
"function_expression",
|
|
1550
|
+
"arrow_function",
|
|
1551
|
+
"method_definition",
|
|
1552
|
+
"class_declaration",
|
|
1553
|
+
"statement_block",
|
|
1554
|
+
"program"
|
|
1555
|
+
])
|
|
1556
|
+
};
|
|
1557
|
+
var SCOPE_BOUNDARY_TYPES = {
|
|
1558
|
+
python: /* @__PURE__ */ new Set(["function_definition", "class_definition"]),
|
|
1559
|
+
typescript: /* @__PURE__ */ new Set([
|
|
1560
|
+
"function_declaration",
|
|
1561
|
+
"function",
|
|
1562
|
+
"function_expression",
|
|
1563
|
+
"arrow_function",
|
|
1564
|
+
"method_definition",
|
|
1565
|
+
"class_declaration",
|
|
1566
|
+
"statement_block"
|
|
1567
|
+
])
|
|
1568
|
+
};
|
|
1569
|
+
var __filename = fileURLToPath5(import.meta.url);
|
|
1570
|
+
var __dirname2 = path3.dirname(__filename);
|
|
1571
|
+
async function loadScopeQuery(langKey) {
|
|
1572
|
+
const queryPath = path3.join(__dirname2, "queries", langKey, "locals.scm");
|
|
1573
|
+
try {
|
|
1574
|
+
return await readFile3(queryPath, "utf-8");
|
|
1575
|
+
} catch {
|
|
1576
|
+
return QUERIES[langKey] ?? null;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
var QUERY_PRIORITY_LANGUAGES = ["typescript", "python"];
|
|
1580
|
+
var SCOPE_CACHE = /* @__PURE__ */ new Map();
|
|
1581
|
+
var SCOPE_CACHE_TTL_MS = 30 * 1e3;
|
|
1582
|
+
function computeHash(value) {
|
|
1583
|
+
return createHash("sha256").update(value).digest("hex");
|
|
1584
|
+
}
|
|
1585
|
+
function getScopeCacheKey(langKey, code, position) {
|
|
1586
|
+
const hash = computeHash(code);
|
|
1587
|
+
return `${langKey}:${hash}:${position.row}:${position.column}`;
|
|
1588
|
+
}
|
|
1589
|
+
function getCachedScope(key, now) {
|
|
1590
|
+
const cached = SCOPE_CACHE.get(key);
|
|
1591
|
+
if (!cached) return null;
|
|
1592
|
+
if (now >= cached.expiresAt) {
|
|
1593
|
+
SCOPE_CACHE.delete(key);
|
|
1594
|
+
return null;
|
|
1595
|
+
}
|
|
1596
|
+
return cached.value;
|
|
1597
|
+
}
|
|
1285
1598
|
var ScopeAnalyzer = class {
|
|
1599
|
+
static async getQuerySource(langKey) {
|
|
1600
|
+
return loadScopeQuery(langKey);
|
|
1601
|
+
}
|
|
1602
|
+
static async getQueryCoverage(langKey, code) {
|
|
1603
|
+
const parser = await loadLanguage(langKey);
|
|
1604
|
+
const queryStr = await loadScopeQuery(langKey);
|
|
1605
|
+
if (!queryStr) return { total: 0, unique: 0, names: [] };
|
|
1606
|
+
const language = parser.language;
|
|
1607
|
+
if (!language) return { total: 0, unique: 0, names: [] };
|
|
1608
|
+
const query = new Query(language, queryStr);
|
|
1609
|
+
const tree = parser.parse(code);
|
|
1610
|
+
const captures = query.captures(tree.rootNode);
|
|
1611
|
+
const names = captures.map((c) => c.node.text);
|
|
1612
|
+
const uniqueNames = Array.from(new Set(names));
|
|
1613
|
+
return { total: captures.length, unique: uniqueNames.length, names: uniqueNames };
|
|
1614
|
+
}
|
|
1615
|
+
static isPriorityLanguage(langKey) {
|
|
1616
|
+
return QUERY_PRIORITY_LANGUAGES.includes(langKey);
|
|
1617
|
+
}
|
|
1618
|
+
static getPriorityLanguages() {
|
|
1619
|
+
return [...QUERY_PRIORITY_LANGUAGES];
|
|
1620
|
+
}
|
|
1286
1621
|
static async getScope(filename, code, position) {
|
|
1287
1622
|
const langKey = ParserRegistry.getLanguage(filename);
|
|
1288
1623
|
if (!langKey) {
|
|
1289
1624
|
throw new Error(`Unsupported language for file: ${filename}`);
|
|
1290
1625
|
}
|
|
1626
|
+
const now = Date.now();
|
|
1627
|
+
const cacheKey = getScopeCacheKey(langKey, code, position);
|
|
1628
|
+
const cached = getCachedScope(cacheKey, now);
|
|
1629
|
+
if (cached) return cached;
|
|
1291
1630
|
const parser = await loadLanguage(langKey);
|
|
1292
1631
|
const tree = parser.parse(code);
|
|
1293
1632
|
const node = tree.rootNode.descendantForPosition(position);
|
|
@@ -1297,17 +1636,16 @@ var ScopeAnalyzer = class {
|
|
|
1297
1636
|
ancestors.push(current);
|
|
1298
1637
|
current = current.parent;
|
|
1299
1638
|
}
|
|
1300
|
-
const scopeNodes = ancestors.filter(
|
|
1301
|
-
|
|
1302
|
-
n.type
|
|
1303
|
-
|
|
1304
|
-
);
|
|
1639
|
+
const scopeNodes = ancestors.filter((n) => {
|
|
1640
|
+
const types = SCOPE_NODE_TYPES[langKey];
|
|
1641
|
+
return types ? types.has(n.type) : false;
|
|
1642
|
+
});
|
|
1305
1643
|
const result = {
|
|
1306
1644
|
locals: [],
|
|
1307
1645
|
closure: []
|
|
1308
1646
|
};
|
|
1309
1647
|
if (scopeNodes.length === 0) return result;
|
|
1310
|
-
const queryStr =
|
|
1648
|
+
const queryStr = await loadScopeQuery(langKey);
|
|
1311
1649
|
if (!queryStr) return result;
|
|
1312
1650
|
const language = parser.language;
|
|
1313
1651
|
if (!language) return result;
|
|
@@ -1319,23 +1657,53 @@ var ScopeAnalyzer = class {
|
|
|
1319
1657
|
for (const capture of captures) {
|
|
1320
1658
|
const defNode = capture.node;
|
|
1321
1659
|
let temp = defNode.parent;
|
|
1322
|
-
if (temp && (temp.type === "function_definition" || temp.type === "function_declaration")) {
|
|
1323
|
-
if (capture.name === "name") {
|
|
1324
|
-
temp = temp.parent;
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
1660
|
let isDirect = true;
|
|
1328
1661
|
while (temp && temp.id !== scopeNode.id) {
|
|
1329
|
-
|
|
1662
|
+
const boundaryTypes = SCOPE_BOUNDARY_TYPES[langKey];
|
|
1663
|
+
if (boundaryTypes?.has(temp.type)) {
|
|
1330
1664
|
isDirect = false;
|
|
1331
1665
|
break;
|
|
1332
1666
|
}
|
|
1333
1667
|
temp = temp.parent;
|
|
1334
1668
|
}
|
|
1669
|
+
if (capture.name === "name") {
|
|
1670
|
+
const boundaryTypes = SCOPE_BOUNDARY_TYPES[langKey];
|
|
1671
|
+
if (boundaryTypes?.has(defNode.parent?.type ?? "")) {
|
|
1672
|
+
temp = defNode.parent?.parent ?? null;
|
|
1673
|
+
isDirect = true;
|
|
1674
|
+
while (temp && temp.id !== scopeNode.id) {
|
|
1675
|
+
if (boundaryTypes?.has(temp.type)) {
|
|
1676
|
+
isDirect = false;
|
|
1677
|
+
break;
|
|
1678
|
+
}
|
|
1679
|
+
temp = temp.parent;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1335
1683
|
if (isDirect) {
|
|
1336
1684
|
names.add(defNode.text);
|
|
1337
1685
|
}
|
|
1338
1686
|
}
|
|
1687
|
+
if (i === 0 && scopeNode.type === "statement_block") {
|
|
1688
|
+
const boundaryTypes = SCOPE_BOUNDARY_TYPES[langKey];
|
|
1689
|
+
const parent = scopeNode.parent;
|
|
1690
|
+
if (parent && boundaryTypes?.has(parent.type)) {
|
|
1691
|
+
const parentCaptures = query.captures(parent);
|
|
1692
|
+
for (const capture of parentCaptures) {
|
|
1693
|
+
if (capture.name === "param") {
|
|
1694
|
+
names.add(capture.node.text);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
if (parent && (parent.type === "for_in_statement" || parent.type === "for_statement")) {
|
|
1699
|
+
const parentCaptures = query.captures(parent);
|
|
1700
|
+
for (const capture of parentCaptures) {
|
|
1701
|
+
if (capture.name === "var") {
|
|
1702
|
+
names.add(capture.node.text);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1339
1707
|
if (i === 0) {
|
|
1340
1708
|
result.locals = Array.from(names);
|
|
1341
1709
|
} else {
|
|
@@ -1345,6 +1713,7 @@ var ScopeAnalyzer = class {
|
|
|
1345
1713
|
}
|
|
1346
1714
|
}
|
|
1347
1715
|
result.closure = Array.from(new Set(result.closure));
|
|
1716
|
+
SCOPE_CACHE.set(cacheKey, { value: result, expiresAt: now + SCOPE_CACHE_TTL_MS });
|
|
1348
1717
|
return result;
|
|
1349
1718
|
}
|
|
1350
1719
|
};
|
|
@@ -1383,6 +1752,135 @@ ${formatTreeSitterSymbols(symbols)}` : "No symbols found.";
|
|
|
1383
1752
|
fileCount: symbols.length > 0 ? 1 : 0
|
|
1384
1753
|
};
|
|
1385
1754
|
}
|
|
1755
|
+
var fastPathFailures = /* @__PURE__ */ new Map();
|
|
1756
|
+
var fastPathLastFallbackReason = /* @__PURE__ */ new Map();
|
|
1757
|
+
var scopeLastFallbackReason = /* @__PURE__ */ new Map();
|
|
1758
|
+
var scopeStrategyUsed = /* @__PURE__ */ new Map();
|
|
1759
|
+
var operationFallbackReasons = /* @__PURE__ */ new Map();
|
|
1760
|
+
var LSP_FALLBACK_BOUNDARY = /* @__PURE__ */ new Set(["documentSymbol"]);
|
|
1761
|
+
var FALLBACK_TOGGLES = [
|
|
1762
|
+
{ operation: "diagnostics", env: "PYB_LSP_FALLBACK_DIAGNOSTICS", defaultEnabled: false },
|
|
1763
|
+
{ operation: "findReferences", env: "PYB_LSP_FALLBACK_REFERENCES", defaultEnabled: false },
|
|
1764
|
+
{
|
|
1765
|
+
operation: "prepareCallHierarchy",
|
|
1766
|
+
env: "PYB_LSP_FALLBACK_CALL_HIERARCHY",
|
|
1767
|
+
defaultEnabled: false
|
|
1768
|
+
},
|
|
1769
|
+
{
|
|
1770
|
+
operation: "incomingCalls",
|
|
1771
|
+
env: "PYB_LSP_FALLBACK_CALL_HIERARCHY",
|
|
1772
|
+
defaultEnabled: false
|
|
1773
|
+
},
|
|
1774
|
+
{
|
|
1775
|
+
operation: "outgoingCalls",
|
|
1776
|
+
env: "PYB_LSP_FALLBACK_CALL_HIERARCHY",
|
|
1777
|
+
defaultEnabled: false
|
|
1778
|
+
}
|
|
1779
|
+
];
|
|
1780
|
+
function isFallbackToggleEnabled(envKey, defaultEnabled) {
|
|
1781
|
+
const raw = String(process.env[envKey] ?? "").trim().toLowerCase();
|
|
1782
|
+
if (!raw) return defaultEnabled;
|
|
1783
|
+
if (raw === "0" || raw === "false" || raw === "off" || raw === "no") return false;
|
|
1784
|
+
if (raw === "1" || raw === "true" || raw === "on" || raw === "yes") return true;
|
|
1785
|
+
return defaultEnabled;
|
|
1786
|
+
}
|
|
1787
|
+
function refreshFallbackBoundaryFromEnv() {
|
|
1788
|
+
for (const toggle of FALLBACK_TOGGLES) {
|
|
1789
|
+
if (isFallbackToggleEnabled(toggle.env, toggle.defaultEnabled)) {
|
|
1790
|
+
LSP_FALLBACK_BOUNDARY.add(toggle.operation);
|
|
1791
|
+
} else {
|
|
1792
|
+
LSP_FALLBACK_BOUNDARY.delete(toggle.operation);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
refreshFallbackBoundaryFromEnv();
|
|
1797
|
+
var FAST_PATH_CONFIG = {
|
|
1798
|
+
exception: { windowMs: 5 * 60 * 1e3, threshold: 3 },
|
|
1799
|
+
empty: { windowMs: 60 * 1e3, threshold: 2 },
|
|
1800
|
+
timeout: { windowMs: 30 * 1e3, threshold: 1 }
|
|
1801
|
+
};
|
|
1802
|
+
var FAST_PATH_TIMEOUT_MS = 500;
|
|
1803
|
+
var FAST_PATH_CACHE_TTL_MS = 30 * 1e3;
|
|
1804
|
+
var fastPathSymbolCache = /* @__PURE__ */ new Map();
|
|
1805
|
+
function recordFastPathFailure(filePath, reason, now) {
|
|
1806
|
+
let perFile = fastPathFailures.get(filePath);
|
|
1807
|
+
if (!perFile) {
|
|
1808
|
+
perFile = /* @__PURE__ */ new Map();
|
|
1809
|
+
fastPathFailures.set(filePath, perFile);
|
|
1810
|
+
}
|
|
1811
|
+
const config = FAST_PATH_CONFIG[reason];
|
|
1812
|
+
const existing = perFile.get(reason);
|
|
1813
|
+
if (!existing || now - existing.firstAt > config.windowMs) {
|
|
1814
|
+
perFile.set(reason, { count: 1, firstAt: now, lastAt: now });
|
|
1815
|
+
return;
|
|
1816
|
+
}
|
|
1817
|
+
existing.count += 1;
|
|
1818
|
+
existing.lastAt = now;
|
|
1819
|
+
}
|
|
1820
|
+
function clearFastPathFailures(filePath) {
|
|
1821
|
+
fastPathFailures.delete(filePath);
|
|
1822
|
+
fastPathLastFallbackReason.delete(filePath);
|
|
1823
|
+
}
|
|
1824
|
+
function recordOperationFallback(operation, filePath, reason) {
|
|
1825
|
+
let perOperation = operationFallbackReasons.get(operation);
|
|
1826
|
+
if (!perOperation) {
|
|
1827
|
+
perOperation = /* @__PURE__ */ new Map();
|
|
1828
|
+
operationFallbackReasons.set(operation, perOperation);
|
|
1829
|
+
}
|
|
1830
|
+
perOperation.set(filePath, reason);
|
|
1831
|
+
}
|
|
1832
|
+
function getOperationFallbackSnapshot(operation) {
|
|
1833
|
+
const entries = operationFallbackReasons.get(operation);
|
|
1834
|
+
if (!entries) return [];
|
|
1835
|
+
return Array.from(entries.entries()).map(([filePath, reason]) => ({
|
|
1836
|
+
filePath,
|
|
1837
|
+
reason
|
|
1838
|
+
}));
|
|
1839
|
+
}
|
|
1840
|
+
function computeContentHash(content) {
|
|
1841
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
1842
|
+
}
|
|
1843
|
+
function getCachedFastPathSymbols(filePath, hash, now) {
|
|
1844
|
+
const cached = fastPathSymbolCache.get(filePath);
|
|
1845
|
+
if (!cached) return null;
|
|
1846
|
+
if (cached.hash !== hash || now >= cached.expiresAt) {
|
|
1847
|
+
fastPathSymbolCache.delete(filePath);
|
|
1848
|
+
return null;
|
|
1849
|
+
}
|
|
1850
|
+
return cached.symbols;
|
|
1851
|
+
}
|
|
1852
|
+
function setCachedFastPathSymbols(filePath, hash, symbols, now) {
|
|
1853
|
+
fastPathSymbolCache.set(filePath, {
|
|
1854
|
+
hash,
|
|
1855
|
+
symbols,
|
|
1856
|
+
expiresAt: now + FAST_PATH_CACHE_TTL_MS
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
function shouldSkipFastPath(filePath, now) {
|
|
1860
|
+
const perFile = fastPathFailures.get(filePath);
|
|
1861
|
+
if (!perFile) return null;
|
|
1862
|
+
for (const [reason, state] of perFile.entries()) {
|
|
1863
|
+
const config = FAST_PATH_CONFIG[reason];
|
|
1864
|
+
if (now - state.firstAt <= config.windowMs && state.count >= config.threshold) {
|
|
1865
|
+
return reason;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return null;
|
|
1869
|
+
}
|
|
1870
|
+
function withTimeout(promise, timeoutMs) {
|
|
1871
|
+
return new Promise((resolve, reject) => {
|
|
1872
|
+
const timer = setTimeout(() => {
|
|
1873
|
+
reject(new Error("Tree-sitter timeout"));
|
|
1874
|
+
}, timeoutMs);
|
|
1875
|
+
promise.then((result) => {
|
|
1876
|
+
clearTimeout(timer);
|
|
1877
|
+
resolve(result);
|
|
1878
|
+
}).catch((error) => {
|
|
1879
|
+
clearTimeout(timer);
|
|
1880
|
+
reject(error);
|
|
1881
|
+
});
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1386
1884
|
function normalizeWorkspaceSymbol(sym) {
|
|
1387
1885
|
if (!sym || typeof sym !== "object") return null;
|
|
1388
1886
|
const range = sym.range ?? sym.location?.range;
|
|
@@ -1390,6 +1888,97 @@ function normalizeWorkspaceSymbol(sym) {
|
|
|
1390
1888
|
const uri = sym.uri ?? sym.location?.uri;
|
|
1391
1889
|
return { ...sym, range, uri };
|
|
1392
1890
|
}
|
|
1891
|
+
function isIdentifierChar(char) {
|
|
1892
|
+
return /[A-Za-z0-9_$']/.test(char);
|
|
1893
|
+
}
|
|
1894
|
+
function extractTokenAtPosition(lines, zeroBasedLine, zeroBasedCharacter) {
|
|
1895
|
+
if (zeroBasedLine < 0 || zeroBasedLine >= lines.length) return null;
|
|
1896
|
+
const line = lines[zeroBasedLine] ?? "";
|
|
1897
|
+
if (zeroBasedCharacter < 0 || zeroBasedCharacter > line.length) return null;
|
|
1898
|
+
const tokenRe = /[\w$'!]+|[+\-*/%&|^~<>=]+/g;
|
|
1899
|
+
let match;
|
|
1900
|
+
while ((match = tokenRe.exec(line)) !== null) {
|
|
1901
|
+
const start = match.index;
|
|
1902
|
+
const end = start + match[0].length;
|
|
1903
|
+
if (zeroBasedCharacter >= start && zeroBasedCharacter < end) {
|
|
1904
|
+
const token = match[0];
|
|
1905
|
+
return token.length > 50 ? token.slice(0, 50) : token;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
function findTokenOccurrencesInLines(token, lines) {
|
|
1911
|
+
const results = [];
|
|
1912
|
+
if (!token) return results;
|
|
1913
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
1914
|
+
const line = lines[lineIndex];
|
|
1915
|
+
let fromIndex = 0;
|
|
1916
|
+
while (fromIndex < line.length) {
|
|
1917
|
+
const index = line.indexOf(token, fromIndex);
|
|
1918
|
+
if (index === -1) break;
|
|
1919
|
+
const before = index > 0 ? line[index - 1] : "";
|
|
1920
|
+
const afterIndex = index + token.length;
|
|
1921
|
+
const after = afterIndex < line.length ? line[afterIndex] : "";
|
|
1922
|
+
if ((!before || !isIdentifierChar(before)) && (!after || !isIdentifierChar(after))) {
|
|
1923
|
+
results.push({ line: lineIndex, start: index, end: afterIndex });
|
|
1924
|
+
}
|
|
1925
|
+
fromIndex = index + token.length;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
return results;
|
|
1929
|
+
}
|
|
1930
|
+
function findSymbolAtPosition(symbols, line, character) {
|
|
1931
|
+
let matched = null;
|
|
1932
|
+
const visit = (node) => {
|
|
1933
|
+
if (!node?.range) return;
|
|
1934
|
+
const start = node.range.start;
|
|
1935
|
+
const end = node.range.end;
|
|
1936
|
+
const within = line > start.line || line === start.line && character >= start.character ? line < end.line || line === end.line && character <= end.character : false;
|
|
1937
|
+
if (within) {
|
|
1938
|
+
matched = node;
|
|
1939
|
+
if (Array.isArray(node.children)) {
|
|
1940
|
+
for (const child of node.children) {
|
|
1941
|
+
visit(child);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
for (const sym of symbols) visit(sym);
|
|
1947
|
+
return matched;
|
|
1948
|
+
}
|
|
1949
|
+
async function getTreeSitterDiagnostics(filePath) {
|
|
1950
|
+
const lang = ParserRegistry.getLanguage(filePath);
|
|
1951
|
+
if (!lang) return [];
|
|
1952
|
+
const parser = await loadLanguage(lang);
|
|
1953
|
+
const content = await readFile4(filePath, "utf-8");
|
|
1954
|
+
const tree = parser.parse(content);
|
|
1955
|
+
if (!tree) return [];
|
|
1956
|
+
const diagnostics = [];
|
|
1957
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1958
|
+
const visit = (node) => {
|
|
1959
|
+
const shouldReport = node.type === "ERROR" || node.isMissing || node.hasError && node.childCount === 0;
|
|
1960
|
+
if (shouldReport) {
|
|
1961
|
+
const range = {
|
|
1962
|
+
start: { line: node.startPosition.row, character: node.startPosition.column },
|
|
1963
|
+
end: { line: node.endPosition.row, character: node.endPosition.column }
|
|
1964
|
+
};
|
|
1965
|
+
const key = `${range.start.line}:${range.start.character}:${range.end.line}:${range.end.character}:${node.type}`;
|
|
1966
|
+
if (!seen.has(key)) {
|
|
1967
|
+
diagnostics.push({
|
|
1968
|
+
range,
|
|
1969
|
+
message: `Syntax error near ${node.type}`,
|
|
1970
|
+
severity: 1
|
|
1971
|
+
});
|
|
1972
|
+
seen.add(key);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
for (const child of node.children) {
|
|
1976
|
+
visit(child);
|
|
1977
|
+
}
|
|
1978
|
+
};
|
|
1979
|
+
visit(tree.rootNode);
|
|
1980
|
+
return diagnostics;
|
|
1981
|
+
}
|
|
1393
1982
|
function sortWorkspaceSymbols(symbols) {
|
|
1394
1983
|
return [...symbols].sort((a, b) => {
|
|
1395
1984
|
const nameA = String(a?.name ?? "").toLowerCase();
|
|
@@ -1406,10 +1995,27 @@ function sortWorkspaceSymbols(symbols) {
|
|
|
1406
1995
|
return charA - charB;
|
|
1407
1996
|
});
|
|
1408
1997
|
}
|
|
1998
|
+
function resolveScopeStrategy() {
|
|
1999
|
+
const raw = String(process.env.PYB_GET_SCOPE_STRATEGY ?? "").trim().toLowerCase();
|
|
2000
|
+
if (raw === "lsp") return "lsp";
|
|
2001
|
+
return "tree-sitter";
|
|
2002
|
+
}
|
|
2003
|
+
function getDocumentSymbolFallbackSnapshot() {
|
|
2004
|
+
return Array.from(fastPathLastFallbackReason.entries()).map(([filePath, reason]) => ({
|
|
2005
|
+
filePath,
|
|
2006
|
+
reason
|
|
2007
|
+
}));
|
|
2008
|
+
}
|
|
2009
|
+
function getScopeFallbackSnapshot() {
|
|
2010
|
+
return Array.from(scopeLastFallbackReason.entries()).map(([filePath, reason]) => ({
|
|
2011
|
+
filePath,
|
|
2012
|
+
reason
|
|
2013
|
+
}));
|
|
2014
|
+
}
|
|
1409
2015
|
function serveStatusWithProvider(statusProvider, options) {
|
|
1410
2016
|
const host = options?.host ?? "127.0.0.1";
|
|
1411
2017
|
const port = typeof options?.port === "number" ? options.port : 7337;
|
|
1412
|
-
const
|
|
2018
|
+
const path4 = options?.path ?? "/lsp/status";
|
|
1413
2019
|
const allowCors = options?.allowCors ?? true;
|
|
1414
2020
|
const corsHeaders = allowCors ? {
|
|
1415
2021
|
"Access-Control-Allow-Origin": "*",
|
|
@@ -1419,7 +2025,7 @@ function serveStatusWithProvider(statusProvider, options) {
|
|
|
1419
2025
|
const server = http.createServer((req, res) => {
|
|
1420
2026
|
const method = req.method ?? "GET";
|
|
1421
2027
|
const url = new URL(req.url ?? "/", `http://${host}`);
|
|
1422
|
-
if (url.pathname !==
|
|
2028
|
+
if (url.pathname !== path4) {
|
|
1423
2029
|
res.writeHead(404, corsHeaders);
|
|
1424
2030
|
res.end("Not Found");
|
|
1425
2031
|
return;
|
|
@@ -1439,66 +2045,103 @@ function serveStatusWithProvider(statusProvider, options) {
|
|
|
1439
2045
|
res.end(JSON.stringify(status, null, 2));
|
|
1440
2046
|
});
|
|
1441
2047
|
server.port = port;
|
|
1442
|
-
server.listen(port, host)
|
|
2048
|
+
server.listen(port, host, () => {
|
|
2049
|
+
const address = server.address();
|
|
2050
|
+
if (address && typeof address === "object") {
|
|
2051
|
+
;
|
|
2052
|
+
server.port = address.port;
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
1443
2055
|
return server;
|
|
1444
2056
|
}
|
|
1445
2057
|
var LspAPI = {
|
|
1446
2058
|
async run(input) {
|
|
1447
2059
|
const rootPath = input.rootPath ?? getCwd();
|
|
1448
|
-
const
|
|
2060
|
+
const manager = LspClientManager.getInstance();
|
|
2061
|
+
const client = await manager.getClient(
|
|
1449
2062
|
input.filePath,
|
|
1450
2063
|
rootPath
|
|
1451
2064
|
);
|
|
2065
|
+
const ext = extname2(input.filePath);
|
|
2066
|
+
const fallbackServer = LspServerRegistry.getInstance().getServerForExtension(ext);
|
|
2067
|
+
const serverId = client ? client.serverId ?? fallbackServer?.id ?? "unknown" : fallbackServer?.id ?? "unknown";
|
|
2068
|
+
const recordAttempt = (operation) => {
|
|
2069
|
+
manager.recordRequestAttempt(serverId, operation);
|
|
2070
|
+
};
|
|
2071
|
+
const recordSuccess = (operation, startedAt) => {
|
|
2072
|
+
manager.recordRequestSuccess(serverId, operation, Date.now() - startedAt);
|
|
2073
|
+
};
|
|
2074
|
+
const recordFailure = (operation, reason) => {
|
|
2075
|
+
manager.recordRequestFailure(serverId, operation, reason);
|
|
2076
|
+
};
|
|
1452
2077
|
if (client) {
|
|
1453
2078
|
try {
|
|
1454
2079
|
switch (input.operation) {
|
|
1455
2080
|
case "goToDefinition": {
|
|
1456
2081
|
if (input.line === void 0 || input.character === void 0) {
|
|
2082
|
+
recordAttempt("goToDefinition");
|
|
2083
|
+
recordFailure("goToDefinition", "invalid");
|
|
1457
2084
|
return {
|
|
1458
2085
|
formatted: "Error performing goToDefinition: Missing line/character",
|
|
1459
2086
|
resultCount: 0,
|
|
1460
2087
|
fileCount: 0
|
|
1461
2088
|
};
|
|
1462
2089
|
}
|
|
2090
|
+
recordAttempt("goToDefinition");
|
|
2091
|
+
const startedAt = Date.now();
|
|
1463
2092
|
const result = await client.goToDefinition(
|
|
1464
2093
|
input.filePath,
|
|
1465
2094
|
input.line,
|
|
1466
2095
|
input.character
|
|
1467
2096
|
);
|
|
2097
|
+
recordSuccess("goToDefinition", startedAt);
|
|
1468
2098
|
return formatGenericDefinitionResult(result);
|
|
1469
2099
|
}
|
|
1470
2100
|
case "findReferences": {
|
|
1471
2101
|
if (input.line === void 0 || input.character === void 0) {
|
|
2102
|
+
recordAttempt("findReferences");
|
|
2103
|
+
recordFailure("findReferences", "invalid");
|
|
1472
2104
|
return {
|
|
1473
2105
|
formatted: "Error performing findReferences: Missing line/character",
|
|
1474
2106
|
resultCount: 0,
|
|
1475
2107
|
fileCount: 0
|
|
1476
2108
|
};
|
|
1477
2109
|
}
|
|
2110
|
+
recordAttempt("findReferences");
|
|
2111
|
+
const startedAt = Date.now();
|
|
1478
2112
|
const result = await client.findReferences(
|
|
1479
2113
|
input.filePath,
|
|
1480
2114
|
input.line,
|
|
1481
2115
|
input.character
|
|
1482
2116
|
);
|
|
2117
|
+
recordSuccess("findReferences", startedAt);
|
|
1483
2118
|
return formatGenericReferencesResult(result);
|
|
1484
2119
|
}
|
|
1485
2120
|
case "hover": {
|
|
1486
2121
|
if (input.line === void 0 || input.character === void 0) {
|
|
2122
|
+
recordAttempt("hover");
|
|
2123
|
+
recordFailure("hover", "invalid");
|
|
1487
2124
|
return {
|
|
1488
2125
|
formatted: "Error performing hover: Missing line/character",
|
|
1489
2126
|
resultCount: 0,
|
|
1490
2127
|
fileCount: 0
|
|
1491
2128
|
};
|
|
1492
2129
|
}
|
|
2130
|
+
recordAttempt("hover");
|
|
2131
|
+
const startedAt = Date.now();
|
|
1493
2132
|
const result = await client.hover(
|
|
1494
2133
|
input.filePath,
|
|
1495
2134
|
input.line,
|
|
1496
2135
|
input.character
|
|
1497
2136
|
);
|
|
2137
|
+
recordSuccess("hover", startedAt);
|
|
1498
2138
|
return formatGenericHoverResult(result, input.line, input.character);
|
|
1499
2139
|
}
|
|
1500
2140
|
case "documentSymbol": {
|
|
2141
|
+
recordAttempt("documentSymbol");
|
|
2142
|
+
const startedAt = Date.now();
|
|
1501
2143
|
const result = await client.documentSymbol(input.filePath);
|
|
2144
|
+
recordSuccess("documentSymbol", startedAt);
|
|
1502
2145
|
return formatGenericDocumentSymbolResult(result);
|
|
1503
2146
|
}
|
|
1504
2147
|
case "workspaceSymbol": {
|
|
@@ -1511,44 +2154,58 @@ var LspAPI = {
|
|
|
1511
2154
|
}
|
|
1512
2155
|
case "goToImplementation": {
|
|
1513
2156
|
if (input.line === void 0 || input.character === void 0) {
|
|
2157
|
+
recordAttempt("goToImplementation");
|
|
2158
|
+
recordFailure("goToImplementation", "invalid");
|
|
1514
2159
|
return {
|
|
1515
2160
|
formatted: "Error performing goToImplementation: Missing line/character",
|
|
1516
2161
|
resultCount: 0,
|
|
1517
2162
|
fileCount: 0
|
|
1518
2163
|
};
|
|
1519
2164
|
}
|
|
2165
|
+
recordAttempt("goToImplementation");
|
|
2166
|
+
const startedAt = Date.now();
|
|
1520
2167
|
const result = await client.goToImplementation(
|
|
1521
2168
|
input.filePath,
|
|
1522
2169
|
input.line,
|
|
1523
2170
|
input.character
|
|
1524
2171
|
);
|
|
2172
|
+
recordSuccess("goToImplementation", startedAt);
|
|
1525
2173
|
return formatGenericDefinitionResult(result);
|
|
1526
2174
|
}
|
|
1527
2175
|
case "prepareCallHierarchy": {
|
|
1528
2176
|
if (input.line === void 0 || input.character === void 0) {
|
|
2177
|
+
recordAttempt("prepareCallHierarchy");
|
|
2178
|
+
recordFailure("prepareCallHierarchy", "invalid");
|
|
1529
2179
|
return {
|
|
1530
2180
|
formatted: "Error performing prepareCallHierarchy: Missing line/character",
|
|
1531
2181
|
resultCount: 0,
|
|
1532
2182
|
fileCount: 0
|
|
1533
2183
|
};
|
|
1534
2184
|
}
|
|
2185
|
+
recordAttempt("prepareCallHierarchy");
|
|
2186
|
+
const startedAt = Date.now();
|
|
1535
2187
|
const result = await client.prepareCallHierarchy(
|
|
1536
2188
|
input.filePath,
|
|
1537
2189
|
input.line,
|
|
1538
2190
|
input.character
|
|
1539
2191
|
);
|
|
2192
|
+
recordSuccess("prepareCallHierarchy", startedAt);
|
|
1540
2193
|
return formatGenericCallHierarchyItemsResult(
|
|
1541
2194
|
Array.isArray(result) ? result : []
|
|
1542
2195
|
);
|
|
1543
2196
|
}
|
|
1544
2197
|
case "incomingCalls": {
|
|
1545
2198
|
if (input.line === void 0 || input.character === void 0) {
|
|
2199
|
+
recordAttempt("incomingCalls");
|
|
2200
|
+
recordFailure("incomingCalls", "invalid");
|
|
1546
2201
|
return {
|
|
1547
2202
|
formatted: "Error performing incomingCalls: Missing line/character",
|
|
1548
2203
|
resultCount: 0,
|
|
1549
2204
|
fileCount: 0
|
|
1550
2205
|
};
|
|
1551
2206
|
}
|
|
2207
|
+
recordAttempt("incomingCalls");
|
|
2208
|
+
const startedAt = Date.now();
|
|
1552
2209
|
const items = await client.prepareCallHierarchy(
|
|
1553
2210
|
input.filePath,
|
|
1554
2211
|
input.line,
|
|
@@ -1560,16 +2217,21 @@ var LspAPI = {
|
|
|
1560
2217
|
)
|
|
1561
2218
|
);
|
|
1562
2219
|
const calls = callResults.flat().filter(Boolean);
|
|
2220
|
+
recordSuccess("incomingCalls", startedAt);
|
|
1563
2221
|
return formatGenericIncomingCallsResult(calls);
|
|
1564
2222
|
}
|
|
1565
2223
|
case "outgoingCalls": {
|
|
1566
2224
|
if (input.line === void 0 || input.character === void 0) {
|
|
2225
|
+
recordAttempt("outgoingCalls");
|
|
2226
|
+
recordFailure("outgoingCalls", "invalid");
|
|
1567
2227
|
return {
|
|
1568
2228
|
formatted: "Error performing outgoingCalls: Missing line/character",
|
|
1569
2229
|
resultCount: 0,
|
|
1570
2230
|
fileCount: 0
|
|
1571
2231
|
};
|
|
1572
2232
|
}
|
|
2233
|
+
recordAttempt("outgoingCalls");
|
|
2234
|
+
const startedAt = Date.now();
|
|
1573
2235
|
const items = await client.prepareCallHierarchy(
|
|
1574
2236
|
input.filePath,
|
|
1575
2237
|
input.line,
|
|
@@ -1581,9 +2243,12 @@ var LspAPI = {
|
|
|
1581
2243
|
)
|
|
1582
2244
|
);
|
|
1583
2245
|
const calls = callResults.flat().filter(Boolean);
|
|
2246
|
+
recordSuccess("outgoingCalls", startedAt);
|
|
1584
2247
|
return formatGenericOutgoingCallsResult(calls);
|
|
1585
2248
|
}
|
|
1586
2249
|
case "diagnostics": {
|
|
2250
|
+
recordAttempt("diagnostics");
|
|
2251
|
+
const startedAt = Date.now();
|
|
1587
2252
|
if (input.waitForDiagnostics) {
|
|
1588
2253
|
await client.waitForReadiness(
|
|
1589
2254
|
input.filePath,
|
|
@@ -1591,6 +2256,7 @@ var LspAPI = {
|
|
|
1591
2256
|
);
|
|
1592
2257
|
}
|
|
1593
2258
|
const diagnostics = client.getDiagnostics(input.filePath);
|
|
2259
|
+
recordSuccess("diagnostics", startedAt);
|
|
1594
2260
|
return formatGenericDiagnosticsResult(diagnostics);
|
|
1595
2261
|
}
|
|
1596
2262
|
default: {
|
|
@@ -1602,6 +2268,8 @@ var LspAPI = {
|
|
|
1602
2268
|
}
|
|
1603
2269
|
}
|
|
1604
2270
|
} catch (error) {
|
|
2271
|
+
recordAttempt(input.operation);
|
|
2272
|
+
recordFailure(input.operation, "error");
|
|
1605
2273
|
const message = error instanceof Error ? error.message : String(error);
|
|
1606
2274
|
return {
|
|
1607
2275
|
formatted: `Error performing ${input.operation} with generic client: ${message}`,
|
|
@@ -1610,7 +2278,112 @@ var LspAPI = {
|
|
|
1610
2278
|
};
|
|
1611
2279
|
}
|
|
1612
2280
|
}
|
|
1613
|
-
const
|
|
2281
|
+
const fallbackOperation = input.operation;
|
|
2282
|
+
recordAttempt(input.operation);
|
|
2283
|
+
recordFailure(input.operation, "unavailable");
|
|
2284
|
+
if (LSP_FALLBACK_BOUNDARY.has(fallbackOperation)) {
|
|
2285
|
+
const lang = ParserRegistry.getLanguage(input.filePath);
|
|
2286
|
+
if (!lang) {
|
|
2287
|
+
recordOperationFallback(fallbackOperation, input.filePath, "unsupported");
|
|
2288
|
+
} else {
|
|
2289
|
+
recordOperationFallback(fallbackOperation, input.filePath, "lsp-unavailable");
|
|
2290
|
+
if (fallbackOperation === "diagnostics") {
|
|
2291
|
+
try {
|
|
2292
|
+
const diagnostics = await getTreeSitterDiagnostics(input.filePath);
|
|
2293
|
+
const formatted = formatGenericDiagnosticsResult(diagnostics);
|
|
2294
|
+
return {
|
|
2295
|
+
formatted: `Diagnostics fallback: ${formatted.formatted}`,
|
|
2296
|
+
resultCount: formatted.resultCount,
|
|
2297
|
+
fileCount: formatted.fileCount
|
|
2298
|
+
};
|
|
2299
|
+
} catch {
|
|
2300
|
+
return {
|
|
2301
|
+
formatted: `Diagnostics fallback: LSP unavailable for ${ext}. Static analysis not available.`,
|
|
2302
|
+
resultCount: 0,
|
|
2303
|
+
fileCount: 0
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
if (fallbackOperation === "findReferences") {
|
|
2308
|
+
if (input.line !== void 0 && input.character !== void 0) {
|
|
2309
|
+
try {
|
|
2310
|
+
const content = await readFile4(input.filePath, "utf-8");
|
|
2311
|
+
const lines = content.split("\n");
|
|
2312
|
+
const token = extractTokenAtPosition(
|
|
2313
|
+
lines,
|
|
2314
|
+
input.line - 1,
|
|
2315
|
+
input.character - 1
|
|
2316
|
+
);
|
|
2317
|
+
if (token) {
|
|
2318
|
+
const occurrences = findTokenOccurrencesInLines(token, lines);
|
|
2319
|
+
const uri = normalizeUri(input.filePath);
|
|
2320
|
+
const refs = occurrences.map((entry) => ({
|
|
2321
|
+
uri,
|
|
2322
|
+
range: {
|
|
2323
|
+
start: { line: entry.line, character: entry.start },
|
|
2324
|
+
end: { line: entry.line, character: entry.end }
|
|
2325
|
+
}
|
|
2326
|
+
}));
|
|
2327
|
+
if (refs.length > 0) {
|
|
2328
|
+
const formatted = formatGenericReferencesResult(refs);
|
|
2329
|
+
return {
|
|
2330
|
+
formatted: `References fallback: ${formatted.formatted}`,
|
|
2331
|
+
resultCount: formatted.resultCount,
|
|
2332
|
+
fileCount: formatted.fileCount
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
} catch {
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
return {
|
|
2340
|
+
formatted: `References fallback: LSP unavailable for ${ext}. Returning 0 references.`,
|
|
2341
|
+
resultCount: 0,
|
|
2342
|
+
fileCount: 0
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
if (fallbackOperation === "prepareCallHierarchy") {
|
|
2346
|
+
if (input.line !== void 0 && input.character !== void 0) {
|
|
2347
|
+
try {
|
|
2348
|
+
const symbols = await TreeSitterLspAdapter.getDocumentSymbols(input.filePath);
|
|
2349
|
+
const symbol = findSymbolAtPosition(
|
|
2350
|
+
symbols,
|
|
2351
|
+
input.line - 1,
|
|
2352
|
+
input.character - 1
|
|
2353
|
+
);
|
|
2354
|
+
if (symbol) {
|
|
2355
|
+
const item = {
|
|
2356
|
+
name: symbol.name,
|
|
2357
|
+
kind: symbol.kind,
|
|
2358
|
+
uri: normalizeUri(input.filePath),
|
|
2359
|
+
range: symbol.range,
|
|
2360
|
+
selectionRange: symbol.range
|
|
2361
|
+
};
|
|
2362
|
+
const formatted = formatGenericCallHierarchyItemsResult([item]);
|
|
2363
|
+
return {
|
|
2364
|
+
formatted: `Call hierarchy fallback: ${formatted.formatted}`,
|
|
2365
|
+
resultCount: formatted.resultCount,
|
|
2366
|
+
fileCount: formatted.fileCount
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
} catch {
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
return {
|
|
2373
|
+
formatted: `Call hierarchy fallback: LSP unavailable for ${ext}. Returning 0 items.`,
|
|
2374
|
+
resultCount: 0,
|
|
2375
|
+
fileCount: 0
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
if (fallbackOperation === "incomingCalls" || fallbackOperation === "outgoingCalls") {
|
|
2379
|
+
return {
|
|
2380
|
+
formatted: `Call hierarchy fallback: LSP unavailable for ${ext}. Returning 0 items.`,
|
|
2381
|
+
resultCount: 0,
|
|
2382
|
+
fileCount: 0
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
1614
2387
|
return {
|
|
1615
2388
|
formatted: `No LSP server available for file type: ${ext}`,
|
|
1616
2389
|
resultCount: 0,
|
|
@@ -1623,7 +2396,7 @@ var LspAPI = {
|
|
|
1623
2396
|
async touchFile(filePath, options = {}) {
|
|
1624
2397
|
const client = await LspClientManager.getInstance().getClientForFile(filePath);
|
|
1625
2398
|
if (!client) return false;
|
|
1626
|
-
const content = await
|
|
2399
|
+
const content = await readFile4(filePath, "utf-8");
|
|
1627
2400
|
const languageId = getLanguageId(extname2(filePath));
|
|
1628
2401
|
await client.didOpen(filePath, content, languageId);
|
|
1629
2402
|
if (options.wait) {
|
|
@@ -1687,12 +2460,132 @@ var LspFacade = {
|
|
|
1687
2460
|
async run(input) {
|
|
1688
2461
|
const rootPath = input.rootPath ?? getCwd();
|
|
1689
2462
|
if (input.operation === "documentSymbol") {
|
|
1690
|
-
const
|
|
1691
|
-
|
|
2463
|
+
const flag = String(process.env.PYB_LSP_STRICT_FIRST ?? "").trim().toLowerCase();
|
|
2464
|
+
const lspFirst = flag === "1" || flag === "true" || flag === "on" || flag === "yes";
|
|
2465
|
+
if (lspFirst) {
|
|
1692
2466
|
try {
|
|
1693
|
-
const
|
|
1694
|
-
|
|
2467
|
+
const lspSymbols = await LspAPI.documentSymbolRaw(input.filePath, rootPath);
|
|
2468
|
+
if (lspSymbols && Array.isArray(lspSymbols) && lspSymbols.length > 0) {
|
|
2469
|
+
return formatGenericDocumentSymbolResult(lspSymbols);
|
|
2470
|
+
}
|
|
2471
|
+
const now = Date.now();
|
|
2472
|
+
const lang = ParserRegistry.getLanguage(input.filePath);
|
|
2473
|
+
if (!lang) {
|
|
2474
|
+
fastPathLastFallbackReason.set(input.filePath, "unsupported");
|
|
2475
|
+
} else {
|
|
2476
|
+
let contentHash = null;
|
|
2477
|
+
try {
|
|
2478
|
+
const content = await readFile4(input.filePath, "utf-8");
|
|
2479
|
+
contentHash = computeContentHash(content);
|
|
2480
|
+
const cached = getCachedFastPathSymbols(input.filePath, contentHash, now);
|
|
2481
|
+
if (cached) {
|
|
2482
|
+
clearFastPathFailures(input.filePath);
|
|
2483
|
+
return formatTreeSitterDocumentSymbolResult(cached);
|
|
2484
|
+
}
|
|
2485
|
+
} catch {
|
|
2486
|
+
}
|
|
2487
|
+
try {
|
|
2488
|
+
const symbols = await withTimeout(
|
|
2489
|
+
TreeSitterLspAdapter.getDocumentSymbols(input.filePath),
|
|
2490
|
+
FAST_PATH_TIMEOUT_MS
|
|
2491
|
+
);
|
|
2492
|
+
if (symbols.length > 0) {
|
|
2493
|
+
clearFastPathFailures(input.filePath);
|
|
2494
|
+
if (contentHash) {
|
|
2495
|
+
setCachedFastPathSymbols(input.filePath, contentHash, symbols, now);
|
|
2496
|
+
}
|
|
2497
|
+
return formatTreeSitterDocumentSymbolResult(symbols);
|
|
2498
|
+
}
|
|
2499
|
+
recordFastPathFailure(input.filePath, "empty", now);
|
|
2500
|
+
fastPathLastFallbackReason.set(input.filePath, "empty");
|
|
2501
|
+
} catch (error) {
|
|
2502
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2503
|
+
const reason = message.toLowerCase().includes("timeout") ? "timeout" : "exception";
|
|
2504
|
+
recordFastPathFailure(input.filePath, reason, now);
|
|
2505
|
+
fastPathLastFallbackReason.set(input.filePath, reason);
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
1695
2508
|
} catch {
|
|
2509
|
+
const now = Date.now();
|
|
2510
|
+
const lang = ParserRegistry.getLanguage(input.filePath);
|
|
2511
|
+
if (!lang) {
|
|
2512
|
+
fastPathLastFallbackReason.set(input.filePath, "unsupported");
|
|
2513
|
+
} else {
|
|
2514
|
+
let contentHash = null;
|
|
2515
|
+
try {
|
|
2516
|
+
const content = await readFile4(input.filePath, "utf-8");
|
|
2517
|
+
contentHash = computeContentHash(content);
|
|
2518
|
+
const cached = getCachedFastPathSymbols(input.filePath, contentHash, now);
|
|
2519
|
+
if (cached) {
|
|
2520
|
+
clearFastPathFailures(input.filePath);
|
|
2521
|
+
return formatTreeSitterDocumentSymbolResult(cached);
|
|
2522
|
+
}
|
|
2523
|
+
} catch {
|
|
2524
|
+
}
|
|
2525
|
+
try {
|
|
2526
|
+
const symbols = await withTimeout(
|
|
2527
|
+
TreeSitterLspAdapter.getDocumentSymbols(input.filePath),
|
|
2528
|
+
FAST_PATH_TIMEOUT_MS
|
|
2529
|
+
);
|
|
2530
|
+
if (symbols.length > 0) {
|
|
2531
|
+
clearFastPathFailures(input.filePath);
|
|
2532
|
+
if (contentHash) {
|
|
2533
|
+
setCachedFastPathSymbols(input.filePath, contentHash, symbols, now);
|
|
2534
|
+
}
|
|
2535
|
+
return formatTreeSitterDocumentSymbolResult(symbols);
|
|
2536
|
+
}
|
|
2537
|
+
recordFastPathFailure(input.filePath, "empty", now);
|
|
2538
|
+
fastPathLastFallbackReason.set(input.filePath, "empty");
|
|
2539
|
+
} catch (error) {
|
|
2540
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2541
|
+
const reason = message.toLowerCase().includes("timeout") ? "timeout" : "exception";
|
|
2542
|
+
recordFastPathFailure(input.filePath, reason, now);
|
|
2543
|
+
fastPathLastFallbackReason.set(input.filePath, reason);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
} else {
|
|
2548
|
+
const lang = ParserRegistry.getLanguage(input.filePath);
|
|
2549
|
+
const now = Date.now();
|
|
2550
|
+
if (!lang) {
|
|
2551
|
+
fastPathLastFallbackReason.set(input.filePath, "unsupported");
|
|
2552
|
+
} else {
|
|
2553
|
+
const skipReason = shouldSkipFastPath(input.filePath, now);
|
|
2554
|
+
if (skipReason) {
|
|
2555
|
+
fastPathLastFallbackReason.set(input.filePath, skipReason);
|
|
2556
|
+
} else {
|
|
2557
|
+
let contentHash = null;
|
|
2558
|
+
try {
|
|
2559
|
+
const content = await readFile4(input.filePath, "utf-8");
|
|
2560
|
+
contentHash = computeContentHash(content);
|
|
2561
|
+
const cached = getCachedFastPathSymbols(input.filePath, contentHash, now);
|
|
2562
|
+
if (cached) {
|
|
2563
|
+
clearFastPathFailures(input.filePath);
|
|
2564
|
+
return formatTreeSitterDocumentSymbolResult(cached);
|
|
2565
|
+
}
|
|
2566
|
+
} catch {
|
|
2567
|
+
}
|
|
2568
|
+
try {
|
|
2569
|
+
const symbols = await withTimeout(
|
|
2570
|
+
TreeSitterLspAdapter.getDocumentSymbols(input.filePath),
|
|
2571
|
+
FAST_PATH_TIMEOUT_MS
|
|
2572
|
+
);
|
|
2573
|
+
if (symbols.length > 0) {
|
|
2574
|
+
clearFastPathFailures(input.filePath);
|
|
2575
|
+
if (contentHash) {
|
|
2576
|
+
setCachedFastPathSymbols(input.filePath, contentHash, symbols, now);
|
|
2577
|
+
}
|
|
2578
|
+
return formatTreeSitterDocumentSymbolResult(symbols);
|
|
2579
|
+
}
|
|
2580
|
+
recordFastPathFailure(input.filePath, "empty", now);
|
|
2581
|
+
fastPathLastFallbackReason.set(input.filePath, "empty");
|
|
2582
|
+
} catch (error) {
|
|
2583
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2584
|
+
const reason = message.toLowerCase().includes("timeout") ? "timeout" : "exception";
|
|
2585
|
+
recordFastPathFailure(input.filePath, reason, now);
|
|
2586
|
+
fastPathLastFallbackReason.set(input.filePath, reason);
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
1696
2589
|
}
|
|
1697
2590
|
}
|
|
1698
2591
|
}
|
|
@@ -1713,7 +2606,13 @@ var LspFacade = {
|
|
|
1713
2606
|
};
|
|
1714
2607
|
}
|
|
1715
2608
|
try {
|
|
1716
|
-
const
|
|
2609
|
+
const strategy = resolveScopeStrategy();
|
|
2610
|
+
if (strategy === "lsp") {
|
|
2611
|
+
scopeLastFallbackReason.set(input.filePath, "lsp-unavailable");
|
|
2612
|
+
}
|
|
2613
|
+
const effectiveStrategy = strategy === "lsp" ? "tree-sitter" : strategy;
|
|
2614
|
+
scopeStrategyUsed.set(input.filePath, effectiveStrategy);
|
|
2615
|
+
const code = await readFile4(input.filePath, "utf-8");
|
|
1717
2616
|
const scope = await ScopeAnalyzer.getScope(input.filePath, code, {
|
|
1718
2617
|
row: input.line - 1,
|
|
1719
2618
|
column: input.character - 1
|
|
@@ -1820,9 +2719,50 @@ var LspFacade = {
|
|
|
1820
2719
|
return serveStatusWithProvider(() => LspFacade.status(), options);
|
|
1821
2720
|
},
|
|
1822
2721
|
status() {
|
|
1823
|
-
|
|
2722
|
+
const base = LspAPI.status();
|
|
2723
|
+
return {
|
|
2724
|
+
...base,
|
|
2725
|
+
installNotices: getInstallNotices(),
|
|
2726
|
+
fallbacks: {
|
|
2727
|
+
documentSymbol: getDocumentSymbolFallbackSnapshot(),
|
|
2728
|
+
getScope: getScopeFallbackSnapshot(),
|
|
2729
|
+
diagnostics: getOperationFallbackSnapshot("diagnostics"),
|
|
2730
|
+
findReferences: getOperationFallbackSnapshot("findReferences"),
|
|
2731
|
+
prepareCallHierarchy: getOperationFallbackSnapshot("prepareCallHierarchy"),
|
|
2732
|
+
incomingCalls: getOperationFallbackSnapshot("incomingCalls"),
|
|
2733
|
+
outgoingCalls: getOperationFallbackSnapshot("outgoingCalls"),
|
|
2734
|
+
boundary: Array.from(LSP_FALLBACK_BOUNDARY.values())
|
|
2735
|
+
}
|
|
2736
|
+
};
|
|
1824
2737
|
}
|
|
1825
2738
|
};
|
|
2739
|
+
function __resetLspFastPathStateForTests() {
|
|
2740
|
+
fastPathFailures.clear();
|
|
2741
|
+
fastPathLastFallbackReason.clear();
|
|
2742
|
+
fastPathSymbolCache.clear();
|
|
2743
|
+
}
|
|
2744
|
+
function __resetLspOperationFallbackForTests() {
|
|
2745
|
+
operationFallbackReasons.clear();
|
|
2746
|
+
}
|
|
2747
|
+
function __getLspFastPathFallbackReasonForTests(filePath) {
|
|
2748
|
+
return fastPathLastFallbackReason.get(filePath) ?? null;
|
|
2749
|
+
}
|
|
2750
|
+
function __resetScopeStateForTests() {
|
|
2751
|
+
scopeLastFallbackReason.clear();
|
|
2752
|
+
scopeStrategyUsed.clear();
|
|
2753
|
+
}
|
|
2754
|
+
function __getScopeFallbackReasonForTests(filePath) {
|
|
2755
|
+
return scopeLastFallbackReason.get(filePath) ?? null;
|
|
2756
|
+
}
|
|
2757
|
+
function __getScopeStrategyForTests(filePath) {
|
|
2758
|
+
return scopeStrategyUsed.get(filePath) ?? null;
|
|
2759
|
+
}
|
|
2760
|
+
function __getFallbackBoundaryForTests() {
|
|
2761
|
+
return Array.from(LSP_FALLBACK_BOUNDARY.values());
|
|
2762
|
+
}
|
|
2763
|
+
function __refreshLspFallbackBoundaryForTests() {
|
|
2764
|
+
refreshFallbackBoundaryFromEnv();
|
|
2765
|
+
}
|
|
1826
2766
|
|
|
1827
2767
|
export {
|
|
1828
2768
|
formatDiagnosticsPretty,
|
|
@@ -1830,5 +2770,13 @@ export {
|
|
|
1830
2770
|
loadLanguage,
|
|
1831
2771
|
initParser,
|
|
1832
2772
|
LspAPI,
|
|
1833
|
-
LspFacade
|
|
2773
|
+
LspFacade,
|
|
2774
|
+
__resetLspFastPathStateForTests,
|
|
2775
|
+
__resetLspOperationFallbackForTests,
|
|
2776
|
+
__getLspFastPathFallbackReasonForTests,
|
|
2777
|
+
__resetScopeStateForTests,
|
|
2778
|
+
__getScopeFallbackReasonForTests,
|
|
2779
|
+
__getScopeStrategyForTests,
|
|
2780
|
+
__getFallbackBoundaryForTests,
|
|
2781
|
+
__refreshLspFallbackBoundaryForTests
|
|
1834
2782
|
};
|