@askdkc/dsh-lsp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +27 -0
  4. package/cordis.patch.yml +29 -0
  5. package/docs/usage.md +94 -0
  6. package/lib/cli/doctor.js +135 -0
  7. package/lib/cli/doctor.js.map +1 -0
  8. package/lib/completion-B7NPVCEN.js +79 -0
  9. package/lib/completion-B7NPVCEN.js.map +1 -0
  10. package/lib/errors-DtyWAUi0.js +16 -0
  11. package/lib/errors-DtyWAUi0.js.map +1 -0
  12. package/lib/extra-tool.js +113 -0
  13. package/lib/extra-tool.js.map +1 -0
  14. package/lib/launch-ByUvgkVv.js +308 -0
  15. package/lib/launch-ByUvgkVv.js.map +1 -0
  16. package/lib/provider.js +1164 -0
  17. package/lib/provider.js.map +1 -0
  18. package/lib/types/cli/availability.d.ts +3 -0
  19. package/lib/types/cli/doctor.d.ts +33 -0
  20. package/lib/types/config.d.ts +87 -0
  21. package/lib/types/errors.d.ts +7 -0
  22. package/lib/types/extra-tool.d.ts +13 -0
  23. package/lib/types/host/dsh.d.ts +10 -0
  24. package/lib/types/host/source.d.ts +8 -0
  25. package/lib/types/host/uri.d.ts +2 -0
  26. package/lib/types/host/workspace.d.ts +16 -0
  27. package/lib/types/merge/completion.d.ts +6 -0
  28. package/lib/types/merge/diagnostics.d.ts +6 -0
  29. package/lib/types/merge/hover.d.ts +5 -0
  30. package/lib/types/protocol/capabilities.d.ts +3 -0
  31. package/lib/types/protocol/initialize.d.ts +3 -0
  32. package/lib/types/protocol/normalize-completion.d.ts +3 -0
  33. package/lib/types/protocol/normalize-diagnostic.d.ts +3 -0
  34. package/lib/types/protocol/normalize-hover.d.ts +6 -0
  35. package/lib/types/protocol/normalize-location.d.ts +7 -0
  36. package/lib/types/protocol/types.d.ts +57 -0
  37. package/lib/types/provider.d.ts +35 -0
  38. package/lib/types/routing/classifier.d.ts +8 -0
  39. package/lib/types/routing/language-id.d.ts +5 -0
  40. package/lib/types/routing/route-table.d.ts +13 -0
  41. package/lib/types/routing/tailwind-candidate.d.ts +1 -0
  42. package/lib/types/runtime/cancellation.d.ts +4 -0
  43. package/lib/types/runtime/connection.d.ts +22 -0
  44. package/lib/types/runtime/framing.d.ts +14 -0
  45. package/lib/types/runtime/process-lifecycle.d.ts +21 -0
  46. package/lib/types/runtime/server-pool.d.ts +24 -0
  47. package/lib/types/runtime/server-session.d.ts +32 -0
  48. package/lib/types/runtime/webstack-runtime.d.ts +28 -0
  49. package/lib/types/servers/html.d.ts +2 -0
  50. package/lib/types/servers/launch.d.ts +5 -0
  51. package/lib/types/servers/package-bin.d.ts +2 -0
  52. package/lib/types/servers/phpantom.d.ts +2 -0
  53. package/lib/types/servers/svelte.d.ts +2 -0
  54. package/lib/types/servers/tailwind.d.ts +2 -0
  55. package/lib/types/servers/types.d.ts +26 -0
  56. package/lib/types/servers/typescript.d.ts +2 -0
  57. package/lib/types/service.d.ts +83 -0
  58. package/package.json +138 -0
