@askdkc/dsh-lsp-server 0.1.12 → 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 +8 -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 +135 -7
- package/lib/extra-tool.js.map +1 -1
- package/lib/launch-ByUvgkVv.js.map +1 -1
- package/lib/provider.js +639 -41
- 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/runtime/webstack-runtime.d.ts +13 -1
- package/lib/types/service.d.ts +16 -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,17 +147,17 @@ 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
|
};
|
|
@@ -401,8 +405,8 @@ var JsonRpcConnection = class {
|
|
|
401
405
|
throwIfAborted(signal);
|
|
402
406
|
if (this.closed) throw new Error("connection is closed");
|
|
403
407
|
const id = this.nextId++;
|
|
404
|
-
const promise = new Promise((resolve, reject) => this.pending.set(id, {
|
|
405
|
-
resolve,
|
|
408
|
+
const promise = new Promise((resolve$1, reject) => this.pending.set(id, {
|
|
409
|
+
resolve: resolve$1,
|
|
406
410
|
reject,
|
|
407
411
|
method
|
|
408
412
|
}));
|
|
@@ -451,7 +455,7 @@ var JsonRpcConnection = class {
|
|
|
451
455
|
for (const message of messages) this.dispatch(message);
|
|
452
456
|
}
|
|
453
457
|
dispatch(value) {
|
|
454
|
-
if (!isRecord$
|
|
458
|
+
if (!isRecord$5(value) || value.jsonrpc !== "2.0") return this.close(new JsonRpcFrameError("invalid JSON-RPC message"));
|
|
455
459
|
if ("method" in value && typeof value.method === "string") {
|
|
456
460
|
if ("id" in value) {
|
|
457
461
|
const request = value;
|
|
@@ -480,12 +484,12 @@ var JsonRpcConnection = class {
|
|
|
480
484
|
}
|
|
481
485
|
}
|
|
482
486
|
write(message) {
|
|
483
|
-
return new Promise((resolve, reject) => {
|
|
484
|
-
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());
|
|
485
489
|
});
|
|
486
490
|
}
|
|
487
491
|
};
|
|
488
|
-
function isRecord$
|
|
492
|
+
function isRecord$5(value) {
|
|
489
493
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
490
494
|
}
|
|
491
495
|
|
|
@@ -650,8 +654,8 @@ var ServerPool = class {
|
|
|
650
654
|
async serialize(key, task, signal) {
|
|
651
655
|
const previous = this.queues.get(key) ?? Promise.resolve();
|
|
652
656
|
let release;
|
|
653
|
-
const current = new Promise((resolve) => {
|
|
654
|
-
release = resolve;
|
|
657
|
+
const current = new Promise((resolve$1) => {
|
|
658
|
+
release = resolve$1;
|
|
655
659
|
});
|
|
656
660
|
this.queues.set(key, current);
|
|
657
661
|
try {
|
|
@@ -718,29 +722,29 @@ function normalizeHover(value) {
|
|
|
718
722
|
function normalizeContents(value) {
|
|
719
723
|
if (typeof value === "string") return value;
|
|
720
724
|
if (Array.isArray(value)) {
|
|
721
|
-
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);
|
|
722
726
|
return parts.every((item) => item !== void 0) ? parts.join("\n") : void 0;
|
|
723
727
|
}
|
|
724
|
-
if (isRecord$
|
|
728
|
+
if (isRecord$4(value) && typeof value.value === "string") return value.value;
|
|
725
729
|
}
|
|
726
730
|
function isRange$2(value) {
|
|
727
|
-
return isRecord$
|
|
731
|
+
return isRecord$4(value) && isPosition$2(value.start) && isPosition$2(value.end);
|
|
728
732
|
}
|
|
729
733
|
function isPosition$2(value) {
|
|
730
|
-
return isRecord$
|
|
734
|
+
return isRecord$4(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
|
|
731
735
|
}
|
|
732
|
-
function isRecord$
|
|
736
|
+
function isRecord$4(value) {
|
|
733
737
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
734
738
|
}
|
|
735
739
|
|
|
736
740
|
//#endregion
|
|
737
741
|
//#region src/protocol/normalize-location.ts
|
|
738
742
|
function normalizeLocation(value) {
|
|
739
|
-
if (isRecord$
|
|
743
|
+
if (isRecord$3(value) && typeof value.targetUri === "string" && isRange$1(value.targetSelectionRange)) return {
|
|
740
744
|
uri: value.targetUri,
|
|
741
745
|
range: value.targetSelectionRange
|
|
742
746
|
};
|
|
743
|
-
if (!isRecord$
|
|
747
|
+
if (!isRecord$3(value) || typeof value.uri !== "string" || !isRange$1(value.range)) return void 0;
|
|
744
748
|
return {
|
|
745
749
|
uri: value.uri,
|
|
746
750
|
range: value.range
|
|
@@ -750,19 +754,19 @@ function normalizeLocations(value) {
|
|
|
750
754
|
return (Array.isArray(value) ? value : value === null || value === void 0 ? [] : [value]).map(normalizeLocation).filter((location) => location !== void 0);
|
|
751
755
|
}
|
|
752
756
|
function isRange$1(value) {
|
|
753
|
-
return isRecord$
|
|
757
|
+
return isRecord$3(value) && isPosition$1(value.start) && isPosition$1(value.end);
|
|
754
758
|
}
|
|
755
759
|
function isPosition$1(value) {
|
|
756
|
-
return isRecord$
|
|
760
|
+
return isRecord$3(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
|
|
757
761
|
}
|
|
758
|
-
function isRecord$
|
|
762
|
+
function isRecord$3(value) {
|
|
759
763
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
760
764
|
}
|
|
761
765
|
|
|
762
766
|
//#endregion
|
|
763
767
|
//#region src/protocol/normalize-diagnostic.ts
|
|
764
768
|
function normalizeDiagnostic(value, server, fallbackUri) {
|
|
765
|
-
if (!isRecord$
|
|
769
|
+
if (!isRecord$2(value) || !isRange(value.range) || typeof value.message !== "string") return void 0;
|
|
766
770
|
const uri = typeof value.uri === "string" ? value.uri : fallbackUri;
|
|
767
771
|
const severity = typeof value.severity === "number" && value.severity >= 1 && value.severity <= 4 ? value.severity : void 0;
|
|
768
772
|
const code = typeof value.code === "string" || typeof value.code === "number" ? value.code : void 0;
|
|
@@ -780,19 +784,19 @@ function normalizeDiagnostics(value, server, fallbackUri) {
|
|
|
780
784
|
return (Array.isArray(value) ? value : []).map((item) => normalizeDiagnostic(item, server, fallbackUri)).filter((item) => item !== void 0);
|
|
781
785
|
}
|
|
782
786
|
function isRange(value) {
|
|
783
|
-
return isRecord$
|
|
787
|
+
return isRecord$2(value) && isPosition(value.start) && isPosition(value.end);
|
|
784
788
|
}
|
|
785
789
|
function isPosition(value) {
|
|
786
|
-
return isRecord$
|
|
790
|
+
return isRecord$2(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
|
|
787
791
|
}
|
|
788
|
-
function isRecord$
|
|
792
|
+
function isRecord$2(value) {
|
|
789
793
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
790
794
|
}
|
|
791
795
|
|
|
792
796
|
//#endregion
|
|
793
797
|
//#region src/protocol/normalize-completion.ts
|
|
794
798
|
function normalizeCompletionItem(value, server) {
|
|
795
|
-
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;
|
|
796
800
|
return {
|
|
797
801
|
server,
|
|
798
802
|
label: value.label,
|
|
@@ -807,9 +811,9 @@ function normalizeCompletionItem(value, server) {
|
|
|
807
811
|
};
|
|
808
812
|
}
|
|
809
813
|
function normalizeCompletion(value, server) {
|
|
810
|
-
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);
|
|
811
815
|
}
|
|
812
|
-
function isRecord(value) {
|
|
816
|
+
function isRecord$1(value) {
|
|
813
817
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
814
818
|
}
|
|
815
819
|
|
|
@@ -831,6 +835,451 @@ function sameRange(left, right) {
|
|
|
831
835
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
832
836
|
}
|
|
833
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
|
+
|
|
834
1283
|
//#endregion
|
|
835
1284
|
//#region src/runtime/webstack-runtime.ts
|
|
836
1285
|
const methods = {
|
|
@@ -862,6 +1311,10 @@ const clientCapabilities = {
|
|
|
862
1311
|
};
|
|
863
1312
|
var WebstackLspRuntime = class {
|
|
864
1313
|
pool;
|
|
1314
|
+
snapshots;
|
|
1315
|
+
codeIntelligence;
|
|
1316
|
+
codeIntelligenceRuntime;
|
|
1317
|
+
semanticOperations = /* @__PURE__ */ new Map();
|
|
865
1318
|
workspaces = /* @__PURE__ */ new Map();
|
|
866
1319
|
lifetime = new AbortController();
|
|
867
1320
|
live = /* @__PURE__ */ new Map();
|
|
@@ -871,6 +1324,17 @@ var WebstackLspRuntime = class {
|
|
|
871
1324
|
this.config = config;
|
|
872
1325
|
this.fs = fs;
|
|
873
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
|
+
});
|
|
874
1338
|
this.pool = new ServerPool(async (rawId, key, signal) => {
|
|
875
1339
|
const id = rawId, source = this.workspaces.get(key);
|
|
876
1340
|
const spec = await resolveLaunch(id, config, source.workspace);
|
|
@@ -921,6 +1385,9 @@ var WebstackLspRuntime = class {
|
|
|
921
1385
|
async navigation(request, signal) {
|
|
922
1386
|
const deadline = this.signal(request.operation === "hover" ? this.config.timeouts.primaryHoverMs : this.config.timeouts.navigationMs, signal);
|
|
923
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) {
|
|
924
1391
|
this.validatePosition(source.text, request.position);
|
|
925
1392
|
const result = await this.document(route.primary.server, source, route.primary.languageId, deadline, async (session, connection) => {
|
|
926
1393
|
if (!supportsOperation(session.capabilities, request.operation)) throw new WebstackLspError("LSP_UNSUPPORTED_OPERATION", `${route.primary.server} does not support ${request.operation}`);
|
|
@@ -969,6 +1436,9 @@ var WebstackLspRuntime = class {
|
|
|
969
1436
|
async completion(request, signal) {
|
|
970
1437
|
const deadline = this.signal(this.config.timeouts.completionMs, signal);
|
|
971
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) {
|
|
972
1442
|
this.validatePosition(source.text, request.position);
|
|
973
1443
|
const query = (id, language) => this.document(id, source, language, deadline, (session, connection) => {
|
|
974
1444
|
if (!session.capabilities.completionProvider) throw new WebstackLspError("LSP_UNSUPPORTED_OPERATION", `${id} does not support completion`);
|
|
@@ -1000,7 +1470,7 @@ var WebstackLspRuntime = class {
|
|
|
1000
1470
|
async diagnostics(request, signal) {
|
|
1001
1471
|
const deadline = this.signal(this.config.timeouts.diagnosticsMs, signal);
|
|
1002
1472
|
if (!classifyFile(request.filePath, this.config.routing.bladeSuffixes)) {
|
|
1003
|
-
await
|
|
1473
|
+
await this.snapshots.readFresh(providerScope(request.workspaceRoot), request.filePath, deadline);
|
|
1004
1474
|
return {
|
|
1005
1475
|
diagnostics: [],
|
|
1006
1476
|
skipped: {
|
|
@@ -1010,6 +1480,9 @@ var WebstackLspRuntime = class {
|
|
|
1010
1480
|
};
|
|
1011
1481
|
}
|
|
1012
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) {
|
|
1013
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)));
|
|
1014
1487
|
if (!selected.length) throw new WebstackLspError("LSP_UNAVAILABLE", "no selected diagnostic server supports this file");
|
|
1015
1488
|
const omittedServers = [];
|
|
@@ -1021,10 +1494,10 @@ var WebstackLspRuntime = class {
|
|
|
1021
1494
|
return (await connection.request("textDocument/diagnostic", { textDocument: { uri: source.uri } }, deadline))?.items ?? [];
|
|
1022
1495
|
}
|
|
1023
1496
|
let remove = () => {};
|
|
1024
|
-
const published = new Promise((resolve) => {
|
|
1497
|
+
const published = new Promise((resolve$1) => {
|
|
1025
1498
|
remove = connection.onNotification((message) => {
|
|
1026
1499
|
const p = message.params;
|
|
1027
|
-
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);
|
|
1028
1501
|
});
|
|
1029
1502
|
});
|
|
1030
1503
|
try {
|
|
@@ -1051,7 +1524,62 @@ var WebstackLspRuntime = class {
|
|
|
1051
1524
|
...omittedServers.length ? { omittedServers: omittedServers.sort() } : {}
|
|
1052
1525
|
};
|
|
1053
1526
|
}
|
|
1054
|
-
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) {
|
|
1055
1583
|
return {
|
|
1056
1584
|
providerId: this.config.providerId,
|
|
1057
1585
|
supportedFileSuffixes: supportedFileSuffixes(this.config.routing.bladeSuffixes),
|
|
@@ -1060,7 +1588,7 @@ var WebstackLspRuntime = class {
|
|
|
1060
1588
|
let available = false;
|
|
1061
1589
|
try {
|
|
1062
1590
|
const launch = await resolveLaunch(id, this.config, ".");
|
|
1063
|
-
await this.subprocess.resolveExecutable(launch.command, launch.env, this.signal(5e3));
|
|
1591
|
+
await this.subprocess.resolveExecutable(launch.command, launch.env, this.signal(5e3, signal));
|
|
1064
1592
|
available = true;
|
|
1065
1593
|
} catch {}
|
|
1066
1594
|
return {
|
|
@@ -1074,11 +1602,69 @@ var WebstackLspRuntime = class {
|
|
|
1074
1602
|
}))
|
|
1075
1603
|
};
|
|
1076
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
|
+
}
|
|
1077
1655
|
async dispose() {
|
|
1078
1656
|
this.lifetime.abort();
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
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
|
+
}
|
|
1082
1668
|
}
|
|
1083
1669
|
signal(ms, signal) {
|
|
1084
1670
|
return AbortSignal.any([
|
|
@@ -1091,15 +1677,18 @@ var WebstackLspRuntime = class {
|
|
|
1091
1677
|
throwIfAborted(signal);
|
|
1092
1678
|
const file = classifyFile(request.filePath, this.config.routing.bladeSuffixes);
|
|
1093
1679
|
if (!file) throw new WebstackLspError("LSP_UNAVAILABLE", "unsupported file extension");
|
|
1094
|
-
const source = await
|
|
1680
|
+
const source = await this.snapshots.readFresh(providerScope(request.workspaceRoot), request.filePath, signal).catch((error) => {
|
|
1095
1681
|
throwIfAborted(signal);
|
|
1096
1682
|
throw error;
|
|
1097
1683
|
});
|
|
1684
|
+
this.rememberWorkspace(source);
|
|
1685
|
+
return [source, routeFor(file)];
|
|
1686
|
+
}
|
|
1687
|
+
rememberWorkspace(source) {
|
|
1098
1688
|
this.workspaces.set(source.key, {
|
|
1099
1689
|
...source,
|
|
1100
1690
|
text: ""
|
|
1101
1691
|
});
|
|
1102
|
-
return [source, routeFor(file)];
|
|
1103
1692
|
}
|
|
1104
1693
|
validatePosition(text, position) {
|
|
1105
1694
|
const line = text.split(/\r?\n/)[position.line];
|
|
@@ -1132,6 +1721,13 @@ var WebstackLspRuntime = class {
|
|
|
1132
1721
|
} : void 0);
|
|
1133
1722
|
}
|
|
1134
1723
|
};
|
|
1724
|
+
function providerScope(workspaceRoot) {
|
|
1725
|
+
return {
|
|
1726
|
+
ownerId: "lsp-provider",
|
|
1727
|
+
sessionId: "lsp-provider",
|
|
1728
|
+
workspaceRoot
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1135
1731
|
|
|
1136
1732
|
//#endregion
|
|
1137
1733
|
//#region src/provider.ts
|
|
@@ -1175,6 +1771,8 @@ async function apply(ctx, config = {}) {
|
|
|
1175
1771
|
}
|
|
1176
1772
|
yield ctx.lsp.registerProvider(createCompositeProvider(runtime, resolved));
|
|
1177
1773
|
yield ctx.provide("webstackLsp", createWebstackLspService(runtime));
|
|
1774
|
+
yield ctx.provide("codeIntelligence", runtime.codeIntelligence);
|
|
1775
|
+
yield ctx.provide("codeIntelligenceRuntime", runtime.codeIntelligenceRuntime);
|
|
1178
1776
|
}, "webstack LSP provider");
|
|
1179
1777
|
}
|
|
1180
1778
|
|