@askdkc/dsh-lsp-server 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/docs/usage.md +2 -0
- package/lib/code-intelligence-DvhxPnpu.js +890 -0
- package/lib/code-intelligence-DvhxPnpu.js.map +1 -0
- package/lib/code-intelligence-contracts-CWXTofdI.js +8 -0
- package/lib/code-intelligence-contracts-CWXTofdI.js.map +1 -0
- package/lib/code-intelligence-contracts.js +3 -0
- package/lib/code-intelligence.js +5 -0
- package/lib/errors-DtyWAUi0.js.map +1 -1
- package/lib/extra-tool.js +136 -8
- package/lib/extra-tool.js.map +1 -1
- package/lib/launch-ByUvgkVv.js.map +1 -1
- package/lib/provider.js +653 -40
- package/lib/provider.js.map +1 -1
- package/lib/structure/grammars/manifest.json +40 -0
- package/lib/structure/grammars/tree-sitter-javascript.wasm +0 -0
- package/lib/structure/grammars/tree-sitter-tsx.wasm +0 -0
- package/lib/structure/grammars/tree-sitter-typescript.wasm +0 -0
- package/lib/structure/parser-worker.mjs +200 -0
- package/lib/types/code-intelligence-contracts.d.ts +113 -0
- package/lib/types/code-intelligence.d.ts +5 -0
- package/lib/types/errors.d.ts +1 -1
- package/lib/types/extra-tool.d.ts +2 -1
- package/lib/types/provider.d.ts +4 -0
- package/lib/types/routing/classifier.d.ts +2 -0
- package/lib/types/runtime/webstack-runtime.d.ts +13 -1
- package/lib/types/service.d.ts +23 -0
- package/lib/types/structure/code-intelligence.d.ts +23 -0
- package/lib/types/structure/coordinates.d.ts +14 -0
- package/lib/types/structure/engine.d.ts +49 -0
- package/lib/types/structure/protocol.d.ts +19 -0
- package/lib/types/structure/queries.d.ts +4 -0
- package/lib/types/structure/snapshots.d.ts +37 -0
- package/lib/types/structure/types.d.ts +147 -0
- package/lib/types/structure/worker-pool.d.ts +66 -0
- package/package.json +17 -4
package/lib/provider.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { WebstackLspError } from "./errors-DtyWAUi0.js";
|
|
2
2
|
import { SERVER_IDS, createWebstackLspService, initializationOptions, resolveConfig, resolveLaunch, serverConfiguration } from "./launch-ByUvgkVv.js";
|
|
3
3
|
import { mergeCompletions, mergeDiagnostics } from "./completion-B7NPVCEN.js";
|
|
4
|
+
import { DocumentSnapshotStore, createCodeIntelligenceToolServiceV1 } from "./code-intelligence-DvhxPnpu.js";
|
|
4
5
|
import { LspProviderId } from "@deepseek-ai/dsh-lsp";
|
|
6
|
+
import { isAbsolute } from "node:path";
|
|
5
7
|
import { TextDecoder } from "node:util";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { symbols } from "@deepseek-ai/cordis";
|
|
6
10
|
|
|
7
11
|
//#region src/runtime/cancellation.ts
|
|
8
12
|
function abortError() {
|
|
@@ -25,11 +29,11 @@ function onAbort(signal, callback) {
|
|
|
25
29
|
async function withAbort(promise, signal) {
|
|
26
30
|
throwIfAborted(signal);
|
|
27
31
|
if (!signal) return promise;
|
|
28
|
-
return new Promise((resolve, reject) => {
|
|
32
|
+
return new Promise((resolve$1, reject) => {
|
|
29
33
|
const remove = onAbort(signal, () => reject(abortError()));
|
|
30
34
|
promise.then((value) => {
|
|
31
35
|
remove();
|
|
32
|
-
resolve(value);
|
|
36
|
+
resolve$1(value);
|
|
33
37
|
}, (error) => {
|
|
34
38
|
remove();
|
|
35
39
|
reject(error);
|
|
@@ -143,21 +147,25 @@ const CLASSIFIERS = [
|
|
|
143
147
|
];
|
|
144
148
|
function classifyFile(filePath, bladeSuffixes = [".blade.php"]) {
|
|
145
149
|
const normalizedPath = filePath.replaceAll("\\", "/");
|
|
146
|
-
const basename = normalizedPath.slice(normalizedPath.lastIndexOf("/") + 1).toLowerCase();
|
|
147
|
-
if (!basename || basename === "." || basename === ".." || basename.startsWith(".")) return void 0;
|
|
150
|
+
const basename$1 = normalizedPath.slice(normalizedPath.lastIndexOf("/") + 1).toLowerCase();
|
|
151
|
+
if (!basename$1 || basename$1 === "." || basename$1 === ".." || basename$1.startsWith(".")) return void 0;
|
|
148
152
|
const customBlade = bladeSuffixes.map((suffix) => suffix.toLowerCase()).sort((left, right) => right.length - left.length);
|
|
149
153
|
const match = [...customBlade.map((suffix) => ({
|
|
150
154
|
suffix,
|
|
151
155
|
kind: "blade"
|
|
152
|
-
})), ...CLASSIFIERS.filter((item) => !customBlade.includes(item.suffix))].find((item) => basename.endsWith(item.suffix));
|
|
156
|
+
})), ...CLASSIFIERS.filter((item) => !customBlade.includes(item.suffix))].find((item) => basename$1.endsWith(item.suffix));
|
|
153
157
|
if (!match) return void 0;
|
|
154
158
|
return {
|
|
155
159
|
path: normalizedPath,
|
|
156
|
-
basename,
|
|
160
|
+
basename: basename$1,
|
|
157
161
|
suffix: match.suffix,
|
|
158
162
|
kind: match.kind
|
|
159
163
|
};
|
|
160
164
|
}
|
|
165
|
+
/** Return the supported suffixes, including configured Blade suffixes. */
|
|
166
|
+
function supportedFileSuffixes(bladeSuffixes = [".blade.php"]) {
|
|
167
|
+
return [...new Set([...bladeSuffixes.map((suffix) => suffix.toLowerCase()), ...CLASSIFIERS.map((item) => item.suffix)])];
|
|
168
|
+
}
|
|
161
169
|
|
|
162
170
|
//#endregion
|
|
163
171
|
//#region src/routing/route-table.ts
|
|
@@ -397,8 +405,8 @@ var JsonRpcConnection = class {
|
|
|
397
405
|
throwIfAborted(signal);
|
|
398
406
|
if (this.closed) throw new Error("connection is closed");
|
|
399
407
|
const id = this.nextId++;
|
|
400
|
-
const promise = new Promise((resolve, reject) => this.pending.set(id, {
|
|
401
|
-
resolve,
|
|
408
|
+
const promise = new Promise((resolve$1, reject) => this.pending.set(id, {
|
|
409
|
+
resolve: resolve$1,
|
|
402
410
|
reject,
|
|
403
411
|
method
|
|
404
412
|
}));
|
|
@@ -447,7 +455,7 @@ var JsonRpcConnection = class {
|
|
|
447
455
|
for (const message of messages) this.dispatch(message);
|
|
448
456
|
}
|
|
449
457
|
dispatch(value) {
|
|
450
|
-
if (!isRecord$
|
|
458
|
+
if (!isRecord$5(value) || value.jsonrpc !== "2.0") return this.close(new JsonRpcFrameError("invalid JSON-RPC message"));
|
|
451
459
|
if ("method" in value && typeof value.method === "string") {
|
|
452
460
|
if ("id" in value) {
|
|
453
461
|
const request = value;
|
|
@@ -476,12 +484,12 @@ var JsonRpcConnection = class {
|
|
|
476
484
|
}
|
|
477
485
|
}
|
|
478
486
|
write(message) {
|
|
479
|
-
return new Promise((resolve, reject) => {
|
|
480
|
-
this.output.write(encodeJsonRpc(message), (error) => error ? reject(error) : resolve());
|
|
487
|
+
return new Promise((resolve$1, reject) => {
|
|
488
|
+
this.output.write(encodeJsonRpc(message), (error) => error ? reject(error) : resolve$1());
|
|
481
489
|
});
|
|
482
490
|
}
|
|
483
491
|
};
|
|
484
|
-
function isRecord$
|
|
492
|
+
function isRecord$5(value) {
|
|
485
493
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
486
494
|
}
|
|
487
495
|
|
|
@@ -646,8 +654,8 @@ var ServerPool = class {
|
|
|
646
654
|
async serialize(key, task, signal) {
|
|
647
655
|
const previous = this.queues.get(key) ?? Promise.resolve();
|
|
648
656
|
let release;
|
|
649
|
-
const current = new Promise((resolve) => {
|
|
650
|
-
release = resolve;
|
|
657
|
+
const current = new Promise((resolve$1) => {
|
|
658
|
+
release = resolve$1;
|
|
651
659
|
});
|
|
652
660
|
this.queues.set(key, current);
|
|
653
661
|
try {
|
|
@@ -714,29 +722,29 @@ function normalizeHover(value) {
|
|
|
714
722
|
function normalizeContents(value) {
|
|
715
723
|
if (typeof value === "string") return value;
|
|
716
724
|
if (Array.isArray(value)) {
|
|
717
|
-
const parts = value.map((item) => typeof item === "string" ? item : isRecord$
|
|
725
|
+
const parts = value.map((item) => typeof item === "string" ? item : isRecord$4(item) && typeof item.value === "string" ? item.value : void 0);
|
|
718
726
|
return parts.every((item) => item !== void 0) ? parts.join("\n") : void 0;
|
|
719
727
|
}
|
|
720
|
-
if (isRecord$
|
|
728
|
+
if (isRecord$4(value) && typeof value.value === "string") return value.value;
|
|
721
729
|
}
|
|
722
730
|
function isRange$2(value) {
|
|
723
|
-
return isRecord$
|
|
731
|
+
return isRecord$4(value) && isPosition$2(value.start) && isPosition$2(value.end);
|
|
724
732
|
}
|
|
725
733
|
function isPosition$2(value) {
|
|
726
|
-
return isRecord$
|
|
734
|
+
return isRecord$4(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
|
|
727
735
|
}
|
|
728
|
-
function isRecord$
|
|
736
|
+
function isRecord$4(value) {
|
|
729
737
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
730
738
|
}
|
|
731
739
|
|
|
732
740
|
//#endregion
|
|
733
741
|
//#region src/protocol/normalize-location.ts
|
|
734
742
|
function normalizeLocation(value) {
|
|
735
|
-
if (isRecord$
|
|
743
|
+
if (isRecord$3(value) && typeof value.targetUri === "string" && isRange$1(value.targetSelectionRange)) return {
|
|
736
744
|
uri: value.targetUri,
|
|
737
745
|
range: value.targetSelectionRange
|
|
738
746
|
};
|
|
739
|
-
if (!isRecord$
|
|
747
|
+
if (!isRecord$3(value) || typeof value.uri !== "string" || !isRange$1(value.range)) return void 0;
|
|
740
748
|
return {
|
|
741
749
|
uri: value.uri,
|
|
742
750
|
range: value.range
|
|
@@ -746,19 +754,19 @@ function normalizeLocations(value) {
|
|
|
746
754
|
return (Array.isArray(value) ? value : value === null || value === void 0 ? [] : [value]).map(normalizeLocation).filter((location) => location !== void 0);
|
|
747
755
|
}
|
|
748
756
|
function isRange$1(value) {
|
|
749
|
-
return isRecord$
|
|
757
|
+
return isRecord$3(value) && isPosition$1(value.start) && isPosition$1(value.end);
|
|
750
758
|
}
|
|
751
759
|
function isPosition$1(value) {
|
|
752
|
-
return isRecord$
|
|
760
|
+
return isRecord$3(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
|
|
753
761
|
}
|
|
754
|
-
function isRecord$
|
|
762
|
+
function isRecord$3(value) {
|
|
755
763
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
756
764
|
}
|
|
757
765
|
|
|
758
766
|
//#endregion
|
|
759
767
|
//#region src/protocol/normalize-diagnostic.ts
|
|
760
768
|
function normalizeDiagnostic(value, server, fallbackUri) {
|
|
761
|
-
if (!isRecord$
|
|
769
|
+
if (!isRecord$2(value) || !isRange(value.range) || typeof value.message !== "string") return void 0;
|
|
762
770
|
const uri = typeof value.uri === "string" ? value.uri : fallbackUri;
|
|
763
771
|
const severity = typeof value.severity === "number" && value.severity >= 1 && value.severity <= 4 ? value.severity : void 0;
|
|
764
772
|
const code = typeof value.code === "string" || typeof value.code === "number" ? value.code : void 0;
|
|
@@ -776,19 +784,19 @@ function normalizeDiagnostics(value, server, fallbackUri) {
|
|
|
776
784
|
return (Array.isArray(value) ? value : []).map((item) => normalizeDiagnostic(item, server, fallbackUri)).filter((item) => item !== void 0);
|
|
777
785
|
}
|
|
778
786
|
function isRange(value) {
|
|
779
|
-
return isRecord$
|
|
787
|
+
return isRecord$2(value) && isPosition(value.start) && isPosition(value.end);
|
|
780
788
|
}
|
|
781
789
|
function isPosition(value) {
|
|
782
|
-
return isRecord$
|
|
790
|
+
return isRecord$2(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
|
|
783
791
|
}
|
|
784
|
-
function isRecord$
|
|
792
|
+
function isRecord$2(value) {
|
|
785
793
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
786
794
|
}
|
|
787
795
|
|
|
788
796
|
//#endregion
|
|
789
797
|
//#region src/protocol/normalize-completion.ts
|
|
790
798
|
function normalizeCompletionItem(value, server) {
|
|
791
|
-
if (!isRecord(value) || typeof value.label !== "string" || value.label.length === 0) return void 0;
|
|
799
|
+
if (!isRecord$1(value) || typeof value.label !== "string" || value.label.length === 0) return void 0;
|
|
792
800
|
return {
|
|
793
801
|
server,
|
|
794
802
|
label: value.label,
|
|
@@ -803,9 +811,9 @@ function normalizeCompletionItem(value, server) {
|
|
|
803
811
|
};
|
|
804
812
|
}
|
|
805
813
|
function normalizeCompletion(value, server) {
|
|
806
|
-
return (isRecord(value) && Array.isArray(value.items) ? value.items : Array.isArray(value) ? value : []).map((item) => normalizeCompletionItem(item, server)).filter((item) => item !== void 0);
|
|
814
|
+
return (isRecord$1(value) && Array.isArray(value.items) ? value.items : Array.isArray(value) ? value : []).map((item) => normalizeCompletionItem(item, server)).filter((item) => item !== void 0);
|
|
807
815
|
}
|
|
808
|
-
function isRecord(value) {
|
|
816
|
+
function isRecord$1(value) {
|
|
809
817
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
810
818
|
}
|
|
811
819
|
|
|
@@ -827,6 +835,451 @@ function sameRange(left, right) {
|
|
|
827
835
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
828
836
|
}
|
|
829
837
|
|
|
838
|
+
//#endregion
|
|
839
|
+
//#region src/structure/protocol.ts
|
|
840
|
+
const CAPABILITIES = [
|
|
841
|
+
"snapshots",
|
|
842
|
+
"structure",
|
|
843
|
+
"semantic"
|
|
844
|
+
];
|
|
845
|
+
const MAX_FILES = 100;
|
|
846
|
+
const MAX_INPUT_BYTES = 16 * 1024 * 1024;
|
|
847
|
+
const MAX_OUTPUT_BYTES = 64 * 1024;
|
|
848
|
+
const MAX_CALLS = 100;
|
|
849
|
+
const MAX_BATCH_MS = 3e4;
|
|
850
|
+
const RESPONSE_BYTES = 32 * 1024;
|
|
851
|
+
/** Host-bound V1 facade. Each lease owns its handles and cumulative evaluation budget. */
|
|
852
|
+
var CodeIntelligenceProtocolService = class {
|
|
853
|
+
version = 1;
|
|
854
|
+
capabilities = CAPABILITIES;
|
|
855
|
+
leases = /* @__PURE__ */ new Set();
|
|
856
|
+
closed = false;
|
|
857
|
+
constructor(options) {
|
|
858
|
+
this.options = options;
|
|
859
|
+
}
|
|
860
|
+
async bind(binding, signal) {
|
|
861
|
+
if (this.closed) throw new WebstackLspError("LSP_DISPOSED", "code-intelligence service is disposed");
|
|
862
|
+
validateBinding(binding);
|
|
863
|
+
signal.throwIfAborted();
|
|
864
|
+
await binding.assertCurrent();
|
|
865
|
+
const contextFs = binding.context.get("fs", false);
|
|
866
|
+
const contextSubprocess = binding.context.get("subprocess", false);
|
|
867
|
+
if (identity(contextFs) !== identity(this.options.fs) || identity(contextSubprocess) !== identity(this.options.subprocess)) throw new WebstackLspError("LSP_CODE_INTELLIGENCE_UNAVAILABLE", "host services do not match the provider scope");
|
|
868
|
+
const scope = {
|
|
869
|
+
ownerId: `${binding.owner.agentId}:code:${randomUUID()}`,
|
|
870
|
+
sessionId: binding.owner.sessionId,
|
|
871
|
+
workspaceRoot: binding.workspaceRoot
|
|
872
|
+
};
|
|
873
|
+
const lease = new CodeLease(binding, scope, this.options.backend, this.options.capabilities, identity(this.options.fs), identity(this.options.subprocess), () => this.leases.delete(lease));
|
|
874
|
+
this.leases.add(lease);
|
|
875
|
+
try {
|
|
876
|
+
await lease.assertCurrent(signal);
|
|
877
|
+
if (this.closed) throw new WebstackLspError("LSP_DISPOSED", "code-intelligence service is disposed");
|
|
878
|
+
return lease;
|
|
879
|
+
} catch (error) {
|
|
880
|
+
await lease.dispose();
|
|
881
|
+
throw error;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
async dispose() {
|
|
885
|
+
if (this.closed) return;
|
|
886
|
+
this.closed = true;
|
|
887
|
+
await Promise.all([...this.leases].map((lease) => lease.dispose()));
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
var CodeLease = class {
|
|
891
|
+
lifetime = new AbortController();
|
|
892
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
893
|
+
nodes = /* @__PURE__ */ new Map();
|
|
894
|
+
calls = 0;
|
|
895
|
+
files = 0;
|
|
896
|
+
inputBytes = 0;
|
|
897
|
+
outputBytes = 0;
|
|
898
|
+
started = performance.now();
|
|
899
|
+
disposed = false;
|
|
900
|
+
disposal;
|
|
901
|
+
constructor(binding, scope, backend, capabilities, fsIdentity, subprocessIdentity, onDispose) {
|
|
902
|
+
this.binding = binding;
|
|
903
|
+
this.scope = scope;
|
|
904
|
+
this.backend = backend;
|
|
905
|
+
this.capabilities = capabilities;
|
|
906
|
+
this.fsIdentity = fsIdentity;
|
|
907
|
+
this.subprocessIdentity = subprocessIdentity;
|
|
908
|
+
this.onDispose = onDispose;
|
|
909
|
+
}
|
|
910
|
+
async assertCurrent(signal) {
|
|
911
|
+
signal.throwIfAborted();
|
|
912
|
+
if (this.disposed) throw new WebstackLspError("LSP_STALE_SNAPSHOT", "code evaluation has ended");
|
|
913
|
+
await this.binding.assertCurrent();
|
|
914
|
+
if (identity(this.binding.context.get("fs", false)) !== this.fsIdentity || identity(this.binding.context.get("subprocess", false)) !== this.subprocessIdentity) throw new WebstackLspError("LSP_STALE_SNAPSHOT", "host service scope changed");
|
|
915
|
+
signal.throwIfAborted();
|
|
916
|
+
}
|
|
917
|
+
async request(input, caller) {
|
|
918
|
+
const release = input?.method === "snapshot.release";
|
|
919
|
+
if (!release) this.calls++;
|
|
920
|
+
if (this.disposed) return this.account(outcome("stale", "evaluation_closed"));
|
|
921
|
+
if (!validCodeRequest(input)) return this.account(outcome("unsupported", "invalid_request"));
|
|
922
|
+
if (caller.aborted) return this.account(outcome(caller.reason?.name === "TimeoutError" ? "timeout" : "cancelled", caller.reason?.name === "TimeoutError" ? "caller_deadline" : "caller_cancelled"));
|
|
923
|
+
if (release) return this.release(input.handle);
|
|
924
|
+
if (this.calls > MAX_CALLS || this.outputBytes >= MAX_OUTPUT_BYTES || this.files >= MAX_FILES && input.method === "snapshot.open" || this.inputBytes >= MAX_INPUT_BYTES && input.method === "snapshot.open") return this.account(outcome("limit_exceeded", "evaluation_budget"));
|
|
925
|
+
const remaining = MAX_BATCH_MS - (performance.now() - this.started);
|
|
926
|
+
if (remaining <= 0) return this.account(outcome("timeout", "batch_deadline"));
|
|
927
|
+
if ("handle" in input && !this.snapshots.has(input.handle) || "nodeHandle" in input && !this.nodes.has(input.nodeHandle)) return this.account(outcome("stale", "unknown_handle"));
|
|
928
|
+
const duration = input.method === "semantic.query" ? 1e4 : 2e3;
|
|
929
|
+
const timeout = AbortSignal.timeout(Math.max(1, Math.floor(Math.min(remaining, duration))));
|
|
930
|
+
const signal = AbortSignal.any([
|
|
931
|
+
caller,
|
|
932
|
+
this.lifetime.signal,
|
|
933
|
+
timeout
|
|
934
|
+
]);
|
|
935
|
+
try {
|
|
936
|
+
await this.assertCurrent(signal);
|
|
937
|
+
let response;
|
|
938
|
+
if (input.method === "capabilities") response = this.capabilitiesResponse(await this.capabilities(signal));
|
|
939
|
+
else if (input.method === "snapshot.open") response = await this.open(input, signal);
|
|
940
|
+
else response = await this.query(input, signal);
|
|
941
|
+
await this.assertCurrent(signal);
|
|
942
|
+
return this.account(boundResponse(response));
|
|
943
|
+
} catch (error) {
|
|
944
|
+
if (signal.aborted) return this.account(outcome(this.lifetime.signal.aborted || caller.aborted && caller.reason?.name !== "TimeoutError" ? "cancelled" : "timeout", caller.aborted ? caller.reason?.name === "TimeoutError" ? "caller_deadline" : "caller_cancelled" : "operation_deadline"));
|
|
945
|
+
if (error instanceof WebstackLspError) return this.account(mapError(error));
|
|
946
|
+
return this.account(outcome("unavailable", "provider_operation_failed"));
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
async open(input, signal) {
|
|
950
|
+
this.files++;
|
|
951
|
+
const opened = await this.backend.snapshot.open(this.scope, input.path, signal);
|
|
952
|
+
const version = `${opened.version}:${opened.contentHash}`;
|
|
953
|
+
const response = {
|
|
954
|
+
protocol: "code-intelligence/v1",
|
|
955
|
+
status: "ok",
|
|
956
|
+
sourceKind: "disk",
|
|
957
|
+
freshness: "pinned",
|
|
958
|
+
snapshotVersion: version,
|
|
959
|
+
truncated: false,
|
|
960
|
+
omitted: 0,
|
|
961
|
+
inputBytes: opened.sourceBytes,
|
|
962
|
+
data: {
|
|
963
|
+
handle: opened.handle,
|
|
964
|
+
path: opened.filePath,
|
|
965
|
+
language: opened.language ?? "unsupported",
|
|
966
|
+
parseErrors: opened.parseErrors
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
if (this.inputBytes + opened.sourceBytes > MAX_INPUT_BYTES) {
|
|
970
|
+
await this.backend.snapshot.release(this.scope, opened.handle).catch(() => {});
|
|
971
|
+
return outcome("limit_exceeded", "input_budget");
|
|
972
|
+
}
|
|
973
|
+
this.inputBytes += opened.sourceBytes;
|
|
974
|
+
this.snapshots.set(opened.handle, {
|
|
975
|
+
version,
|
|
976
|
+
path: opened.filePath
|
|
977
|
+
});
|
|
978
|
+
return response;
|
|
979
|
+
}
|
|
980
|
+
async query(input, signal) {
|
|
981
|
+
const handle = "handle" in input ? input.handle : this.nodes.get(input.nodeHandle);
|
|
982
|
+
const snapshot = this.snapshots.get(handle);
|
|
983
|
+
if (!snapshot) return outcome("stale", "unknown_handle");
|
|
984
|
+
const metadata = {
|
|
985
|
+
snapshotVersion: snapshot.version,
|
|
986
|
+
freshness: "current"
|
|
987
|
+
};
|
|
988
|
+
if (input.method === "structure.outline") {
|
|
989
|
+
const result = await this.backend.structure.outline(this.scope, input.handle, {
|
|
990
|
+
...input.range ? { range: input.range } : {},
|
|
991
|
+
...input.limit ? { limit: input.limit } : {}
|
|
992
|
+
}, signal);
|
|
993
|
+
const items = result.declarations.map(projectNode);
|
|
994
|
+
this.rememberNodes(handle, items);
|
|
995
|
+
const status = result.truncated && result.status === "ok" ? "partial" : result.status;
|
|
996
|
+
return responseFor(status, result.status === "unsupported" ? "grammar_missing" : result.errors.hasError ? "parse_errors" : void 0, metadata, { items }, result.truncated, result.truncated ? 1 : 0);
|
|
997
|
+
}
|
|
998
|
+
if (input.method === "structure.enclosing") {
|
|
999
|
+
const result = await this.backend.structure.enclosing(this.scope, input.handle, input.position, input.kinds ? { kinds: input.kinds } : void 0, signal);
|
|
1000
|
+
const items = [...result.node ? [result.node] : [], ...result.ancestors].map(projectNode);
|
|
1001
|
+
this.rememberNodes(handle, items);
|
|
1002
|
+
return responseFor(result.status, result.status === "unsupported" ? "grammar_missing" : result.errors?.hasError ? "parse_errors" : void 0, metadata, { items });
|
|
1003
|
+
}
|
|
1004
|
+
if (input.method === "structure.query") {
|
|
1005
|
+
const result = await this.backend.structure.query(this.scope, input.handle, input.queryId, {
|
|
1006
|
+
...input.range ? { range: input.range } : {},
|
|
1007
|
+
...input.limit ? { limit: input.limit } : {}
|
|
1008
|
+
}, signal);
|
|
1009
|
+
const items = result.captures.map((capture) => ({
|
|
1010
|
+
handle: capture.nodeHandle,
|
|
1011
|
+
kind: capture.kind,
|
|
1012
|
+
range: capture.range,
|
|
1013
|
+
...capture.name ? { name: capture.name } : {}
|
|
1014
|
+
}));
|
|
1015
|
+
this.rememberNodes(handle, items);
|
|
1016
|
+
return responseFor(result.truncated && result.status === "ok" ? "partial" : result.status, void 0, metadata, {
|
|
1017
|
+
items,
|
|
1018
|
+
queryId: result.queryId
|
|
1019
|
+
}, result.truncated, result.truncated ? 1 : 0);
|
|
1020
|
+
}
|
|
1021
|
+
if (input.method === "structure.span") {
|
|
1022
|
+
const result = await this.backend.structure.span(this.scope, input.nodeHandle, input.maxChars, signal);
|
|
1023
|
+
return responseFor(result.status, void 0, metadata, {
|
|
1024
|
+
text: result.text,
|
|
1025
|
+
range: result.range
|
|
1026
|
+
}, result.truncated, result.truncated ? 1 : 0);
|
|
1027
|
+
}
|
|
1028
|
+
return this.semantic(input, metadata, signal);
|
|
1029
|
+
}
|
|
1030
|
+
async semantic(input, metadata, signal) {
|
|
1031
|
+
const result = await this.backend.semantic.query(this.scope, input.handle, {
|
|
1032
|
+
operation: input.kind,
|
|
1033
|
+
...input.position ? { position: input.position } : {},
|
|
1034
|
+
...input.limit ? { maxResults: input.limit } : {}
|
|
1035
|
+
}, signal);
|
|
1036
|
+
if (result.status === "unsupported" || result.status === "unavailable" || result.status === "stale" || result.status === "timeout" || result.status === "cancelled" || result.status === "limit_exceeded") return responseFor(result.status, `semantic_${result.status}`, metadata);
|
|
1037
|
+
const value = result.result;
|
|
1038
|
+
if (input.kind === "hover") {
|
|
1039
|
+
if (!isRecord(value) || value.kind !== "hover") return outcome("unavailable", "semantic_result_invalid");
|
|
1040
|
+
const hover = value.hover;
|
|
1041
|
+
if (hover === null) return responseFor(result.status, void 0, metadata, { hover: null });
|
|
1042
|
+
if (!isRecord(hover) || typeof hover.contents !== "string") return outcome("unavailable", "semantic_result_invalid");
|
|
1043
|
+
const contents = trimUtf16(hover.contents, 16e3);
|
|
1044
|
+
const truncated = contents !== hover.contents;
|
|
1045
|
+
return responseFor(result.status, void 0, metadata, { hover: {
|
|
1046
|
+
contents,
|
|
1047
|
+
...hover.range ? { range: hover.range } : {}
|
|
1048
|
+
} }, truncated, truncated ? hover.contents.length - contents.length : 0);
|
|
1049
|
+
}
|
|
1050
|
+
if (input.kind === "completion") {
|
|
1051
|
+
if (!isRecord(value) || !Array.isArray(value.items)) return outcome("unavailable", "semantic_result_invalid");
|
|
1052
|
+
let truncated = Boolean(result.truncated);
|
|
1053
|
+
const items = value.items.map((item) => ({
|
|
1054
|
+
label: typeof item.label === "string" ? item.label.slice(0, 1e3) : "",
|
|
1055
|
+
...typeof item.detail === "string" ? { detail: item.detail.slice(0, 4e3) } : {},
|
|
1056
|
+
...typeof item.documentation === "string" ? { documentation: item.documentation.slice(0, 8e3) } : {},
|
|
1057
|
+
...Number.isInteger(item.kind) ? { kind: item.kind } : {},
|
|
1058
|
+
...typeof item.insertText === "string" ? { insertText: item.insertText.slice(0, 8e3) } : {}
|
|
1059
|
+
}));
|
|
1060
|
+
if (items.some((item, index) => item.label !== value.items[index]?.label || item.detail !== value.items[index]?.detail || item.documentation !== value.items[index]?.documentation || item.insertText !== value.items[index]?.insertText)) truncated = true;
|
|
1061
|
+
const limit = input.limit ?? 200;
|
|
1062
|
+
const omitted = (result.omitted ?? 0) + Math.max(0, items.length - limit);
|
|
1063
|
+
return responseFor(truncated || omitted ? "partial" : result.status, void 0, metadata, { items: items.slice(0, limit) }, truncated || omitted > 0, omitted);
|
|
1064
|
+
}
|
|
1065
|
+
if (input.kind === "diagnostics") {
|
|
1066
|
+
if (!isRecord(value) || !Array.isArray(value.diagnostics)) return outcome("unavailable", "semantic_result_invalid");
|
|
1067
|
+
if (value.skipped) return responseFor("unsupported", "unsupported_file_type", metadata);
|
|
1068
|
+
let truncated = Boolean(result.truncated);
|
|
1069
|
+
const items = value.diagnostics.map((item) => {
|
|
1070
|
+
const message = typeof item.message === "string" ? item.message.slice(0, 8e3) : "";
|
|
1071
|
+
if (message !== item.message) truncated = true;
|
|
1072
|
+
return {
|
|
1073
|
+
uri: item.uri,
|
|
1074
|
+
range: item.range,
|
|
1075
|
+
message,
|
|
1076
|
+
...Number.isInteger(item.severity) ? { severity: item.severity } : {}
|
|
1077
|
+
};
|
|
1078
|
+
});
|
|
1079
|
+
const limit = input.limit ?? 200;
|
|
1080
|
+
const omitted = (result.omitted ?? 0) + Math.max(0, items.length - limit);
|
|
1081
|
+
return responseFor(truncated || omitted || result.status === "partial" ? "partial" : result.status, void 0, metadata, { items: items.slice(0, limit) }, truncated || omitted > 0, omitted);
|
|
1082
|
+
}
|
|
1083
|
+
if (!isRecord(value) || value.kind !== "locations" || !Array.isArray(value.locations)) return outcome("unavailable", "semantic_result_invalid");
|
|
1084
|
+
return responseFor(result.status, void 0, metadata, { items: value.locations }, Boolean(result.truncated), result.omitted ?? 0);
|
|
1085
|
+
}
|
|
1086
|
+
async release(handle) {
|
|
1087
|
+
const known = this.snapshots.delete(handle);
|
|
1088
|
+
for (const [node, snapshot] of this.nodes) if (snapshot === handle) this.nodes.delete(node);
|
|
1089
|
+
if (!known) return responseFor("ok", "released", {
|
|
1090
|
+
sourceKind: "disk",
|
|
1091
|
+
freshness: "unknown"
|
|
1092
|
+
}, { released: false });
|
|
1093
|
+
const result = await this.backend.snapshot.release(this.scope, handle);
|
|
1094
|
+
return responseFor("ok", "released", {
|
|
1095
|
+
sourceKind: "disk",
|
|
1096
|
+
freshness: "unknown"
|
|
1097
|
+
}, result);
|
|
1098
|
+
}
|
|
1099
|
+
capabilitiesResponse(capabilities) {
|
|
1100
|
+
return responseFor("ok", "capabilities", {
|
|
1101
|
+
sourceKind: "disk",
|
|
1102
|
+
freshness: "unknown"
|
|
1103
|
+
}, capabilities);
|
|
1104
|
+
}
|
|
1105
|
+
rememberNodes(snapshotHandle, items) {
|
|
1106
|
+
for (const item of items) this.nodes.set(item.handle, snapshotHandle);
|
|
1107
|
+
}
|
|
1108
|
+
account(response) {
|
|
1109
|
+
if (Buffer.byteLength(JSON.stringify(response)) > RESPONSE_BYTES) response = outcome("limit_exceeded", "response_limit");
|
|
1110
|
+
this.outputBytes += Buffer.byteLength(JSON.stringify(response));
|
|
1111
|
+
if (this.outputBytes > MAX_OUTPUT_BYTES) return outcome("limit_exceeded", "response_budget");
|
|
1112
|
+
return response;
|
|
1113
|
+
}
|
|
1114
|
+
dispose() {
|
|
1115
|
+
if (this.disposal) return this.disposal;
|
|
1116
|
+
this.disposed = true;
|
|
1117
|
+
this.lifetime.abort();
|
|
1118
|
+
this.snapshots.clear();
|
|
1119
|
+
this.nodes.clear();
|
|
1120
|
+
this.onDispose();
|
|
1121
|
+
this.disposal = Promise.resolve(this.backend.releaseOwner?.(this.scope)).then(() => void 0, () => void 0);
|
|
1122
|
+
return this.disposal;
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
function outcome(status, reason) {
|
|
1126
|
+
return {
|
|
1127
|
+
protocol: "code-intelligence/v1",
|
|
1128
|
+
status,
|
|
1129
|
+
reason,
|
|
1130
|
+
sourceKind: "disk",
|
|
1131
|
+
freshness: status === "stale" ? "stale" : "unknown",
|
|
1132
|
+
truncated: false,
|
|
1133
|
+
omitted: 0
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
function responseFor(status, reason, metadata, data, truncated = false, omitted = 0) {
|
|
1137
|
+
const successful = status === "ok" || status === "partial";
|
|
1138
|
+
return {
|
|
1139
|
+
protocol: "code-intelligence/v1",
|
|
1140
|
+
status,
|
|
1141
|
+
...reason ? { reason } : {},
|
|
1142
|
+
sourceKind: "disk",
|
|
1143
|
+
freshness: metadata.freshness,
|
|
1144
|
+
...metadata.snapshotVersion ? { snapshotVersion: metadata.snapshotVersion } : {},
|
|
1145
|
+
truncated,
|
|
1146
|
+
omitted,
|
|
1147
|
+
...successful && data !== void 0 ? { data } : {}
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
function boundResponse(response) {
|
|
1151
|
+
if (Buffer.byteLength(JSON.stringify(response), "utf8") <= RESPONSE_BYTES) return response;
|
|
1152
|
+
if (isRecord(response.data) && Array.isArray(response.data.items)) {
|
|
1153
|
+
const items = [...response.data.items];
|
|
1154
|
+
let omitted = response.omitted;
|
|
1155
|
+
while (items.length && Buffer.byteLength(JSON.stringify({
|
|
1156
|
+
...response,
|
|
1157
|
+
data: {
|
|
1158
|
+
...response.data,
|
|
1159
|
+
items
|
|
1160
|
+
}
|
|
1161
|
+
}), "utf8") > RESPONSE_BYTES) {
|
|
1162
|
+
items.pop();
|
|
1163
|
+
omitted++;
|
|
1164
|
+
}
|
|
1165
|
+
const shortened = {
|
|
1166
|
+
...response,
|
|
1167
|
+
status: "partial",
|
|
1168
|
+
reason: response.reason ?? "response_truncated",
|
|
1169
|
+
truncated: true,
|
|
1170
|
+
omitted,
|
|
1171
|
+
data: {
|
|
1172
|
+
...response.data,
|
|
1173
|
+
items
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
if (Buffer.byteLength(JSON.stringify(shortened), "utf8") <= RESPONSE_BYTES) return shortened;
|
|
1177
|
+
}
|
|
1178
|
+
return outcome("limit_exceeded", "response_limit");
|
|
1179
|
+
}
|
|
1180
|
+
function mapError(error) {
|
|
1181
|
+
if (error.code === "LSP_STALE_SNAPSHOT") return outcome("stale", "source_changed");
|
|
1182
|
+
if (error.code === "LSP_STRUCTURE_TIMEOUT") return outcome("timeout", "parser_deadline");
|
|
1183
|
+
if (error.code === "LSP_STRUCTURE_LIMIT" || error.code === "LSP_SNAPSHOT_LIMIT") return outcome("limit_exceeded", "provider_limit");
|
|
1184
|
+
if (error.code === "LSP_STRUCTURE_UNSUPPORTED" || error.code === "LSP_UNSUPPORTED_OPERATION") return outcome("unsupported", "capability_missing");
|
|
1185
|
+
if (error.code === "LSP_STRUCTURE_BUSY") return outcome("unavailable", "parser_busy");
|
|
1186
|
+
if (error.code === "LSP_CODE_INTELLIGENCE_UNAVAILABLE" || error.code === "LSP_STRUCTURE_UNAVAILABLE" || error.code === "LSP_UNAVAILABLE") return outcome("unavailable", "provider_unavailable");
|
|
1187
|
+
if (error.code === "LSP_WORKSPACE_REQUIRED") return outcome("unavailable", "invalid_path");
|
|
1188
|
+
return outcome("unavailable", "provider_operation_failed");
|
|
1189
|
+
}
|
|
1190
|
+
function projectNode(node) {
|
|
1191
|
+
return {
|
|
1192
|
+
handle: node.handle,
|
|
1193
|
+
kind: node.kind,
|
|
1194
|
+
range: node.range,
|
|
1195
|
+
...node.name ? { name: node.name } : {}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
function trimUtf16(value, limit) {
|
|
1199
|
+
if (value.length <= limit) return value;
|
|
1200
|
+
const end = value.charCodeAt(limit - 1) >= 55296 && value.charCodeAt(limit - 1) <= 56319 ? limit - 1 : limit;
|
|
1201
|
+
return value.slice(0, end);
|
|
1202
|
+
}
|
|
1203
|
+
function validCodeRequest(value) {
|
|
1204
|
+
if (!isRecord(value) || typeof value.method !== "string") return false;
|
|
1205
|
+
const allowed = {
|
|
1206
|
+
capabilities: ["method"],
|
|
1207
|
+
"snapshot.open": ["method", "path"],
|
|
1208
|
+
"snapshot.release": ["method", "handle"],
|
|
1209
|
+
"structure.outline": [
|
|
1210
|
+
"method",
|
|
1211
|
+
"handle",
|
|
1212
|
+
"range",
|
|
1213
|
+
"limit"
|
|
1214
|
+
],
|
|
1215
|
+
"structure.enclosing": [
|
|
1216
|
+
"method",
|
|
1217
|
+
"handle",
|
|
1218
|
+
"position",
|
|
1219
|
+
"kinds"
|
|
1220
|
+
],
|
|
1221
|
+
"structure.query": [
|
|
1222
|
+
"method",
|
|
1223
|
+
"handle",
|
|
1224
|
+
"queryId",
|
|
1225
|
+
"range",
|
|
1226
|
+
"limit"
|
|
1227
|
+
],
|
|
1228
|
+
"structure.span": [
|
|
1229
|
+
"method",
|
|
1230
|
+
"nodeHandle",
|
|
1231
|
+
"maxChars"
|
|
1232
|
+
],
|
|
1233
|
+
"semantic.query": [
|
|
1234
|
+
"method",
|
|
1235
|
+
"handle",
|
|
1236
|
+
"kind",
|
|
1237
|
+
"position",
|
|
1238
|
+
"limit"
|
|
1239
|
+
]
|
|
1240
|
+
}[value.method];
|
|
1241
|
+
if (!allowed || Object.keys(value).some((key) => !allowed.includes(key))) return false;
|
|
1242
|
+
const handle = "handle" in value ? value.handle : "nodeHandle" in value ? value.nodeHandle : void 0;
|
|
1243
|
+
if (handle !== void 0 && (typeof handle !== "string" || !handle.length || handle.length > 256)) return false;
|
|
1244
|
+
if (value.method === "snapshot.open") return typeof value.path === "string" && value.path.length > 0 && value.path.length <= 4096;
|
|
1245
|
+
if (value.method === "capabilities" || value.method === "snapshot.release") return true;
|
|
1246
|
+
if ("limit" in value && value.limit !== void 0 && (!Number.isSafeInteger(value.limit) || value.limit < 1 || value.limit > 200)) return false;
|
|
1247
|
+
if ("range" in value && value.range !== void 0 && !validRange(value.range)) return false;
|
|
1248
|
+
if (value.method === "structure.outline") return value.limit === void 0 || value.limit <= 100;
|
|
1249
|
+
if (value.method === "structure.enclosing") return validPosition(value.position) && (value.kinds === void 0 || Array.isArray(value.kinds) && value.kinds.length <= 30 && value.kinds.every((kind) => typeof kind === "string" && kind.length <= 100));
|
|
1250
|
+
if (value.method === "structure.query") return [
|
|
1251
|
+
"declarations",
|
|
1252
|
+
"calls",
|
|
1253
|
+
"imports"
|
|
1254
|
+
].includes(String(value.queryId));
|
|
1255
|
+
if (value.method === "structure.span") return value.maxChars === void 0 || Number.isSafeInteger(value.maxChars) && value.maxChars >= 1 && value.maxChars <= 8e3;
|
|
1256
|
+
if (value.method === "semantic.query") return [
|
|
1257
|
+
"definition",
|
|
1258
|
+
"references",
|
|
1259
|
+
"implementation",
|
|
1260
|
+
"hover",
|
|
1261
|
+
"diagnostics",
|
|
1262
|
+
"completion"
|
|
1263
|
+
].includes(String(value.kind)) && (value.kind === "diagnostics" || validPosition(value.position));
|
|
1264
|
+
return false;
|
|
1265
|
+
}
|
|
1266
|
+
function validRange(value) {
|
|
1267
|
+
if (!isRecord(value) || !validPosition(value.start) || !validPosition(value.end)) return false;
|
|
1268
|
+
return value.end.line > value.start.line || value.end.line === value.start.line && value.end.character >= value.start.character;
|
|
1269
|
+
}
|
|
1270
|
+
function validPosition(value) {
|
|
1271
|
+
return isRecord(value) && Number.isSafeInteger(value.line) && Number.isSafeInteger(value.character) && value.line >= 0 && value.character >= 0;
|
|
1272
|
+
}
|
|
1273
|
+
function isRecord(value) {
|
|
1274
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1275
|
+
}
|
|
1276
|
+
function identity(value) {
|
|
1277
|
+
return value && typeof value === "object" ? value[symbols.original] ?? value : value;
|
|
1278
|
+
}
|
|
1279
|
+
function validateBinding(binding) {
|
|
1280
|
+
if (!binding || !binding.owner || typeof binding.owner.agentId !== "string" || typeof binding.owner.sessionId !== "string" || typeof binding.workspaceRoot !== "string" || !isAbsolute(binding.workspaceRoot) || !binding.scope || typeof binding.assertCurrent !== "function" || !binding.context || typeof binding.context.get !== "function") throw new WebstackLspError("LSP_CODE_INTELLIGENCE_UNAVAILABLE", "invalid trusted code host binding");
|
|
1281
|
+
}
|
|
1282
|
+
|
|
830
1283
|
//#endregion
|
|
831
1284
|
//#region src/runtime/webstack-runtime.ts
|
|
832
1285
|
const methods = {
|
|
@@ -858,6 +1311,10 @@ const clientCapabilities = {
|
|
|
858
1311
|
};
|
|
859
1312
|
var WebstackLspRuntime = class {
|
|
860
1313
|
pool;
|
|
1314
|
+
snapshots;
|
|
1315
|
+
codeIntelligence;
|
|
1316
|
+
codeIntelligenceRuntime;
|
|
1317
|
+
semanticOperations = /* @__PURE__ */ new Map();
|
|
861
1318
|
workspaces = /* @__PURE__ */ new Map();
|
|
862
1319
|
lifetime = new AbortController();
|
|
863
1320
|
live = /* @__PURE__ */ new Map();
|
|
@@ -867,6 +1324,17 @@ var WebstackLspRuntime = class {
|
|
|
867
1324
|
this.config = config;
|
|
868
1325
|
this.fs = fs;
|
|
869
1326
|
this.subprocess = subprocess;
|
|
1327
|
+
this.snapshots = new DocumentSnapshotStore((workspaceRoot, filePath, maxBytes, signal) => readSource(this.fs, workspaceRoot, filePath, maxBytes, signal), config.limits.maxDocumentBytes);
|
|
1328
|
+
this.codeIntelligenceRuntime = createCodeIntelligenceToolServiceV1({
|
|
1329
|
+
snapshots: this.snapshots,
|
|
1330
|
+
semanticQuery: (scope, snapshot, request, signal) => this.semanticFromSnapshot(scope, snapshot, request, signal)
|
|
1331
|
+
});
|
|
1332
|
+
this.codeIntelligence = new CodeIntelligenceProtocolService({
|
|
1333
|
+
backend: this.codeIntelligenceRuntime,
|
|
1334
|
+
fs: this.fs,
|
|
1335
|
+
subprocess: this.subprocess,
|
|
1336
|
+
capabilities: (signal) => this.codeCapabilities(signal)
|
|
1337
|
+
});
|
|
870
1338
|
this.pool = new ServerPool(async (rawId, key, signal) => {
|
|
871
1339
|
const id = rawId, source = this.workspaces.get(key);
|
|
872
1340
|
const spec = await resolveLaunch(id, config, source.workspace);
|
|
@@ -917,6 +1385,9 @@ var WebstackLspRuntime = class {
|
|
|
917
1385
|
async navigation(request, signal) {
|
|
918
1386
|
const deadline = this.signal(request.operation === "hover" ? this.config.timeouts.primaryHoverMs : this.config.timeouts.navigationMs, signal);
|
|
919
1387
|
const [source, route] = await this.prepare(request, deadline);
|
|
1388
|
+
return this.navigationFromSource(request, source, route, deadline, signal);
|
|
1389
|
+
}
|
|
1390
|
+
async navigationFromSource(request, source, route, deadline, signal) {
|
|
920
1391
|
this.validatePosition(source.text, request.position);
|
|
921
1392
|
const result = await this.document(route.primary.server, source, route.primary.languageId, deadline, async (session, connection) => {
|
|
922
1393
|
if (!supportsOperation(session.capabilities, request.operation)) throw new WebstackLspError("LSP_UNSUPPORTED_OPERATION", `${route.primary.server} does not support ${request.operation}`);
|
|
@@ -965,6 +1436,9 @@ var WebstackLspRuntime = class {
|
|
|
965
1436
|
async completion(request, signal) {
|
|
966
1437
|
const deadline = this.signal(this.config.timeouts.completionMs, signal);
|
|
967
1438
|
const [source, route] = await this.prepare(request, deadline);
|
|
1439
|
+
return this.completionFromSource(request, source, route, deadline, signal);
|
|
1440
|
+
}
|
|
1441
|
+
async completionFromSource(request, source, route, deadline, signal) {
|
|
968
1442
|
this.validatePosition(source.text, request.position);
|
|
969
1443
|
const query = (id, language) => this.document(id, source, language, deadline, (session, connection) => {
|
|
970
1444
|
if (!session.capabilities.completionProvider) throw new WebstackLspError("LSP_UNSUPPORTED_OPERATION", `${id} does not support completion`);
|
|
@@ -995,7 +1469,20 @@ var WebstackLspRuntime = class {
|
|
|
995
1469
|
}
|
|
996
1470
|
async diagnostics(request, signal) {
|
|
997
1471
|
const deadline = this.signal(this.config.timeouts.diagnosticsMs, signal);
|
|
1472
|
+
if (!classifyFile(request.filePath, this.config.routing.bladeSuffixes)) {
|
|
1473
|
+
await this.snapshots.readFresh(providerScope(request.workspaceRoot), request.filePath, deadline);
|
|
1474
|
+
return {
|
|
1475
|
+
diagnostics: [],
|
|
1476
|
+
skipped: {
|
|
1477
|
+
reason: "unsupported_file_type",
|
|
1478
|
+
filePath: request.filePath
|
|
1479
|
+
}
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
998
1482
|
const [source, route] = await this.prepare(request, deadline);
|
|
1483
|
+
return this.diagnosticsFromSource(request, source, route, deadline, signal);
|
|
1484
|
+
}
|
|
1485
|
+
async diagnosticsFromSource(request, source, route, deadline, signal) {
|
|
999
1486
|
const selected = [route.primary, ...route.auxiliary && this.config.tailwind.enabled ? [route.auxiliary] : []].filter((item) => this.config.servers[item.server].enabled && (!request.servers || request.servers.includes(item.server)));
|
|
1000
1487
|
if (!selected.length) throw new WebstackLspError("LSP_UNAVAILABLE", "no selected diagnostic server supports this file");
|
|
1001
1488
|
const omittedServers = [];
|
|
@@ -1007,10 +1494,10 @@ var WebstackLspRuntime = class {
|
|
|
1007
1494
|
return (await connection.request("textDocument/diagnostic", { textDocument: { uri: source.uri } }, deadline))?.items ?? [];
|
|
1008
1495
|
}
|
|
1009
1496
|
let remove = () => {};
|
|
1010
|
-
const published = new Promise((resolve) => {
|
|
1497
|
+
const published = new Promise((resolve$1) => {
|
|
1011
1498
|
remove = connection.onNotification((message) => {
|
|
1012
1499
|
const p = message.params;
|
|
1013
|
-
if (message.method === "textDocument/publishDiagnostics" && p?.uri === source.uri && (p.version === void 0 || p.version === 1)) resolve(p.diagnostics);
|
|
1500
|
+
if (message.method === "textDocument/publishDiagnostics" && p?.uri === source.uri && (p.version === void 0 || p.version === 1)) resolve$1(p.diagnostics);
|
|
1014
1501
|
});
|
|
1015
1502
|
});
|
|
1016
1503
|
try {
|
|
@@ -1037,15 +1524,71 @@ var WebstackLspRuntime = class {
|
|
|
1037
1524
|
...omittedServers.length ? { omittedServers: omittedServers.sort() } : {}
|
|
1038
1525
|
};
|
|
1039
1526
|
}
|
|
1040
|
-
async
|
|
1527
|
+
async semanticFromSnapshot(scope, snapshot, request, signal) {
|
|
1528
|
+
const source = snapshot.source;
|
|
1529
|
+
this.rememberWorkspace(source);
|
|
1530
|
+
const file = classifyFile(snapshot.descriptor.filePath, this.config.routing.bladeSuffixes);
|
|
1531
|
+
if (!file) {
|
|
1532
|
+
if (request.operation === "diagnostics") return {
|
|
1533
|
+
diagnostics: [],
|
|
1534
|
+
skipped: {
|
|
1535
|
+
reason: "unsupported_file_type",
|
|
1536
|
+
filePath: snapshot.descriptor.filePath
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
throw new WebstackLspError("LSP_UNAVAILABLE", "unsupported file extension");
|
|
1540
|
+
}
|
|
1541
|
+
const route = routeFor(file);
|
|
1542
|
+
let result;
|
|
1543
|
+
if (request.operation === "diagnostics") {
|
|
1544
|
+
const deadline = this.signal(this.config.timeouts.diagnosticsMs, signal);
|
|
1545
|
+
result = await this.diagnosticsFromSource({
|
|
1546
|
+
workspaceRoot: scope.workspaceRoot,
|
|
1547
|
+
filePath: snapshot.descriptor.filePath,
|
|
1548
|
+
...request.severity ? { severity: request.severity } : {},
|
|
1549
|
+
...request.servers ? { servers: request.servers } : {}
|
|
1550
|
+
}, source, route, deadline, signal);
|
|
1551
|
+
} else if (request.operation === "completion") {
|
|
1552
|
+
const deadline = this.signal(this.config.timeouts.completionMs, signal);
|
|
1553
|
+
result = await this.completionFromSource({
|
|
1554
|
+
workspaceRoot: scope.workspaceRoot,
|
|
1555
|
+
filePath: snapshot.descriptor.filePath,
|
|
1556
|
+
position: request.position
|
|
1557
|
+
}, source, route, deadline, signal);
|
|
1558
|
+
} else {
|
|
1559
|
+
const operation = {
|
|
1560
|
+
definition: "goToDefinition",
|
|
1561
|
+
references: "findReferences",
|
|
1562
|
+
implementation: "goToImplementation",
|
|
1563
|
+
hover: "hover"
|
|
1564
|
+
}[request.operation];
|
|
1565
|
+
const position = request.position;
|
|
1566
|
+
const deadline = this.signal(request.operation === "hover" ? this.config.timeouts.primaryHoverMs : this.config.timeouts.navigationMs, signal);
|
|
1567
|
+
result = await this.navigationFromSource({
|
|
1568
|
+
workspaceRoot: scope.workspaceRoot,
|
|
1569
|
+
filePath: snapshot.descriptor.filePath,
|
|
1570
|
+
languageId: route.primary.languageId,
|
|
1571
|
+
operation,
|
|
1572
|
+
position
|
|
1573
|
+
}, source, route, deadline, signal);
|
|
1574
|
+
}
|
|
1575
|
+
if (!("skipped" in result && result.skipped)) {
|
|
1576
|
+
const operations = this.semanticOperations.get(route.primary.languageId) ?? /* @__PURE__ */ new Set();
|
|
1577
|
+
operations.add(request.operation);
|
|
1578
|
+
this.semanticOperations.set(route.primary.languageId, operations);
|
|
1579
|
+
}
|
|
1580
|
+
return result;
|
|
1581
|
+
}
|
|
1582
|
+
async status(signal) {
|
|
1041
1583
|
return {
|
|
1042
1584
|
providerId: this.config.providerId,
|
|
1585
|
+
supportedFileSuffixes: supportedFileSuffixes(this.config.routing.bladeSuffixes),
|
|
1043
1586
|
servers: await Promise.all(SERVER_IDS.map(async (id) => {
|
|
1044
1587
|
const config = this.config.servers[id];
|
|
1045
1588
|
let available = false;
|
|
1046
1589
|
try {
|
|
1047
1590
|
const launch = await resolveLaunch(id, this.config, ".");
|
|
1048
|
-
await this.subprocess.resolveExecutable(launch.command, launch.env, this.signal(5e3));
|
|
1591
|
+
await this.subprocess.resolveExecutable(launch.command, launch.env, this.signal(5e3, signal));
|
|
1049
1592
|
available = true;
|
|
1050
1593
|
} catch {}
|
|
1051
1594
|
return {
|
|
@@ -1059,11 +1602,69 @@ var WebstackLspRuntime = class {
|
|
|
1059
1602
|
}))
|
|
1060
1603
|
};
|
|
1061
1604
|
}
|
|
1605
|
+
async codeCapabilities(signal) {
|
|
1606
|
+
signal.throwIfAborted();
|
|
1607
|
+
const [provider, lsp] = await Promise.all([Promise.resolve(this.codeIntelligenceRuntime.capabilities()), this.status(signal)]);
|
|
1608
|
+
const server = lsp.servers.find((item) => item.id === "typescript");
|
|
1609
|
+
const languageCapabilities = provider.parsers.map((parser) => {
|
|
1610
|
+
const semanticIds = parser.language === "javascript" ? ["javascript", "javascriptreact"] : parser.language === "tsx" ? ["typescriptreact"] : ["typescript"];
|
|
1611
|
+
const semantic = [...new Set(semanticIds.flatMap((id) => [...this.semanticOperations.get(id) ?? []]))];
|
|
1612
|
+
const semanticReady = semantic.length > 0 && Boolean(server?.enabled && server.available);
|
|
1613
|
+
const reason = !parser.available ? "grammar_unavailable" : !server?.enabled || !server.available ? "server_unavailable" : !semanticReady ? "not_yet_verified" : void 0;
|
|
1614
|
+
return {
|
|
1615
|
+
language: parser.language,
|
|
1616
|
+
structure: parser.available,
|
|
1617
|
+
semantic,
|
|
1618
|
+
semanticReady,
|
|
1619
|
+
...reason ? { reason } : {}
|
|
1620
|
+
};
|
|
1621
|
+
});
|
|
1622
|
+
signal.throwIfAborted();
|
|
1623
|
+
return {
|
|
1624
|
+
version: 1,
|
|
1625
|
+
capabilities: [
|
|
1626
|
+
"snapshots",
|
|
1627
|
+
"structure",
|
|
1628
|
+
"semantic"
|
|
1629
|
+
],
|
|
1630
|
+
languages: provider.parsers.filter((parser) => parser.available).map((parser) => parser.language),
|
|
1631
|
+
queryIds: [
|
|
1632
|
+
"declarations",
|
|
1633
|
+
"calls",
|
|
1634
|
+
"imports"
|
|
1635
|
+
],
|
|
1636
|
+
semanticReady: languageCapabilities.some((language) => language.semanticReady),
|
|
1637
|
+
sourceKinds: ["disk"],
|
|
1638
|
+
languageCapabilities,
|
|
1639
|
+
limits: {
|
|
1640
|
+
documentBytes: provider.limits.maxDocumentBytes,
|
|
1641
|
+
captureItems: provider.limits.maxCaptures,
|
|
1642
|
+
outlineItems: provider.limits.maxOutline,
|
|
1643
|
+
spanChars: provider.limits.maxSpanChars,
|
|
1644
|
+
responseBytes: provider.limits.maxResponseBytes,
|
|
1645
|
+
files: 100,
|
|
1646
|
+
inputBytes: 16777216,
|
|
1647
|
+
outputBytes: 65536,
|
|
1648
|
+
calls: 100,
|
|
1649
|
+
batchMs: 3e4,
|
|
1650
|
+
structureMs: 2e3,
|
|
1651
|
+
semanticMs: 1e4
|
|
1652
|
+
}
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1062
1655
|
async dispose() {
|
|
1063
1656
|
this.lifetime.abort();
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1657
|
+
try {
|
|
1658
|
+
await this.codeIntelligence.dispose?.();
|
|
1659
|
+
} finally {
|
|
1660
|
+
try {
|
|
1661
|
+
await this.pool.disposeAll();
|
|
1662
|
+
} finally {
|
|
1663
|
+
await this.codeIntelligenceRuntime.dispose?.();
|
|
1664
|
+
this.workspaces.clear();
|
|
1665
|
+
this.live.clear();
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1067
1668
|
}
|
|
1068
1669
|
signal(ms, signal) {
|
|
1069
1670
|
return AbortSignal.any([
|
|
@@ -1076,15 +1677,18 @@ var WebstackLspRuntime = class {
|
|
|
1076
1677
|
throwIfAborted(signal);
|
|
1077
1678
|
const file = classifyFile(request.filePath, this.config.routing.bladeSuffixes);
|
|
1078
1679
|
if (!file) throw new WebstackLspError("LSP_UNAVAILABLE", "unsupported file extension");
|
|
1079
|
-
const source = await
|
|
1680
|
+
const source = await this.snapshots.readFresh(providerScope(request.workspaceRoot), request.filePath, signal).catch((error) => {
|
|
1080
1681
|
throwIfAborted(signal);
|
|
1081
1682
|
throw error;
|
|
1082
1683
|
});
|
|
1684
|
+
this.rememberWorkspace(source);
|
|
1685
|
+
return [source, routeFor(file)];
|
|
1686
|
+
}
|
|
1687
|
+
rememberWorkspace(source) {
|
|
1083
1688
|
this.workspaces.set(source.key, {
|
|
1084
1689
|
...source,
|
|
1085
1690
|
text: ""
|
|
1086
1691
|
});
|
|
1087
|
-
return [source, routeFor(file)];
|
|
1088
1692
|
}
|
|
1089
1693
|
validatePosition(text, position) {
|
|
1090
1694
|
const line = text.split(/\r?\n/)[position.line];
|
|
@@ -1117,6 +1721,13 @@ var WebstackLspRuntime = class {
|
|
|
1117
1721
|
} : void 0);
|
|
1118
1722
|
}
|
|
1119
1723
|
};
|
|
1724
|
+
function providerScope(workspaceRoot) {
|
|
1725
|
+
return {
|
|
1726
|
+
ownerId: "lsp-provider",
|
|
1727
|
+
sessionId: "lsp-provider",
|
|
1728
|
+
workspaceRoot
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1120
1731
|
|
|
1121
1732
|
//#endregion
|
|
1122
1733
|
//#region src/provider.ts
|
|
@@ -1160,6 +1771,8 @@ async function apply(ctx, config = {}) {
|
|
|
1160
1771
|
}
|
|
1161
1772
|
yield ctx.lsp.registerProvider(createCompositeProvider(runtime, resolved));
|
|
1162
1773
|
yield ctx.provide("webstackLsp", createWebstackLspService(runtime));
|
|
1774
|
+
yield ctx.provide("codeIntelligence", runtime.codeIntelligence);
|
|
1775
|
+
yield ctx.provide("codeIntelligenceRuntime", runtime.codeIntelligenceRuntime);
|
|
1163
1776
|
}, "webstack LSP provider");
|
|
1164
1777
|
}
|
|
1165
1778
|
|