@@ -0,0 +1,1164 @@
1
+ import { WebstackLspError } from "./errors-DtyWAUi0.js";
2
+ import { SERVER_IDS, createWebstackLspService, initializationOptions, resolveConfig, resolveLaunch, serverConfiguration } from "./launch-ByUvgkVv.js";
3
+ import { mergeCompletions, mergeDiagnostics } from "./completion-B7NPVCEN.js";
4
+ import { LspProviderId } from "@deepseek-ai/dsh-lsp";
5
+ import { TextDecoder } from "node:util";
6
+
7
+ //#region src/runtime/cancellation.ts
8
+ function abortError() {
9
+ const error = /* @__PURE__ */ new Error("The operation was aborted");
10
+ error.name = "AbortError";
11
+ return error;
12
+ }
13
+ function throwIfAborted(signal) {
14
+ if (signal?.aborted) throw abortError();
15
+ }
16
+ function onAbort(signal, callback) {
17
+ if (!signal) return () => {};
18
+ if (signal.aborted) {
19
+ callback();
20
+ return () => {};
21
+ }
22
+ signal.addEventListener("abort", callback, { once: true });
23
+ return () => signal.removeEventListener("abort", callback);
24
+ }
25
+ async function withAbort(promise, signal) {
26
+ throwIfAborted(signal);
27
+ if (!signal) return promise;
28
+ return new Promise((resolve, reject) => {
29
+ const remove = onAbort(signal, () => reject(abortError()));
30
+ promise.then((value) => {
31
+ remove();
32
+ resolve(value);
33
+ }, (error) => {
34
+ remove();
35
+ reject(error);
36
+ });
37
+ });
38
+ }
39
+
40
+ //#endregion
41
+ //#region src/host/dsh.ts
42
+ async function readSource(fs, workspaceRoot, filePath, maxBytes, signal) {
43
+ if (!workspaceRoot?.trim() || !filePath?.trim()) throw new WebstackLspError("LSP_WORKSPACE_REQUIRED", "workspaceRoot and filePath are required");
44
+ throwIfAborted(signal);
45
+ const options = signal ? { signal } : {};
46
+ const root = await fs.resolve(workspaceRoot, options);
47
+ if ((await fs.stat(root, signal))?.type !== "directory") throw new Error("workspace root is not a directory");
48
+ const workspace = fs.processPath(root);
49
+ const target = await fs.resolve(filePath, {
50
+ cwd: workspace,
51
+ ...options
52
+ });
53
+ if (!fs.contains(root, target)) throw new Error("source is outside workspace");
54
+ if ((await fs.stat(target, signal))?.type !== "file") throw new Error("source is not a regular file");
55
+ let text = "", bytes = 0;
56
+ for await (const chunk of await fs.streamText(target, signal)) {
57
+ throwIfAborted(signal);
58
+ bytes += Buffer.byteLength(chunk);
59
+ if (bytes > maxBytes) throw new Error("source exceeds document limit");
60
+ text += chunk;
61
+ }
62
+ throwIfAborted(signal);
63
+ return {
64
+ workspace,
65
+ workspaceUri: fs.fileUrl(root),
66
+ key: root.targetKey,
67
+ uri: fs.fileUrl(target),
68
+ text
69
+ };
70
+ }
71
+
72
+ //#endregion
73
+ //#region src/routing/classifier.ts
74
+ const CLASSIFIERS = [
75
+ {
76
+ suffix: ".blade.php",
77
+ kind: "blade"
78
+ },
79
+ {
80
+ suffix: ".typescriptreact",
81
+ kind: "typescriptreact"
82
+ },
83
+ {
84
+ suffix: ".javascriptreact",
85
+ kind: "javascriptreact"
86
+ },
87
+ {
88
+ suffix: ".tsx",
89
+ kind: "typescriptreact"
90
+ },
91
+ {
92
+ suffix: ".jsx",
93
+ kind: "javascriptreact"
94
+ },
95
+ {
96
+ suffix: ".mts",
97
+ kind: "typescript"
98
+ },
99
+ {
100
+ suffix: ".cts",
101
+ kind: "typescript"
102
+ },
103
+ {
104
+ suffix: ".mjs",
105
+ kind: "javascript"
106
+ },
107
+ {
108
+ suffix: ".cjs",
109
+ kind: "javascript"
110
+ },
111
+ {
112
+ suffix: ".svelte",
113
+ kind: "svelte"
114
+ },
115
+ {
116
+ suffix: ".html",
117
+ kind: "html"
118
+ },
119
+ {
120
+ suffix: ".htm",
121
+ kind: "html"
122
+ },
123
+ {
124
+ suffix: ".php",
125
+ kind: "php"
126
+ },
127
+ {
128
+ suffix: ".ts",
129
+ kind: "typescript"
130
+ },
131
+ {
132
+ suffix: ".js",
133
+ kind: "javascript"
134
+ },
135
+ {
136
+ suffix: ".css",
137
+ kind: "css"
138
+ },
139
+ {
140
+ suffix: ".pcss",
141
+ kind: "css"
142
+ }
143
+ ];
144
+ function classifyFile(filePath, bladeSuffixes = [".blade.php"]) {
145
+ const normalizedPath = filePath.replaceAll("\\", "/");
146
+ const basename = normalizedPath.slice(normalizedPath.lastIndexOf("/") + 1).toLowerCase();
147
+ if (!basename || basename === "." || basename === ".." || basename.startsWith(".")) return void 0;
148
+ const customBlade = bladeSuffixes.map((suffix) => suffix.toLowerCase()).sort((left, right) => right.length - left.length);
149
+ const match = [...customBlade.map((suffix) => ({
150
+ suffix,
151
+ kind: "blade"
152
+ })), ...CLASSIFIERS.filter((item) => !customBlade.includes(item.suffix))].find((item) => basename.endsWith(item.suffix));
153
+ if (!match) return void 0;
154
+ return {
155
+ path: normalizedPath,
156
+ basename,
157
+ suffix: match.suffix,
158
+ kind: match.kind
159
+ };
160
+ }
161
+
162
+ //#endregion
163
+ //#region src/routing/route-table.ts
164
+ const ROUTES = {
165
+ blade: {
166
+ primary: {
167
+ server: "phpantom",
168
+ languageId: "blade"
169
+ },
170
+ auxiliary: {
171
+ server: "tailwind",
172
+ languageId: "html"
173
+ }
174
+ },
175
+ php: { primary: {
176
+ server: "phpantom",
177
+ languageId: "php"
178
+ } },
179
+ typescript: {
180
+ primary: {
181
+ server: "typescript",
182
+ languageId: "typescript"
183
+ },
184
+ auxiliary: {
185
+ server: "tailwind",
186
+ languageId: "typescript"
187
+ }
188
+ },
189
+ typescriptreact: {
190
+ primary: {
191
+ server: "typescript",
192
+ languageId: "typescriptreact"
193
+ },
194
+ auxiliary: {
195
+ server: "tailwind",
196
+ languageId: "typescriptreact"
197
+ }
198
+ },
199
+ javascript: {
200
+ primary: {
201
+ server: "typescript",
202
+ languageId: "javascript"
203
+ },
204
+ auxiliary: {
205
+ server: "tailwind",
206
+ languageId: "javascript"
207
+ }
208
+ },
209
+ javascriptreact: {
210
+ primary: {
211
+ server: "typescript",
212
+ languageId: "javascriptreact"
213
+ },
214
+ auxiliary: {
215
+ server: "tailwind",
216
+ languageId: "javascriptreact"
217
+ }
218
+ },
219
+ svelte: {
220
+ primary: {
221
+ server: "svelte",
222
+ languageId: "svelte"
223
+ },
224
+ auxiliary: {
225
+ server: "tailwind",
226
+ languageId: "svelte"
227
+ }
228
+ },
229
+ html: {
230
+ primary: {
231
+ server: "html",
232
+ languageId: "html"
233
+ },
234
+ auxiliary: {
235
+ server: "tailwind",
236
+ languageId: "html"
237
+ }
238
+ },
239
+ css: {
240
+ primary: {
241
+ server: "css",
242
+ languageId: "css"
243
+ },
244
+ auxiliary: {
245
+ server: "tailwind",
246
+ languageId: "css"
247
+ }
248
+ }
249
+ };
250
+ function routeFor(file) {
251
+ const route = ROUTES[file.kind];
252
+ return {
253
+ primary: { ...route.primary },
254
+ ...route.auxiliary ? { auxiliary: { ...route.auxiliary } } : {}
255
+ };
256
+ }
257
+
258
+ //#endregion
259
+ //#region src/routing/tailwind-candidate.ts
260
+ const DEFAULT_PATTERNS = [
261
+ /\bclass\s*=\s*["'][^"']*$/i,
262
+ /\bclassName\s*=\s*["'][^"']*$/i,
263
+ /\bclass:list\s*=\s*["'][^"']*$/i,
264
+ /@apply\s+[\w-]*$/i,
265
+ /\b(?:clsx|cva)\s*\([^)]*["'][^"']*$/i
266
+ ];
267
+ function hasTailwindCandidate(source, line, classRegex = [], character, classAttributes = [
268
+ "class",
269
+ "className",
270
+ "class:list"
271
+ ]) {
272
+ const lines = source.split(/\r?\n/);
273
+ const context = [...lines.slice(Math.max(0, line - 2), line), (lines[line] ?? "").slice(0, character)].join("\n");
274
+ if (classAttributes.some((attribute) => {
275
+ const index = context.lastIndexOf(attribute + "=");
276
+ return index >= 0 && /^[\"'][^\"']*$/.test(context.slice(index + attribute.length + 1).trimStart());
277
+ })) return true;
278
+ const configured = classRegex.flatMap((item) => Array.isArray(item) ? item : [item]).map((pattern) => new RegExp(pattern));
279
+ return [...DEFAULT_PATTERNS, ...configured].some((pattern) => pattern.test(context));
280
+ }
281
+
282
+ //#endregion
283
+ //#region src/protocol/initialize.ts
284
+ async function initializeServer(connection, params, signal) {
285
+ throwIfAborted(signal);
286
+ const value = await connection.request("initialize", params, signal);
287
+ if (!isInitializeResult(value)) throw new Error("malformed initialize response");
288
+ await connection.notify("initialized", {});
289
+ return value;
290
+ }
291
+ function isInitializeResult(value) {
292
+ return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.capabilities === "object" && value.capabilities !== null;
293
+ }
294
+
295
+ //#endregion
296
+ //#region src/runtime/framing.ts
297
+ var JsonRpcFrameError = class extends Error {
298
+ code = "LSP_MALFORMED_RESPONSE";
299
+ constructor(message) {
300
+ super(message);
301
+ this.name = "JsonRpcFrameError";
302
+ }
303
+ };
304
+ var ContentLengthDecoder = class {
305
+ buffer = Buffer.alloc(0);
306
+ decoder = new TextDecoder("utf-8", { fatal: true });
307
+ constructor(maxMessageBytes) {
308
+ this.maxMessageBytes = maxMessageBytes;
309
+ if (!Number.isSafeInteger(maxMessageBytes) || maxMessageBytes <= 0) throw new RangeError("maxMessageBytes must be positive");
310
+ }
311
+ feed(chunk) {
312
+ this.buffer = Buffer.concat([this.buffer, Buffer.from(chunk)]);
313
+ const messages = [];
314
+ while (true) {
315
+ const headerEnd = this.buffer.indexOf(Buffer.from("\r\n\r\n"));
316
+ if (headerEnd < 0) {
317
+ if (this.buffer.length > 8192) throw new JsonRpcFrameError("header exceeds limit");
318
+ break;
319
+ }
320
+ const header = this.buffer.subarray(0, headerEnd).toString("ascii");
321
+ const length = this.parseLength(header);
322
+ const bodyStart = headerEnd + 4;
323
+ if (length > this.maxMessageBytes) throw new JsonRpcFrameError("message exceeds configured limit");
324
+ if (this.buffer.length < bodyStart + length) break;
325
+ const body = this.buffer.subarray(bodyStart, bodyStart + length);
326
+ this.buffer = this.buffer.subarray(bodyStart + length);
327
+ let text;
328
+ try {
329
+ text = this.decoder.decode(body);
330
+ } catch {
331
+ throw new JsonRpcFrameError("message is not valid UTF-8");
332
+ }
333
+ try {
334
+ messages.push(JSON.parse(text));
335
+ } catch {
336
+ throw new JsonRpcFrameError("message body is not valid JSON");
337
+ }
338
+ }
339
+ return messages;
340
+ }
341
+ end() {
342
+ if (this.buffer.length > 0) throw new JsonRpcFrameError("incomplete JSON-RPC frame");
343
+ }
344
+ parseLength(header) {
345
+ const lines = header.split("\r\n");
346
+ let length;
347
+ for (const line of lines) {
348
+ const separator = line.indexOf(":");
349
+ if (separator < 1) throw new JsonRpcFrameError("malformed header");
350
+ const key = line.slice(0, separator).toLowerCase();
351
+ const value = line.slice(separator + 1).trim();
352
+ if (key === "content-length") {
353
+ if (length !== void 0 || !/^\d+$/.test(value)) throw new JsonRpcFrameError("invalid Content-Length");
354
+ length = Number(value);
355
+ }
356
+ }
357
+ if (length === void 0 || !Number.isSafeInteger(length)) throw new JsonRpcFrameError("Content-Length is required");
358
+ return length;
359
+ }
360
+ };
361
+ function encodeJsonRpc(message) {
362
+ const body = Buffer.from(JSON.stringify(message), "utf8");
363
+ return Buffer.concat([Buffer.from(`Content-Length: ${body.length}\r\n\r\n`, "ascii"), body]);
364
+ }
365
+
366
+ //#endregion
367
+ //#region src/runtime/connection.ts
368
+ var JsonRpcConnection = class {
369
+ decoder;
370
+ pending = /* @__PURE__ */ new Map();
371
+ notificationListeners = /* @__PURE__ */ new Set();
372
+ nextId = 1;
373
+ closed = false;
374
+ requestHandler;
375
+ constructor(input, output, options) {
376
+ this.input = input;
377
+ this.output = output;
378
+ this.decoder = new ContentLengthDecoder(options.maxMessageBytes);
379
+ this.requestHandler = options.onServerRequest ?? (() => null);
380
+ if (options.onNotification) this.notificationListeners.add(options.onNotification);
381
+ input.on("data", (chunk) => this.receive(chunk));
382
+ input.once("end", () => this.close(/* @__PURE__ */ new Error("LSP process closed stdout")));
383
+ input.once("error", (error) => this.close(error));
384
+ output.on("error", (error) => this.close(error));
385
+ }
386
+ get isOpen() {
387
+ return !this.closed;
388
+ }
389
+ onNotification(listener) {
390
+ this.notificationListeners.add(listener);
391
+ return () => this.notificationListeners.delete(listener);
392
+ }
393
+ setRequestHandler(handler) {
394
+ this.requestHandler = handler;
395
+ }
396
+ async request(method, params, signal) {
397
+ throwIfAborted(signal);
398
+ if (this.closed) throw new Error("connection is closed");
399
+ const id = this.nextId++;
400
+ const promise = new Promise((resolve, reject) => this.pending.set(id, {
401
+ resolve,
402
+ reject,
403
+ method
404
+ }));
405
+ promise.catch(() => {});
406
+ const remove = onAbort(signal, () => {
407
+ this.notify("$/cancelRequest", { id }).catch(() => {});
408
+ this.pending.get(id)?.reject(abortError());
409
+ this.pending.delete(id);
410
+ });
411
+ try {
412
+ this.write({
413
+ jsonrpc: "2.0",
414
+ id,
415
+ method,
416
+ ...params === void 0 ? {} : { params }
417
+ }).catch((error) => this.close(error));
418
+ return await promise;
419
+ } finally {
420
+ remove();
421
+ this.pending.delete(id);
422
+ }
423
+ }
424
+ async notify(method, params) {
425
+ if (this.closed) throw new Error("connection is closed");
426
+ await this.write({
427
+ jsonrpc: "2.0",
428
+ method,
429
+ ...params === void 0 ? {} : { params }
430
+ });
431
+ }
432
+ close(reason = /* @__PURE__ */ new Error("connection closed")) {
433
+ if (this.closed) return;
434
+ this.closed = true;
435
+ for (const pending of this.pending.values()) pending.reject(reason);
436
+ this.pending.clear();
437
+ }
438
+ receive(chunk) {
439
+ if (this.closed) return;
440
+ let messages;
441
+ try {
442
+ messages = this.decoder.feed(chunk);
443
+ } catch (error) {
444
+ this.close(error instanceof Error ? error : new JsonRpcFrameError("invalid frame"));
445
+ return;
446
+ }
447
+ for (const message of messages) this.dispatch(message);
448
+ }
449
+ dispatch(value) {
450
+ if (!isRecord$4(value) || value.jsonrpc !== "2.0") return this.close(new JsonRpcFrameError("invalid JSON-RPC message"));
451
+ if ("method" in value && typeof value.method === "string") {
452
+ if ("id" in value) {
453
+ const request = value;
454
+ Promise.resolve().then(() => this.requestHandler(request.method, request.params)).then((result) => this.write({
455
+ jsonrpc: "2.0",
456
+ id: request.id,
457
+ result
458
+ }), (error) => this.write({
459
+ jsonrpc: "2.0",
460
+ id: request.id,
461
+ error: {
462
+ code: -32603,
463
+ message: error instanceof Error ? error.message : "server request failed"
464
+ }
465
+ })).catch(() => {});
466
+ } else for (const listener of this.notificationListeners) listener(value);
467
+ return;
468
+ }
469
+ if ("id" in value) {
470
+ const response = value;
471
+ const pending = this.pending.get(response.id);
472
+ if (!pending) return;
473
+ if (response.error) pending.reject(/* @__PURE__ */ new Error(`${response.error.code}: ${response.error.message}`));
474
+ else pending.resolve(response.result);
475
+ this.pending.delete(response.id);
476
+ }
477
+ }
478
+ write(message) {
479
+ return new Promise((resolve, reject) => {
480
+ this.output.write(encodeJsonRpc(message), (error) => error ? reject(error) : resolve());
481
+ });
482
+ }
483
+ };
484
+ function isRecord$4(value) {
485
+ return typeof value === "object" && value !== null && !Array.isArray(value);
486
+ }
487
+
488
+ //#endregion
489
+ //#region src/runtime/server-session.ts
490
+ var ServerSession = class {
491
+ state = "NEW";
492
+ capabilities = {};
493
+ child;
494
+ connection;
495
+ stopping;
496
+ lifetime = new AbortController();
497
+ constructor(options) {
498
+ this.options = options;
499
+ }
500
+ async start(signal) {
501
+ throwIfAborted(signal);
502
+ this.state = "STARTING";
503
+ try {
504
+ const child = this.options.spawn();
505
+ this.child = child;
506
+ child.done.catch(() => {});
507
+ if (!child.stdout || !child.stdin) throw new Error("server stdio is unavailable");
508
+ const connection = new JsonRpcConnection(child.stdout, child.stdin, {
509
+ maxMessageBytes: this.options.maxMessageBytes,
510
+ onServerRequest: (method, params) => this.answer(method, params)
511
+ });
512
+ this.connection = connection;
513
+ child.done.then(() => connection.close(/* @__PURE__ */ new Error("LSP process exited")), (error) => connection.close(error instanceof Error ? error : /* @__PURE__ */ new Error("LSP spawn failed"))).then(() => {
514
+ if (this.state !== "STOPPING" && this.state !== "STOPPED") this.state = "FAILED";
515
+ });
516
+ const result = await initializeServer(connection, this.options.initialize, this.signal(signal));
517
+ if (result.capabilities.positionEncoding && result.capabilities.positionEncoding !== "utf-16") throw new Error("server requires unsupported position encoding");
518
+ const sync = result.capabilities.textDocumentSync;
519
+ if (sync === void 0 || sync === 0 || typeof sync === "object" && sync !== null && !sync.openClose) throw new Error("server does not support document open/close");
520
+ this.capabilities = result.capabilities;
521
+ await connection.notify("workspace/didChangeConfiguration", { settings: this.options.configuration });
522
+ throwIfAborted(this.signal(signal));
523
+ this.state = "READY";
524
+ return result;
525
+ } catch (error) {
526
+ this.state = "FAILED";
527
+ await this.stop();
528
+ throw error;
529
+ }
530
+ }
531
+ get usable() {
532
+ return this.state === "READY" && !!this.connection?.isOpen;
533
+ }
534
+ isUsable() {
535
+ return this.usable;
536
+ }
537
+ async run(task, signal) {
538
+ if (!this.usable || !this.connection) throw new Error("session is not ready");
539
+ try {
540
+ return await withAbort(task(this.connection), this.signal(signal));
541
+ } catch (error) {
542
+ if (signal?.aborted || !this.connection.isOpen) await this.stop();
543
+ throw error;
544
+ }
545
+ }
546
+ stop() {
547
+ return this.stopping ??= this.teardown();
548
+ }
549
+ async teardown() {
550
+ this.state = "STOPPING";
551
+ this.lifetime.abort();
552
+ const child = this.child, connection = this.connection;
553
+ const timeout = AbortSignal.timeout(this.options.shutdown.shutdownMs);
554
+ if (connection?.isOpen) try {
555
+ await connection.request("shutdown", null, timeout);
556
+ await connection.notify("exit");
557
+ } catch {}
558
+ connection?.close(/* @__PURE__ */ new Error("session stopped"));
559
+ if (child) {
560
+ child.terminate();
561
+ if (!await child.waitForExit(AbortSignal.timeout(this.options.shutdown.killGraceMs + 1e3))) throw new Error("language server process tree did not exit within the cleanup deadline");
562
+ }
563
+ this.state = "STOPPED";
564
+ }
565
+ signal(signal) {
566
+ return signal ? AbortSignal.any([signal, this.lifetime.signal]) : this.lifetime.signal;
567
+ }
568
+ answer(method, params) {
569
+ if (method === "workspace/applyEdit") return {
570
+ applied: false,
571
+ failureReason: "read-only provider"
572
+ };
573
+ if (method === "workspace/workspaceFolders") return this.options.initialize.workspaceFolders ?? [];
574
+ if (method === "workspace/configuration") return (params?.items ?? []).map((item) => {
575
+ let value = this.options.configuration;
576
+ for (const key of item.section?.split(".") ?? []) value = typeof value === "object" && value !== null ? value[key] : void 0;
577
+ return value ?? null;
578
+ });
579
+ return null;
580
+ }
581
+ };
582
+
583
+ //#endregion
584
+ //#region src/runtime/server-pool.ts
585
+ var ServerPool = class {
586
+ sessions = /* @__PURE__ */ new Map();
587
+ creation = /* @__PURE__ */ new Map();
588
+ queues = /* @__PURE__ */ new Map();
589
+ disposing = false;
590
+ constructor(factory) {
591
+ this.factory = factory;
592
+ }
593
+ async getOrCreate(serverId, workspace, signal) {
594
+ throwIfAborted(signal);
595
+ if (this.disposing) throw new Error("server pool is disposing");
596
+ const key = keyOf(serverId, workspace);
597
+ const existing = this.sessions.get(key);
598
+ if (existing?.isUsable()) return existing;
599
+ const pending = this.creation.get(key);
600
+ if (pending) return withAbort(pending, signal);
601
+ const creation = this.create(key, serverId, workspace, existing, signal);
602
+ this.creation.set(key, creation);
603
+ try {
604
+ return await creation;
605
+ } finally {
606
+ if (this.creation.get(key) === creation) this.creation.delete(key);
607
+ }
608
+ }
609
+ run(serverId, workspace, task, signal) {
610
+ return this.serialize(keyOf(serverId, workspace), async () => task(await this.getOrCreate(serverId, workspace, signal)), signal);
611
+ }
612
+ runWithRetry(serverId, workspace, task, signal) {
613
+ return this.serialize(keyOf(serverId, workspace), async () => {
614
+ for (let attempt = 0;; attempt++) {
615
+ const session = await this.getOrCreate(serverId, workspace, signal);
616
+ try {
617
+ return await task(session);
618
+ } catch (error) {
619
+ throwIfAborted(signal);
620
+ if (attempt > 0 || session.isUsable()) throw error;
621
+ if (this.sessions.get(keyOf(serverId, workspace)) === session) await this.evict(serverId, workspace);
622
+ }
623
+ }
624
+ }, signal);
625
+ }
626
+ async evict(serverId, workspace) {
627
+ const key = keyOf(serverId, workspace);
628
+ const session = this.sessions.get(key);
629
+ this.sessions.delete(key);
630
+ if (session) await session.stop();
631
+ }
632
+ async disposeAll() {
633
+ this.disposing = true;
634
+ const results = await Promise.allSettled([...this.sessions.values()].map((session) => session.stop()));
635
+ this.sessions.clear();
636
+ await Promise.all([...this.creation.values()].map((creation) => creation.then((session) => session.stop(), () => {})));
637
+ this.creation.clear();
638
+ const failures = results.filter((result) => result.status === "rejected");
639
+ if (failures.length) throw new AggregateError(failures.map((result) => result.reason), "language server cleanup failed");
640
+ }
641
+ get size() {
642
+ return this.sessions.size;
643
+ }
644
+ async serialize(key, task, signal) {
645
+ const previous = this.queues.get(key) ?? Promise.resolve();
646
+ let release;
647
+ const current = new Promise((resolve) => {
648
+ release = resolve;
649
+ });
650
+ this.queues.set(key, current);
651
+ try {
652
+ await withAbort(previous, signal);
653
+ throwIfAborted(signal);
654
+ return await task();
655
+ } finally {
656
+ previous.then(release, release);
657
+ current.then(() => {
658
+ if (this.queues.get(key) === current) this.queues.delete(key);
659
+ });
660
+ }
661
+ }
662
+ async create(key, serverId, workspace, previous, signal) {
663
+ if (previous) {
664
+ this.sessions.delete(key);
665
+ await previous.stop();
666
+ }
667
+ throwIfAborted(signal);
668
+ const session = await this.factory(serverId, workspace, signal);
669
+ try {
670
+ await session.start(signal);
671
+ } catch (error) {
672
+ await session.stop();
673
+ throw error;
674
+ }
675
+ if (this.disposing) {
676
+ await session.stop();
677
+ throw new Error("server pool is disposing");
678
+ }
679
+ this.sessions.set(key, session);
680
+ return session;
681
+ }
682
+ };
683
+ function keyOf(serverId, workspace) {
684
+ return `${serverId}\u0000${workspace}`;
685
+ }
686
+
687
+ //#endregion
688
+ //#region src/protocol/capabilities.ts
689
+ function supportsOperation(capabilities, operation) {
690
+ if (operation === "goToDefinition") return capabilities.definitionProvider === true || isProvider(capabilities.definitionProvider);
691
+ if (operation === "findReferences") return capabilities.referencesProvider === true || isProvider(capabilities.referencesProvider);
692
+ if (operation === "goToImplementation") return capabilities.implementationProvider === true || isProvider(capabilities.implementationProvider);
693
+ return capabilities.hoverProvider === true || isProvider(capabilities.hoverProvider);
694
+ }
695
+ function isProvider(value) {
696
+ return typeof value === "object" && value !== null || value === true;
697
+ }
698
+
699
+ //#endregion
700
+ //#region src/protocol/normalize-hover.ts
701
+ function normalizeHover(value) {
702
+ if (value === null || value === void 0) return null;
703
+ if (typeof value !== "object" || value === null) return void 0;
704
+ const record = value;
705
+ const contents = normalizeContents(record.contents);
706
+ if (contents === void 0) return void 0;
707
+ return {
708
+ contents,
709
+ ...isRange$2(record.range) ? { range: record.range } : {}
710
+ };
711
+ }
712
+ function normalizeContents(value) {
713
+ if (typeof value === "string") return value;
714
+ if (Array.isArray(value)) {
715
+ const parts = value.map((item) => typeof item === "string" ? item : isRecord$3(item) && typeof item.value === "string" ? item.value : void 0);
716
+ return parts.every((item) => item !== void 0) ? parts.join("\n") : void 0;
717
+ }
718
+ if (isRecord$3(value) && typeof value.value === "string") return value.value;
719
+ }
720
+ function isRange$2(value) {
721
+ return isRecord$3(value) && isPosition$2(value.start) && isPosition$2(value.end);
722
+ }
723
+ function isPosition$2(value) {
724
+ return isRecord$3(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
725
+ }
726
+ function isRecord$3(value) {
727
+ return typeof value === "object" && value !== null && !Array.isArray(value);
728
+ }
729
+
730
+ //#endregion
731
+ //#region src/protocol/normalize-location.ts
732
+ function normalizeLocation(value) {
733
+ if (isRecord$2(value) && typeof value.targetUri === "string" && isRange$1(value.targetSelectionRange)) return {
734
+ uri: value.targetUri,
735
+ range: value.targetSelectionRange
736
+ };
737
+ if (!isRecord$2(value) || typeof value.uri !== "string" || !isRange$1(value.range)) return void 0;
738
+ return {
739
+ uri: value.uri,
740
+ range: value.range
741
+ };
742
+ }
743
+ function normalizeLocations(value) {
744
+ return (Array.isArray(value) ? value : value === null || value === void 0 ? [] : [value]).map(normalizeLocation).filter((location) => location !== void 0);
745
+ }
746
+ function isRange$1(value) {
747
+ return isRecord$2(value) && isPosition$1(value.start) && isPosition$1(value.end);
748
+ }
749
+ function isPosition$1(value) {
750
+ return isRecord$2(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
751
+ }
752
+ function isRecord$2(value) {
753
+ return typeof value === "object" && value !== null && !Array.isArray(value);
754
+ }
755
+
756
+ //#endregion
757
+ //#region src/protocol/normalize-diagnostic.ts
758
+ function normalizeDiagnostic(value, server, fallbackUri) {
759
+ if (!isRecord$1(value) || !isRange(value.range) || typeof value.message !== "string") return void 0;
760
+ const uri = typeof value.uri === "string" ? value.uri : fallbackUri;
761
+ const severity = typeof value.severity === "number" && value.severity >= 1 && value.severity <= 4 ? value.severity : void 0;
762
+ const code = typeof value.code === "string" || typeof value.code === "number" ? value.code : void 0;
763
+ return {
764
+ server,
765
+ uri,
766
+ range: value.range,
767
+ message: value.message,
768
+ ...severity === void 0 ? {} : { severity },
769
+ ...code === void 0 ? {} : { code },
770
+ ...typeof value.source === "string" ? { source: value.source } : {}
771
+ };
772
+ }
773
+ function normalizeDiagnostics(value, server, fallbackUri) {
774
+ return (Array.isArray(value) ? value : []).map((item) => normalizeDiagnostic(item, server, fallbackUri)).filter((item) => item !== void 0);
775
+ }
776
+ function isRange(value) {
777
+ return isRecord$1(value) && isPosition(value.start) && isPosition(value.end);
778
+ }
779
+ function isPosition(value) {
780
+ return isRecord$1(value) && Number.isInteger(value.line) && Number.isInteger(value.character) && value.line >= 0 && value.character >= 0;
781
+ }
782
+ function isRecord$1(value) {
783
+ return typeof value === "object" && value !== null && !Array.isArray(value);
784
+ }
785
+
786
+ //#endregion
787
+ //#region src/protocol/normalize-completion.ts
788
+ function normalizeCompletionItem(value, server) {
789
+ if (!isRecord(value) || typeof value.label !== "string" || value.label.length === 0) return void 0;
790
+ return {
791
+ server,
792
+ label: value.label,
793
+ ...typeof value.kind === "number" ? { kind: value.kind } : {},
794
+ ...typeof value.detail === "string" ? { detail: value.detail } : {},
795
+ ...typeof value.documentation === "string" ? { documentation: value.documentation } : {},
796
+ ...typeof value.sortText === "string" ? { sortText: value.sortText } : {},
797
+ ...typeof value.filterText === "string" ? { filterText: value.filterText } : {},
798
+ ...typeof value.insertText === "string" ? { insertText: value.insertText } : {},
799
+ ...typeof value.insertTextFormat === "number" ? { insertTextFormat: value.insertTextFormat } : {},
800
+ ...typeof value.deprecated === "boolean" ? { deprecated: value.deprecated } : {}
801
+ };
802
+ }
803
+ function normalizeCompletion(value, server) {
804
+ return (isRecord(value) && Array.isArray(value.items) ? value.items : Array.isArray(value) ? value : []).map((item) => normalizeCompletionItem(item, server)).filter((item) => item !== void 0);
805
+ }
806
+ function isRecord(value) {
807
+ return typeof value === "object" && value !== null && !Array.isArray(value);
808
+ }
809
+
810
+ //#endregion
811
+ //#region src/merge/hover.ts
812
+ function mergeHover(primary, auxiliary) {
813
+ if (!primary && !auxiliary) return null;
814
+ if (!primary) return auxiliary ? { ...auxiliary } : null;
815
+ if (!auxiliary) return { ...primary };
816
+ const contents = [primary.contents, auxiliary.contents].filter((value, index, values) => values.indexOf(value) === index);
817
+ const range = sameRange(primary.range, auxiliary.range) ? primary.range : void 0;
818
+ return {
819
+ server: primary.server,
820
+ contents: contents.join("\n\n"),
821
+ ...range ? { range } : {}
822
+ };
823
+ }
824
+ function sameRange(left, right) {
825
+ return JSON.stringify(left) === JSON.stringify(right);
826
+ }
827
+
828
+ //#endregion
829
+ //#region src/runtime/webstack-runtime.ts
830
+ const methods = {
831
+ hover: "textDocument/hover",
832
+ goToDefinition: "textDocument/definition",
833
+ findReferences: "textDocument/references",
834
+ goToImplementation: "textDocument/implementation"
835
+ };
836
+ const clientCapabilities = {
837
+ general: { positionEncodings: ["utf-16"] },
838
+ workspace: {
839
+ configuration: true,
840
+ workspaceFolders: true,
841
+ applyEdit: false
842
+ },
843
+ textDocument: {
844
+ synchronization: {
845
+ dynamicRegistration: false,
846
+ didSave: false
847
+ },
848
+ hover: { contentFormat: ["markdown", "plaintext"] },
849
+ completion: { completionItem: {
850
+ snippetSupport: true,
851
+ documentationFormat: ["markdown", "plaintext"]
852
+ } },
853
+ publishDiagnostics: { versionSupport: true },
854
+ diagnostic: {}
855
+ }
856
+ };
857
+ var WebstackLspRuntime = class {
858
+ pool;
859
+ workspaces = /* @__PURE__ */ new Map();
860
+ lifetime = new AbortController();
861
+ live = /* @__PURE__ */ new Map();
862
+ starts = /* @__PURE__ */ new Map();
863
+ restarts = /* @__PURE__ */ new Map();
864
+ constructor(config, fs, subprocess) {
865
+ this.config = config;
866
+ this.fs = fs;
867
+ this.subprocess = subprocess;
868
+ this.pool = new ServerPool(async (rawId, key, signal) => {
869
+ const id = rawId, source = this.workspaces.get(key);
870
+ const spec = await resolveLaunch(id, config, source.workspace);
871
+ let executable;
872
+ try {
873
+ executable = await subprocess.resolveExecutable(spec.command, spec.env, signal);
874
+ } catch {
875
+ throwIfAborted(signal);
876
+ throw new WebstackLspError("LSP_UNAVAILABLE", `${id} executable is unavailable; run dsh-lsp-webstack --json`);
877
+ }
878
+ const session = new ServerSession({
879
+ spawn: () => subprocess.spawn({
880
+ argv: [executable, ...spec.args],
881
+ cwd: source.workspace,
882
+ env: spec.env,
883
+ stdio: {
884
+ stdin: "pipe",
885
+ stdout: "pipe",
886
+ stderr: { maxBytes: config.limits.maxStderrBytes }
887
+ },
888
+ graceMs: config.timeouts.killGraceMs
889
+ }),
890
+ initialize: {
891
+ processId: null,
892
+ rootUri: source.workspaceUri,
893
+ workspaceFolders: [{
894
+ uri: source.workspaceUri,
895
+ name: "workspace"
896
+ }],
897
+ capabilities: clientCapabilities,
898
+ initializationOptions: initializationOptions(id, config)
899
+ },
900
+ configuration: serverConfiguration(id, config),
901
+ maxMessageBytes: config.limits.maxMessageBytes,
902
+ shutdown: {
903
+ shutdownMs: config.timeouts.shutdownMs,
904
+ killGraceMs: config.timeouts.killGraceMs
905
+ }
906
+ });
907
+ const slot = `${id}\u0000${key}`;
908
+ if (this.starts.has(slot)) this.restarts.set(id, (this.restarts.get(id) ?? 0) + 1);
909
+ this.starts.set(slot, (this.starts.get(slot) ?? 0) + 1);
910
+ for (const [old] of this.live) if (old.state === "STOPPED" || old.state === "FAILED") this.live.delete(old);
911
+ this.live.set(session, id);
912
+ return session;
913
+ });
914
+ }
915
+ async navigation(request, signal) {
916
+ const deadline = this.signal(request.operation === "hover" ? this.config.timeouts.primaryHoverMs : this.config.timeouts.navigationMs, signal);
917
+ const [source, route] = await this.prepare(request, deadline);
918
+ this.validatePosition(source.text, request.position);
919
+ const result = await this.document(route.primary.server, source, route.primary.languageId, deadline, async (session, connection) => {
920
+ if (!supportsOperation(session.capabilities, request.operation)) throw new WebstackLspError("LSP_UNSUPPORTED_OPERATION", `${route.primary.server} does not support ${request.operation}`);
921
+ return connection.request(methods[request.operation], {
922
+ textDocument: { uri: source.uri },
923
+ position: request.position,
924
+ ...request.operation === "findReferences" ? { context: { includeDeclaration: true } } : {}
925
+ }, deadline);
926
+ });
927
+ if (request.operation !== "hover") return {
928
+ kind: "locations",
929
+ locations: normalizeLocations(result),
930
+ resolvedWorkspaceUri: source.workspaceUri
931
+ };
932
+ const primary = normalizeHover(result);
933
+ if (primary === void 0) throw new WebstackLspError("LSP_MALFORMED_RESPONSE", "invalid hover result");
934
+ let auxiliary = null;
935
+ if (this.useTailwind(route, source, request.position)) {
936
+ const auxSignal = this.signal(this.config.timeouts.auxiliaryHoverMs, signal);
937
+ try {
938
+ const value = await this.document("tailwind", source, route.auxiliary.languageId, auxSignal, (_session, connection) => connection.request("textDocument/hover", {
939
+ textDocument: { uri: source.uri },
940
+ position: request.position
941
+ }, auxSignal));
942
+ auxiliary = normalizeHover(value) ?? null;
943
+ } catch {
944
+ throwIfAborted(signal);
945
+ throwIfAborted(this.lifetime.signal);
946
+ }
947
+ }
948
+ const hover = mergeHover(primary && {
949
+ ...primary,
950
+ server: route.primary.server
951
+ }, auxiliary && {
952
+ ...auxiliary,
953
+ server: "tailwind"
954
+ });
955
+ return {
956
+ kind: "hover",
957
+ hover: hover ? {
958
+ contents: hover.contents,
959
+ ...hover.range ? { range: hover.range } : {}
960
+ } : null
961
+ };
962
+ }
963
+ async completion(request, signal) {
964
+ const deadline = this.signal(this.config.timeouts.completionMs, signal);
965
+ const [source, route] = await this.prepare(request, deadline);
966
+ this.validatePosition(source.text, request.position);
967
+ const query = (id, language) => this.document(id, source, language, deadline, (session, connection) => {
968
+ if (!session.capabilities.completionProvider) throw new WebstackLspError("LSP_UNSUPPORTED_OPERATION", `${id} does not support completion`);
969
+ return connection.request("textDocument/completion", {
970
+ textDocument: { uri: source.uri },
971
+ position: request.position,
972
+ context: {
973
+ triggerKind: request.triggerKind ?? 1,
974
+ ...request.triggerCharacter ? { triggerCharacter: request.triggerCharacter } : {}
975
+ }
976
+ }, deadline);
977
+ }).then((value) => {
978
+ const prefix = source.text.split(/\r?\n/)[request.position.line].slice(0, request.position.character).match(/[\w$:@/!\[\]-]+$/)?.[0]?.toLowerCase() ?? "";
979
+ return normalizeCompletion(value, id).sort((a, b) => Number((b.filterText ?? b.label).toLowerCase().startsWith(prefix)) - Number((a.filterText ?? a.label).toLowerCase().startsWith(prefix)));
980
+ });
981
+ const primary = await query(route.primary.server, route.primary.languageId);
982
+ let auxiliary = [];
983
+ if (this.useTailwind(route, source, request.position)) try {
984
+ auxiliary = await query("tailwind", route.auxiliary.languageId);
985
+ } catch {
986
+ throwIfAborted(signal);
987
+ throwIfAborted(this.lifetime.signal);
988
+ }
989
+ return mergeCompletions([primary, auxiliary], {
990
+ maxCompletions: Number.MAX_SAFE_INTEGER,
991
+ maxResultChars: Number.MAX_SAFE_INTEGER
992
+ });
993
+ }
994
+ async diagnostics(request, signal) {
995
+ const deadline = this.signal(this.config.timeouts.diagnosticsMs, signal);
996
+ const [source, route] = await this.prepare(request, deadline);
997
+ 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)));
998
+ if (!selected.length) throw new WebstackLspError("LSP_UNAVAILABLE", "no selected diagnostic server supports this file");
999
+ const omittedServers = [];
1000
+ const groups = await Promise.all(selected.map(async (item) => {
1001
+ try {
1002
+ const value = await this.document(item.server, source, item.languageId, deadline, async (session, connection, opened) => {
1003
+ if (session.capabilities.diagnosticProvider) {
1004
+ await opened();
1005
+ return (await connection.request("textDocument/diagnostic", { textDocument: { uri: source.uri } }, deadline))?.items ?? [];
1006
+ }
1007
+ let remove = () => {};
1008
+ const published = new Promise((resolve) => {
1009
+ remove = connection.onNotification((message) => {
1010
+ const p = message.params;
1011
+ if (message.method === "textDocument/publishDiagnostics" && p?.uri === source.uri && (p.version === void 0 || p.version === 1)) resolve(p.diagnostics);
1012
+ });
1013
+ });
1014
+ try {
1015
+ await opened();
1016
+ return await withAbort(published, deadline);
1017
+ } finally {
1018
+ remove();
1019
+ }
1020
+ }, true);
1021
+ return normalizeDiagnostics(value, item.server, source.uri).filter((d) => !request.severity || request.severity === "all" || d.severity === (request.severity === "error" ? 1 : 2));
1022
+ } catch (error) {
1023
+ throwIfAborted(signal);
1024
+ throwIfAborted(this.lifetime.signal);
1025
+ if (item.server === route.primary.server || this.config.missingServerPolicy === "error") throw error;
1026
+ omittedServers.push(item.server);
1027
+ return [];
1028
+ }
1029
+ }));
1030
+ return {
1031
+ ...mergeDiagnostics(groups, {
1032
+ maxDiagnostics: Number.MAX_SAFE_INTEGER,
1033
+ maxResultChars: Number.MAX_SAFE_INTEGER
1034
+ }),
1035
+ ...omittedServers.length ? { omittedServers: omittedServers.sort() } : {}
1036
+ };
1037
+ }
1038
+ async status() {
1039
+ return {
1040
+ providerId: this.config.providerId,
1041
+ servers: await Promise.all(SERVER_IDS.map(async (id) => {
1042
+ const config = this.config.servers[id];
1043
+ let available = false;
1044
+ try {
1045
+ const launch = await resolveLaunch(id, this.config, ".");
1046
+ await this.subprocess.resolveExecutable(launch.command, launch.env, this.signal(5e3));
1047
+ available = true;
1048
+ } catch {}
1049
+ return {
1050
+ id,
1051
+ enabled: config.enabled && (id !== "tailwind" || this.config.tailwind.enabled),
1052
+ available,
1053
+ source: config.mode,
1054
+ liveWorkspaces: [...this.live].filter(([s, server]) => server === id && s.isUsable()).length,
1055
+ restarts: this.restarts.get(id) ?? 0
1056
+ };
1057
+ }))
1058
+ };
1059
+ }
1060
+ async dispose() {
1061
+ this.lifetime.abort();
1062
+ await this.pool.disposeAll();
1063
+ this.workspaces.clear();
1064
+ this.live.clear();
1065
+ }
1066
+ signal(ms, signal) {
1067
+ return AbortSignal.any([
1068
+ this.lifetime.signal,
1069
+ AbortSignal.timeout(ms),
1070
+ ...signal ? [signal] : []
1071
+ ]);
1072
+ }
1073
+ async prepare(request, signal) {
1074
+ throwIfAborted(signal);
1075
+ const file = classifyFile(request.filePath, this.config.routing.bladeSuffixes);
1076
+ if (!file) throw new WebstackLspError("LSP_UNAVAILABLE", "unsupported file extension");
1077
+ const source = await readSource(this.fs, request.workspaceRoot, request.filePath, this.config.limits.maxDocumentBytes, signal).catch((error) => {
1078
+ throwIfAborted(signal);
1079
+ throw error;
1080
+ });
1081
+ this.workspaces.set(source.key, {
1082
+ ...source,
1083
+ text: ""
1084
+ });
1085
+ return [source, routeFor(file)];
1086
+ }
1087
+ validatePosition(text, position) {
1088
+ const line = text.split(/\r?\n/)[position.line];
1089
+ if (!Number.isSafeInteger(position.line) || !Number.isSafeInteger(position.character) || position.line < 0 || position.character < 0 || line === void 0 || position.character > line.length) throw new RangeError("position is outside document (zero-based UTF-16 required)");
1090
+ }
1091
+ useTailwind(route, source, position) {
1092
+ return !!route.auxiliary && this.config.tailwind.enabled && this.config.servers.tailwind.enabled && (route.primary.languageId === "blade" || this.config.routing.tailwindLanguages.includes(route.primary.languageId) || route.primary.languageId === "css") && (this.config.tailwind.queryStrategy === "always" || hasTailwindCandidate(source.text, position.line, this.config.tailwind.classRegex, position.character, this.config.tailwind.classAttributes));
1093
+ }
1094
+ async document(id, source, language, signal, task, manualOpen = false) {
1095
+ if (!this.config.servers[id].enabled) throw new WebstackLspError("LSP_UNAVAILABLE", `${id} is disabled`);
1096
+ return this.pool.runWithRetry(id, source.key, (session) => session.run(async (connection) => {
1097
+ let opened = false;
1098
+ const open = async () => {
1099
+ opened = true;
1100
+ await connection.notify("textDocument/didOpen", { textDocument: {
1101
+ uri: source.uri,
1102
+ languageId: language,
1103
+ version: 1,
1104
+ text: source.text
1105
+ } });
1106
+ };
1107
+ try {
1108
+ if (!manualOpen) await open();
1109
+ return await task(session, connection, open);
1110
+ } finally {
1111
+ if (opened && connection.isOpen) await connection.notify("textDocument/didClose", { textDocument: { uri: source.uri } }).catch(() => {});
1112
+ }
1113
+ }, signal), signal);
1114
+ }
1115
+ };
1116
+
1117
+ //#endregion
1118
+ //#region src/provider.ts
1119
+ const name = "lsp-webstack-provider";
1120
+ const inject = [
1121
+ "fs",
1122
+ "lsp",
1123
+ "subprocess"
1124
+ ];
1125
+ const OUTER_EXTENSION_MAP = {
1126
+ ".php": "php",
1127
+ ".ts": "typescript",
1128
+ ".tsx": "typescriptreact",
1129
+ ".mts": "typescript",
1130
+ ".cts": "typescript",
1131
+ ".js": "javascript",
1132
+ ".jsx": "javascriptreact",
1133
+ ".mjs": "javascript",
1134
+ ".cjs": "javascript",
1135
+ ".svelte": "svelte",
1136
+ ".html": "html",
1137
+ ".htm": "html",
1138
+ ".css": "css",
1139
+ ".pcss": "css"
1140
+ };
1141
+ function createCompositeProvider(runtime, config = { providerId: "webstack" }) {
1142
+ return {
1143
+ id: LspProviderId(config.providerId),
1144
+ extensionToLanguage: { ...OUTER_EXTENSION_MAP },
1145
+ query: (request, signal) => runtime.navigation(request, signal)
1146
+ };
1147
+ }
1148
+ async function apply(ctx, config = {}) {
1149
+ const resolved = resolveConfig(config);
1150
+ const runtime = new WebstackLspRuntime(resolved, ctx.fs, ctx.subprocess);
1151
+ await ctx.effect(async function* () {
1152
+ yield () => runtime.dispose();
1153
+ if (resolved.missingServerPolicy === "error") {
1154
+ const missing = (await runtime.status()).servers.filter((server) => server.enabled && !server.available);
1155
+ if (missing.length) throw new Error(`unavailable language servers: ${missing.map((server) => server.id).join(", ")}`);
1156
+ }
1157
+ yield ctx.lsp.registerProvider(createCompositeProvider(runtime, resolved));
1158
+ yield ctx.provide("webstackLsp", createWebstackLspService(runtime));
1159
+ }, "webstack LSP provider");
1160
+ }
1161
+
1162
+ //#endregion
1163
+ export { OUTER_EXTENSION_MAP, apply, createCompositeProvider, inject, name };
1164
+ //# sourceMappingURL=provider.js.map