@staticbolt/lsp 1.0.0-beta.12

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/lib/index.mjs ADDED
@@ -0,0 +1,903 @@
1
+ import * as vs from "vscode-languageserver/node.js";
2
+ import * as vscode$1 from "vscode-languageserver";
3
+ import vscode, { RequestType } from "vscode-languageserver";
4
+ import { TextDocument } from "vscode-languageserver-textdocument";
5
+ import * as vscodeUri from "vscode-uri";
6
+ import { EventEmitter } from "node:events";
7
+ import * as fs from "node:fs";
8
+ import { globSync, watch } from "node:fs";
9
+ import { access, constants } from "node:fs/promises";
10
+ import * as path$1 from "node:path";
11
+ import path, { join } from "node:path";
12
+ import { pathToFileURL } from "node:url";
13
+ import vscodeHtml from "vscode-html-languageservice";
14
+ import { Resolver } from "@staticbolt/core";
15
+
16
+ //#region src/helpers/config-loader.ts
17
+ const CONFIG_CANDIDATES = [".staticbolt.ts", ".staticbolt.js"];
18
+ async function findConfigFile(workspaceRoot) {
19
+ for (const name of CONFIG_CANDIDATES) {
20
+ const full = join(workspaceRoot, name);
21
+ try {
22
+ await access(full, constants.R_OK);
23
+ return full;
24
+ } catch {}
25
+ }
26
+ return null;
27
+ }
28
+ async function importFresh(absolutePath) {
29
+ const module_ = await import(`${pathToFileURL(absolutePath).href}?t=${Date.now()}`);
30
+ if (!("default" in module_)) throw new Error(`Config file "${absolutePath}" has no default export. Use \`export default { … }\`.`);
31
+ return module_.default;
32
+ }
33
+ var ConfigLoader = class extends EventEmitter {
34
+ workspaceRoot;
35
+ configPath = null;
36
+ watcher = null;
37
+ current = null;
38
+ debounceTimer = null;
39
+ DEBOUNCE_MS = 150;
40
+ constructor(workspaceRoot) {
41
+ super();
42
+ this.workspaceRoot = workspaceRoot;
43
+ }
44
+ /** Start watching. Resolves with the initial config (or null if absent). */
45
+ async start() {
46
+ this.configPath = await findConfigFile(this.workspaceRoot);
47
+ if (!this.configPath) return null;
48
+ await this.load();
49
+ this.watch(this.configPath);
50
+ return this.current;
51
+ }
52
+ /** Stop watching and release all resources. */
53
+ dispose() {
54
+ if (this.debounceTimer !== null) clearTimeout(this.debounceTimer);
55
+ this.watcher?.close();
56
+ this.watcher = null;
57
+ }
58
+ /** The last successfully loaded config, or null if none loaded yet. */
59
+ get config() {
60
+ return this.current;
61
+ }
62
+ async load() {
63
+ if (!this.configPath) return;
64
+ try {
65
+ this.current = await importFresh(this.configPath);
66
+ this.emit("change", this.current);
67
+ } catch (error) {
68
+ this.emit("error", error instanceof Error ? error : new Error(String(error)));
69
+ }
70
+ }
71
+ watch(configPath) {
72
+ this.watcher = watch(configPath, { persistent: false }, (event) => {
73
+ if (event === "rename" || event === "change") this.scheduleReload();
74
+ });
75
+ this.watcher.on("error", (error) => this.emit("error", error));
76
+ }
77
+ scheduleReload() {
78
+ if (this.debounceTimer !== null) clearTimeout(this.debounceTimer);
79
+ this.debounceTimer = setTimeout(async () => {
80
+ this.debounceTimer = null;
81
+ if (!this.configPath) return;
82
+ try {
83
+ await access(this.configPath, constants.R_OK);
84
+ } catch {
85
+ this.emit("error", /* @__PURE__ */ new Error(`Config file was removed: ${this.configPath}`));
86
+ return;
87
+ }
88
+ await this.load();
89
+ }, this.DEBOUNCE_MS);
90
+ }
91
+ };
92
+ var ConfigManager = class {
93
+ console;
94
+ configs = /* @__PURE__ */ new Map();
95
+ lspHtmlData = [];
96
+ constructor(console) {
97
+ this.console = console;
98
+ }
99
+ async get(workspaceRoot) {
100
+ const configLoader = this.configs.get(workspaceRoot);
101
+ if (configLoader) {
102
+ if (!configLoader.config) {
103
+ this.configs.delete(workspaceRoot);
104
+ return null;
105
+ }
106
+ return configLoader.config;
107
+ }
108
+ const newConfigLoader = new ConfigLoader(workspaceRoot);
109
+ this.configs.set(workspaceRoot, newConfigLoader);
110
+ newConfigLoader.on("error", (error) => {
111
+ this.console.error(`Failed to load config file: ${error.message}`);
112
+ });
113
+ newConfigLoader.on("change", async (config) => {
114
+ await this.collectData(config);
115
+ });
116
+ await newConfigLoader.start();
117
+ if (!newConfigLoader.config) return null;
118
+ return newConfigLoader.config;
119
+ }
120
+ dispose() {
121
+ for (const configLoader of this.configs.values()) configLoader.dispose();
122
+ }
123
+ async collectData(config) {
124
+ this.lspHtmlData = [];
125
+ for (const pluginOrArray of config.plugins ?? []) {
126
+ const plugins = Array.isArray(pluginOrArray) ? pluginOrArray : [pluginOrArray];
127
+ for (const plugin of plugins) if (plugin.lspHtmlData) {
128
+ const htmlData = await plugin.lspHtmlData();
129
+ if (htmlData) {
130
+ htmlDataInjectPluginName(htmlData, plugin.name);
131
+ this.lspHtmlData.push(htmlData);
132
+ }
133
+ }
134
+ }
135
+ }
136
+ };
137
+ function htmlDataInjectPluginName(htmlData, pluginName) {
138
+ for (const tag of htmlData.tags ?? []) {
139
+ tag.description = replaceDescription(tag.description, pluginName);
140
+ for (const attribute of tag.attributes ?? []) attribute.description = replaceDescription(attribute.description, pluginName);
141
+ }
142
+ for (const attribute of htmlData.globalAttributes ?? []) attribute.description = replaceDescription(attribute.description, pluginName);
143
+ }
144
+ function replaceDescription(description, pluginName) {
145
+ const info = `_Provided by **staticbolt** \`${pluginName}\` plugin._`;
146
+ if (!description) return info;
147
+ if (typeof description === "string") {
148
+ if (description.includes(info)) return description;
149
+ return `${description}\n\n${info}`;
150
+ }
151
+ if (description.value.includes(info)) return description;
152
+ description.value = `${description.value}\n\n${info}`;
153
+ return description;
154
+ }
155
+
156
+ //#endregion
157
+ //#region src/helpers/find-projects.ts
158
+ function findStaticboltProjects(searchRoots) {
159
+ const results = [];
160
+ for (const root of searchRoots) {
161
+ const rootPath = vscodeUri.URI.parse(root.uri).fsPath;
162
+ const configs = globSync("**/.staticbolt.{ts,js}", {
163
+ cwd: rootPath,
164
+ exclude: ["**/node_modules/**", "**/.git/**"]
165
+ });
166
+ for (const configPath of configs) {
167
+ const absDirectory = path$1.join(rootPath, path$1.dirname(configPath));
168
+ const projectUri = vscodeUri.URI.file(absDirectory).toString();
169
+ results.push({
170
+ name: path$1.basename(absDirectory),
171
+ uri: projectUri
172
+ });
173
+ }
174
+ }
175
+ return results;
176
+ }
177
+
178
+ //#endregion
179
+ //#region src/language-model-cache.ts
180
+ function getLanguageModelCache(maxEntries, cleanupIntervalTimeInSec, parse) {
181
+ let languageModels = {};
182
+ let nModels = 0;
183
+ let cleanupInterval = void 0;
184
+ if (cleanupIntervalTimeInSec > 0) cleanupInterval = setInterval(() => {
185
+ const cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1e3;
186
+ const uris = Object.keys(languageModels);
187
+ for (const uri of uris) if (languageModels[uri].cTime < cutoffTime) {
188
+ delete languageModels[uri];
189
+ nModels--;
190
+ }
191
+ }, cleanupIntervalTimeInSec * 1e3);
192
+ return {
193
+ get(document) {
194
+ const version = document.version;
195
+ const languageId = document.languageId;
196
+ const languageModelInfo = languageModels[document.uri];
197
+ if (languageModelInfo && languageModelInfo.version === version && languageModelInfo.languageId === languageId) {
198
+ languageModelInfo.cTime = Date.now();
199
+ return languageModelInfo.languageModel;
200
+ }
201
+ const languageModel = parse(document);
202
+ languageModels[document.uri] = {
203
+ languageModel,
204
+ version,
205
+ languageId,
206
+ cTime: Date.now()
207
+ };
208
+ if (!languageModelInfo) nModels++;
209
+ if (nModels === maxEntries) {
210
+ let oldestTime = Number.MAX_VALUE;
211
+ let oldestUri = null;
212
+ for (const uri in languageModels) {
213
+ const languageModelInfo = languageModels[uri];
214
+ if (languageModelInfo.cTime < oldestTime) {
215
+ oldestUri = uri;
216
+ oldestTime = languageModelInfo.cTime;
217
+ }
218
+ }
219
+ if (oldestUri) {
220
+ delete languageModels[oldestUri];
221
+ nModels--;
222
+ }
223
+ }
224
+ return languageModel;
225
+ },
226
+ onDocumentRemoved(document) {
227
+ const uri = document.uri;
228
+ if (languageModels[uri]) {
229
+ delete languageModels[uri];
230
+ nModels--;
231
+ }
232
+ },
233
+ dispose() {
234
+ if (cleanupInterval !== void 0) {
235
+ clearInterval(cleanupInterval);
236
+ cleanupInterval = void 0;
237
+ languageModels = {};
238
+ nModels = 0;
239
+ }
240
+ }
241
+ };
242
+ }
243
+
244
+ //#endregion
245
+ //#region src/modes/embedded-support.ts
246
+ const TokenType = vscodeHtml.TokenType;
247
+ const Position = vscodeHtml.Position;
248
+ function getDocumentRegions(languageService, document) {
249
+ const regions = [];
250
+ const scanner = languageService.createScanner(document.getText());
251
+ let lastTagName = "";
252
+ let lastAttributeName = null;
253
+ let languageIdFromType = void 0;
254
+ const importedScripts = [];
255
+ let token = scanner.scan();
256
+ while (token !== TokenType.EOS) {
257
+ switch (token) {
258
+ case TokenType.StartTag:
259
+ lastTagName = scanner.getTokenText();
260
+ lastAttributeName = null;
261
+ languageIdFromType = "javascript";
262
+ break;
263
+ case TokenType.Styles:
264
+ regions.push({
265
+ languageId: "css",
266
+ start: scanner.getTokenOffset(),
267
+ end: scanner.getTokenEnd()
268
+ });
269
+ break;
270
+ case TokenType.Script:
271
+ regions.push({
272
+ languageId: languageIdFromType,
273
+ start: scanner.getTokenOffset(),
274
+ end: scanner.getTokenEnd()
275
+ });
276
+ break;
277
+ case TokenType.AttributeName:
278
+ lastAttributeName = scanner.getTokenText();
279
+ break;
280
+ case TokenType.AttributeValue:
281
+ if (lastAttributeName === "src" && lastTagName.toLowerCase() === "script") {
282
+ let value = scanner.getTokenText();
283
+ if (value[0] === "'" || value[0] === "\"") value = value.slice(1, -1);
284
+ importedScripts.push(value);
285
+ } else if (lastAttributeName === "type" && lastTagName.toLowerCase() === "script") {
286
+ const token = scanner.getTokenText();
287
+ if (/["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test(token) || token === "module") languageIdFromType = "javascript";
288
+ else if (/["']text\/typescript["']/.test(token)) languageIdFromType = "typescript";
289
+ else languageIdFromType = void 0;
290
+ } else {
291
+ const attributeLanguageId = getAttributeLanguage(lastAttributeName);
292
+ if (attributeLanguageId) {
293
+ let start = scanner.getTokenOffset();
294
+ let end = scanner.getTokenEnd();
295
+ const firstChar = document.getText()[start];
296
+ if (firstChar === "'" || firstChar === "\"") {
297
+ start++;
298
+ end--;
299
+ }
300
+ regions.push({
301
+ languageId: attributeLanguageId,
302
+ start,
303
+ end,
304
+ attributeValue: true
305
+ });
306
+ }
307
+ }
308
+ lastAttributeName = null;
309
+ break;
310
+ }
311
+ token = scanner.scan();
312
+ }
313
+ return {
314
+ getLanguageRanges: (range) => getLanguageRanges(document, regions, range),
315
+ getEmbeddedDocument: (languageId, ignoreAttributeValues) => getEmbeddedDocument(document, regions, languageId, ignoreAttributeValues),
316
+ getLanguageAtPosition: (position) => getLanguageAtPosition(document, regions, position),
317
+ getLanguagesInDocument: () => getLanguagesInDocument(document, regions),
318
+ getImportedScripts: () => importedScripts
319
+ };
320
+ }
321
+ function getLanguageRanges(document, regions, range) {
322
+ const result = [];
323
+ let currentPos = range ? range.start : Position.create(0, 0);
324
+ let currentOffset = range ? document.offsetAt(range.start) : 0;
325
+ const endOffset = range ? document.offsetAt(range.end) : document.getText().length;
326
+ for (const region of regions) if (region.end > currentOffset && region.start < endOffset) {
327
+ const start = Math.max(region.start, currentOffset);
328
+ const startPos = document.positionAt(start);
329
+ if (currentOffset < region.start) result.push({
330
+ start: currentPos,
331
+ end: startPos,
332
+ languageId: "html"
333
+ });
334
+ const end = Math.min(region.end, endOffset);
335
+ const endPos = document.positionAt(end);
336
+ if (end > region.start) result.push({
337
+ start: startPos,
338
+ end: endPos,
339
+ languageId: region.languageId,
340
+ attributeValue: region.attributeValue
341
+ });
342
+ currentOffset = end;
343
+ currentPos = endPos;
344
+ }
345
+ if (currentOffset < endOffset) {
346
+ const endPos = range ? range.end : document.positionAt(endOffset);
347
+ result.push({
348
+ start: currentPos,
349
+ end: endPos,
350
+ languageId: "html"
351
+ });
352
+ }
353
+ return result;
354
+ }
355
+ function getLanguagesInDocument(_document, regions) {
356
+ const result = [];
357
+ for (const region of regions) if (region.languageId && !result.includes(region.languageId)) {
358
+ result.push(region.languageId);
359
+ if (result.length === 3) return result;
360
+ }
361
+ result.push("html");
362
+ return result;
363
+ }
364
+ function getLanguageAtPosition(document, regions, position) {
365
+ const offset = document.offsetAt(position);
366
+ for (const region of regions) if (region.start <= offset) {
367
+ if (offset <= region.end) return region.languageId;
368
+ } else break;
369
+ return "html";
370
+ }
371
+ function getEmbeddedDocument(document, contents, languageId, ignoreAttributeValues) {
372
+ let currentPos = 0;
373
+ const oldContent = document.getText();
374
+ let result = "";
375
+ let lastSuffix = "";
376
+ for (const c of contents) if (c.languageId === languageId && (!ignoreAttributeValues || !c.attributeValue)) {
377
+ result = substituteWithWhitespace(result, currentPos, c.start, oldContent, lastSuffix, getPrefix(c));
378
+ result += updateContent(c, oldContent.slice(c.start, c.end));
379
+ currentPos = c.end;
380
+ lastSuffix = getSuffix(c);
381
+ }
382
+ result = substituteWithWhitespace(result, currentPos, oldContent.length, oldContent, lastSuffix, "");
383
+ return TextDocument.create(document.uri, languageId, document.version, result);
384
+ }
385
+ function getPrefix(c) {
386
+ if (c.attributeValue) switch (c.languageId) {
387
+ case "css": return "__{";
388
+ }
389
+ return "";
390
+ }
391
+ function getSuffix(c) {
392
+ if (c.attributeValue) switch (c.languageId) {
393
+ case "css": return "}";
394
+ case "javascript": return ";";
395
+ }
396
+ return "";
397
+ }
398
+ function updateContent(c, content) {
399
+ if (!c.attributeValue && c.languageId === "javascript") return content.replace(`<!--`, `/* `).replace(`-->`, ` */`);
400
+ if (c.languageId === "css") return content.replace(/(&quot;|&#34;)/g, (match, _, offset) => {
401
+ const spaces = " ".repeat(match.length - 1);
402
+ const afterChar = content[offset + match.length];
403
+ if (!afterChar || afterChar.includes(" ")) return `${spaces}"`;
404
+ return `"${spaces}`;
405
+ });
406
+ return content;
407
+ }
408
+ function substituteWithWhitespace(result, start, end, oldContent, before, after) {
409
+ result += before;
410
+ let accumulatedWS = -before.length;
411
+ for (let index = start; index < end; index++) {
412
+ const ch = oldContent[index];
413
+ if (ch === "\n" || ch === "\r") {
414
+ accumulatedWS = 0;
415
+ result += ch;
416
+ } else accumulatedWS++;
417
+ }
418
+ result = append(result, " ", accumulatedWS - after.length);
419
+ result += after;
420
+ return result;
421
+ }
422
+ function append(result, string_, n) {
423
+ while (n > 0) {
424
+ if (n & 1) result += string_;
425
+ n >>= 1;
426
+ string_ += string_;
427
+ }
428
+ return result;
429
+ }
430
+ function getAttributeLanguage(attributeName) {
431
+ const match = attributeName.match(/^(style)$|^(on\w+)$/i);
432
+ if (!match) return null;
433
+ return match[1] ? "css" : "javascript";
434
+ }
435
+
436
+ //#endregion
437
+ //#region src/modes/html-mode.ts
438
+ function getHTMLMode(htmlLanguageService) {
439
+ const htmlDocuments = getLanguageModelCache(10, 60, (document) => htmlLanguageService.parseHTMLDocument(document));
440
+ return {
441
+ getId() {
442
+ return "html";
443
+ },
444
+ async doComplete(document, position, documentContext, htmlData) {
445
+ setHtmlDataProviders(htmlLanguageService, htmlData);
446
+ const htmlDocument = htmlDocuments.get(document);
447
+ const completionList = await htmlLanguageService.doComplete2(document, position, htmlDocument, documentContext);
448
+ for (const item of completionList.items) item.sortText = "0_" + item.label;
449
+ return completionList;
450
+ },
451
+ async doHover(document, position, htmlData) {
452
+ setHtmlDataProviders(htmlLanguageService, htmlData);
453
+ return htmlLanguageService.doHover(document, position, htmlDocuments.get(document));
454
+ },
455
+ async onDocumentRemoved(document) {
456
+ htmlDocuments.onDocumentRemoved(document);
457
+ },
458
+ async findDocumentLinks(document, documentContext, projectRoot) {
459
+ const resolver = new Resolver(projectRoot);
460
+ const documentFs = vscodeUri.URI.parse(document.uri).fsPath;
461
+ const projectRootRelative = path.relative(projectRoot, documentFs);
462
+ const links = htmlLanguageService.findDocumentLinks(document, documentContext);
463
+ for (const link of links) {
464
+ if (!link.target) continue;
465
+ const linkFs = vscodeUri.URI.parse(link.target ?? "").fsPath;
466
+ const source = path.relative(path.dirname(documentFs), linkFs);
467
+ const resolved = resolver.resolve(source, projectRootRelative);
468
+ if (resolved) link.target = vscodeUri.URI.file(resolved.path).toString();
469
+ }
470
+ return links;
471
+ },
472
+ dispose() {
473
+ htmlDocuments.dispose();
474
+ }
475
+ };
476
+ }
477
+ function setHtmlDataProviders(htmlLanguageService, htmlData) {
478
+ htmlLanguageService.setDataProviders(false, [{
479
+ getId() {
480
+ return FILE_PROTOCOL;
481
+ },
482
+ isApplicable(languageId) {
483
+ return languageId === "html";
484
+ },
485
+ provideValues(tag, attribute) {
486
+ const attributes = htmlData.flatMap((data) => data.tags?.filter((t) => t.name === tag) ?? []).flatMap((t) => t.attributes?.filter((a) => a.name === attribute) ?? []);
487
+ return [...htmlData.flatMap((data) => data.globalAttributes?.filter((a) => a.name === attribute) ?? []), ...attributes].flatMap((a) => a.values ?? []);
488
+ },
489
+ provideTags() {
490
+ return htmlData.flatMap((data) => data.tags ?? []);
491
+ },
492
+ provideAttributes(tag) {
493
+ const tags = htmlData.flatMap((data) => data.tags?.filter((t) => t.name === tag) ?? []);
494
+ const globalAttributes = htmlData.flatMap((data) => data.globalAttributes ?? []);
495
+ const attributes = tags.flatMap((t) => t.attributes ?? []);
496
+ return [...globalAttributes, ...attributes];
497
+ }
498
+ }]);
499
+ }
500
+
501
+ //#endregion
502
+ //#region src/modes/language-modes.ts
503
+ const FILE_PROTOCOL = "staticbolt-server";
504
+ function getLanguageModes(clientCapabilities, requestService) {
505
+ const htmlLanguageService = vscodeHtml.getLanguageService({
506
+ clientCapabilities,
507
+ fileSystemProvider: requestService,
508
+ customDataProviders: [{
509
+ getId() {
510
+ return FILE_PROTOCOL;
511
+ },
512
+ isApplicable(languageId) {
513
+ return languageId === "html";
514
+ },
515
+ provideValues(tag, attribute) {
516
+ return [{ name: `tag:${tag} attribute:${attribute}` }];
517
+ },
518
+ provideTags() {
519
+ return [{
520
+ name: "staticbolt",
521
+ description: "# staticbolt-description",
522
+ attributes: []
523
+ }];
524
+ },
525
+ provideAttributes(tag) {
526
+ if (tag === "staticbolt") return [{
527
+ name: "staticbolt-attribute",
528
+ description: "# staticbolt-attribute-description",
529
+ values: [{ name: "staticbolt-attribute-value" }]
530
+ }];
531
+ return [{ name: "global-attribute" }];
532
+ }
533
+ }],
534
+ useDefaultDataProvider: false
535
+ });
536
+ const documentRegions = getLanguageModelCache(10, 60, (document) => getDocumentRegions(htmlLanguageService, document));
537
+ let modelCaches = [documentRegions];
538
+ let modes = Object.create(null);
539
+ modes["html"] = getHTMLMode(htmlLanguageService);
540
+ return {
541
+ async updateDataProviders(dataProviders) {
542
+ htmlLanguageService.setDataProviders(true, dataProviders);
543
+ },
544
+ getModeAtPosition(document, position) {
545
+ const languageId = documentRegions.get(document).getLanguageAtPosition(position);
546
+ if (languageId) return modes[languageId];
547
+ },
548
+ getModesInRange(document, range) {
549
+ return documentRegions.get(document).getLanguageRanges(range).map((r) => {
550
+ return {
551
+ start: r.start,
552
+ end: r.end,
553
+ mode: r.languageId ? modes[r.languageId] : void 0,
554
+ attributeValue: r.attributeValue
555
+ };
556
+ });
557
+ },
558
+ getAllModesInDocument(document) {
559
+ const result = [];
560
+ for (const languageId of documentRegions.get(document).getLanguagesInDocument()) {
561
+ const mode = modes[languageId];
562
+ if (mode) result.push(mode);
563
+ }
564
+ return result;
565
+ },
566
+ getAllModes() {
567
+ const result = [];
568
+ for (const languageId in modes) {
569
+ const mode = modes[languageId];
570
+ if (mode) result.push(mode);
571
+ }
572
+ return result;
573
+ },
574
+ getMode(languageId) {
575
+ return modes[languageId];
576
+ },
577
+ onDocumentRemoved(document) {
578
+ for (const mc of modelCaches) mc.onDocumentRemoved(document);
579
+ for (const mode in modes) modes[mode].onDocumentRemoved(document);
580
+ },
581
+ dispose() {
582
+ for (const mc of modelCaches) mc.dispose();
583
+ modelCaches = [];
584
+ for (const mode in modes) modes[mode].dispose();
585
+ modes = {};
586
+ }
587
+ };
588
+ }
589
+
590
+ //#endregion
591
+ //#region src/requests.ts
592
+ const FsStatRequest = { type: new RequestType("fs/stat") };
593
+ const FsReadDirectoryRequest = { type: new RequestType("fs/readDir") };
594
+ const FileType$1 = Object.freeze({
595
+ /** The file type is unknown. */
596
+ Unknown: 0,
597
+ /** A regular file. */
598
+ File: 1,
599
+ /** A directory. */
600
+ Directory: 2,
601
+ /** A symbolic link to a file. */
602
+ SymbolicLink: 64
603
+ });
604
+ function getFileSystemProvider(handledSchemas, connection, runtime) {
605
+ const fileFs = runtime.fileFs && handledSchemas.includes("file") ? runtime.fileFs : void 0;
606
+ return {
607
+ async stat(uri) {
608
+ if (fileFs && uri.startsWith("file:")) return fileFs.stat(uri);
609
+ return await connection.sendRequest(FsStatRequest.type, uri.toString());
610
+ },
611
+ readDirectory(uri) {
612
+ if (fileFs && uri.startsWith("file:")) return fileFs.readDirectory(uri);
613
+ return connection.sendRequest(FsReadDirectoryRequest.type, uri.toString());
614
+ }
615
+ };
616
+ }
617
+
618
+ //#endregion
619
+ //#region src/utils/arrays.ts
620
+ function pushAll(to, from) {
621
+ if (from) for (const item of from) to.push(item);
622
+ }
623
+
624
+ //#endregion
625
+ //#region src/utils/strings.ts
626
+ function startsWith(haystack, needle) {
627
+ if (haystack.length < needle.length) return false;
628
+ for (let index = 0; index < needle.length; index++) if (haystack[index] !== needle[index]) return false;
629
+ return true;
630
+ }
631
+ function endsWith(haystack, needle) {
632
+ const diff = haystack.length - needle.length;
633
+ if (diff > 0) return haystack.indexOf(needle, diff) === diff;
634
+ if (diff === 0) return haystack === needle;
635
+ return false;
636
+ }
637
+ const CR = "\r".codePointAt(0);
638
+ const NL = "\n".codePointAt(0);
639
+
640
+ //#endregion
641
+ //#region src/utils/document-context.ts
642
+ function getDocumentContext(documentUri, workspaceFolders) {
643
+ function getRootFolder() {
644
+ for (const folder of workspaceFolders) {
645
+ let folderURI = folder.uri;
646
+ if (!endsWith(folderURI, "/")) folderURI = folderURI + "/";
647
+ if (startsWith(documentUri, folderURI)) return folderURI;
648
+ }
649
+ }
650
+ return { resolveReference: (reference, base = documentUri) => {
651
+ if (/^\w[\w\d+.-]*:/.test(reference)) return reference;
652
+ if (reference[0] === "/") {
653
+ const folderUri = getRootFolder();
654
+ if (folderUri) return folderUri + reference.slice(1);
655
+ }
656
+ const baseUri = vscodeUri.URI.parse(base);
657
+ const baseUriDirectory = baseUri.path.endsWith("/") ? baseUri : vscodeUri.Utils.dirname(baseUri);
658
+ return vscodeUri.Utils.resolvePath(baseUriDirectory, reference).toString(true);
659
+ } };
660
+ }
661
+
662
+ //#endregion
663
+ //#region src/utils/find-project-root.ts
664
+ function findProjectRoot(fileUri) {
665
+ let directory = path$1.dirname(vscodeUri.URI.parse(fileUri).fsPath);
666
+ while (true) {
667
+ for (const config of [".staticbolt.ts", ".staticbolt.js"]) if (fs.existsSync(path$1.join(directory, config))) return directory;
668
+ const parent = path$1.dirname(directory);
669
+ if (parent === directory) return null;
670
+ directory = parent;
671
+ }
672
+ }
673
+
674
+ //#endregion
675
+ //#region src/utils/runner.ts
676
+ function formatError(message, error) {
677
+ if (error instanceof Error) return `${message}: ${error.message}\n${error.stack}`;
678
+ if (typeof error === "string") return `${message}: ${error}`;
679
+ if (error) return `${message}: ${error.toString()}`;
680
+ return message;
681
+ }
682
+ function runSafe(runtime, function_, errorValue, errorMessage, token) {
683
+ return new Promise((resolve) => {
684
+ runtime.timer.setImmediate(() => {
685
+ if (token.isCancellationRequested) {
686
+ resolve(cancelValue());
687
+ return;
688
+ }
689
+ return function_().then((result) => {
690
+ if (token.isCancellationRequested) {
691
+ resolve(cancelValue());
692
+ return;
693
+ }
694
+ resolve(result);
695
+ }, (error) => {
696
+ console.error(formatError(errorMessage, error));
697
+ resolve(errorValue);
698
+ });
699
+ });
700
+ });
701
+ }
702
+ function cancelValue() {
703
+ return new vscode.ResponseError(vscode.LSPErrorCodes.RequestCancelled, "Request cancelled");
704
+ }
705
+
706
+ //#endregion
707
+ //#region src/html-server.ts
708
+ function startServer(connection, runtime) {
709
+ const documents = new vscode$1.TextDocuments(TextDocument);
710
+ documents.listen(connection);
711
+ let lspSearchRoots = [];
712
+ let workspaceFolders = [];
713
+ let configManager;
714
+ let languageModes;
715
+ const documentSettings = {};
716
+ documents.onDidClose((document) => {
717
+ delete documentSettings[document.document.uri];
718
+ });
719
+ connection.onInitialize((parameters) => {
720
+ if (Array.isArray(parameters.workspaceFolders)) lspSearchRoots = parameters.workspaceFolders;
721
+ else {
722
+ lspSearchRoots = [];
723
+ if (parameters.rootPath) lspSearchRoots.push({
724
+ name: "",
725
+ uri: vscodeUri.URI.file(parameters.rootPath).toString()
726
+ });
727
+ }
728
+ workspaceFolders = findStaticboltProjects(lspSearchRoots);
729
+ connection.console.log(`[staticbolt] discovered projects:\n` + workspaceFolders.map((f) => ` - [${f.name}]: ${f.uri}`).join("\n"));
730
+ const fileSystemProvider = getFileSystemProvider(["file"], connection, runtime);
731
+ languageModes = getLanguageModes(parameters.capabilities, fileSystemProvider);
732
+ configManager = new ConfigManager(connection.console);
733
+ documents.onDidClose((document) => {
734
+ languageModes.onDocumentRemoved(document.document);
735
+ });
736
+ connection.onShutdown(() => {
737
+ languageModes.dispose();
738
+ configManager.dispose();
739
+ });
740
+ return { capabilities: {
741
+ textDocumentSync: vscode$1.TextDocumentSyncKind.Incremental,
742
+ completionProvider: {
743
+ resolveProvider: true,
744
+ triggerCharacters: [
745
+ ".",
746
+ ":",
747
+ "<",
748
+ "\"",
749
+ "=",
750
+ "/"
751
+ ]
752
+ },
753
+ hoverProvider: true,
754
+ documentLinkProvider: { resolveProvider: false }
755
+ } };
756
+ });
757
+ connection.onInitialized(() => {});
758
+ connection.onCompletion(async (textDocumentPosition, token) => {
759
+ return runSafe(runtime, async () => {
760
+ const projectRoot = findProjectRoot(textDocumentPosition.textDocument.uri);
761
+ if (!projectRoot) return null;
762
+ const document = documents.get(textDocumentPosition.textDocument.uri);
763
+ if (!document) return null;
764
+ const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
765
+ if (!mode?.doComplete) return {
766
+ isIncomplete: true,
767
+ items: []
768
+ };
769
+ if (!await configManager.get(projectRoot)) return {
770
+ isIncomplete: true,
771
+ items: []
772
+ };
773
+ const documentContext = getDocumentContext(document.uri, workspaceFolders);
774
+ return mode.doComplete(document, textDocumentPosition.position, documentContext, configManager.lspHtmlData);
775
+ }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, token);
776
+ });
777
+ connection.onCompletionResolve((item, token) => {
778
+ return runSafe(runtime, async () => {
779
+ const data = item.data;
780
+ if (!isCompletionItemData(data)) return item;
781
+ const document = documents.get(data.uri);
782
+ if (!document) return item;
783
+ const mode = languageModes.getMode(data.languageId);
784
+ if (!mode?.doResolve) return item;
785
+ return mode.doResolve(document, item);
786
+ }, item, `Error while resolving completion proposal`, token);
787
+ });
788
+ connection.onHover((textDocumentPosition, token) => {
789
+ return runSafe(runtime, async () => {
790
+ const projectRoot = findProjectRoot(textDocumentPosition.textDocument.uri);
791
+ if (!projectRoot) return null;
792
+ const document = documents.get(textDocumentPosition.textDocument.uri);
793
+ if (!document) return null;
794
+ const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
795
+ if (!mode?.doHover) return null;
796
+ if (!await configManager.get(projectRoot)) return null;
797
+ return mode.doHover(document, textDocumentPosition.position, configManager.lspHtmlData);
798
+ }, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`, token);
799
+ });
800
+ connection.onDocumentLinks((documentLinkParameter, token) => {
801
+ return runSafe(runtime, async () => {
802
+ const projectRoot = findProjectRoot(documentLinkParameter.textDocument.uri);
803
+ if (!projectRoot) return null;
804
+ const document = documents.get(documentLinkParameter.textDocument.uri);
805
+ if (!document) return [];
806
+ const links = [];
807
+ const documentContext = getDocumentContext(document.uri, workspaceFolders);
808
+ for (const mode of languageModes.getAllModesInDocument(document)) if (mode.findDocumentLinks) pushAll(links, await mode.findDocumentLinks(document, documentContext, projectRoot));
809
+ return links;
810
+ }, [], `Error while document links for ${documentLinkParameter.textDocument.uri}`, token);
811
+ });
812
+ connection.listen();
813
+ }
814
+ function isCompletionItemData(value) {
815
+ return value && typeof value.languageId === "string" && typeof value.uri === "string" && typeof value.offset === "number";
816
+ }
817
+
818
+ //#endregion
819
+ //#region src/utils/node-fs.ts
820
+ const FileType = Object.freeze({
821
+ /** The file type is unknown. */
822
+ Unknown: 0,
823
+ /** A regular file. */
824
+ File: 1,
825
+ /** A directory. */
826
+ Directory: 2,
827
+ /** A symbolic link to a file. */
828
+ SymbolicLink: 64
829
+ });
830
+ function getNodeFileFS() {
831
+ function ensureFileUri(location) {
832
+ if (!location.startsWith("file:")) throw new Error("fileSystemProvider can only handle file URLs");
833
+ }
834
+ return {
835
+ stat(location) {
836
+ ensureFileUri(location);
837
+ return new Promise((c, error_) => {
838
+ const uri = vscodeUri.URI.parse(location);
839
+ fs.stat(uri.fsPath, (error, stats) => {
840
+ if (error) return error.code === "ENOENT" ? c({
841
+ type: FileType.Unknown,
842
+ ctime: -1,
843
+ mtime: -1,
844
+ size: -1
845
+ }) : error_(error);
846
+ let type = FileType.Unknown;
847
+ if (stats.isFile()) type = FileType.File;
848
+ else if (stats.isDirectory()) type = FileType.Directory;
849
+ else if (stats.isSymbolicLink()) type = FileType.SymbolicLink;
850
+ c({
851
+ type,
852
+ ctime: stats.ctime.getTime(),
853
+ mtime: stats.mtime.getTime(),
854
+ size: stats.size
855
+ });
856
+ });
857
+ });
858
+ },
859
+ readDirectory(location) {
860
+ ensureFileUri(location);
861
+ return new Promise((c, error_) => {
862
+ const path = vscodeUri.URI.parse(location).fsPath;
863
+ fs.readdir(path, { withFileTypes: true }, (error, children) => {
864
+ if (error) return error_(error);
865
+ c(children.map((stat) => {
866
+ if (stat.isSymbolicLink()) return [stat.name, FileType.SymbolicLink];
867
+ else if (stat.isDirectory()) return [stat.name, FileType.Directory];
868
+ else if (stat.isFile()) return [stat.name, FileType.File];
869
+ else return [stat.name, FileType.Unknown];
870
+ }));
871
+ });
872
+ });
873
+ }
874
+ };
875
+ }
876
+
877
+ //#endregion
878
+ //#region src/index.ts
879
+ const connection = vs.createConnection();
880
+ console.log = connection.console.log.bind(connection.console);
881
+ console.error = connection.console.error.bind(connection.console);
882
+ process.on("unhandledRejection", (error) => {
883
+ connection.console.error(formatError(`Unhandled exception`, error));
884
+ });
885
+ const runtime = {
886
+ timer: {
887
+ setImmediate(callback, ...arguments_) {
888
+ const handle = setImmediate(callback, ...arguments_);
889
+ return { dispose: () => clearImmediate(handle) };
890
+ },
891
+ setTimeout(callback, ms, ...arguments_) {
892
+ const handle = setTimeout(callback, ms, ...arguments_);
893
+ return { dispose: () => clearTimeout(handle) };
894
+ }
895
+ },
896
+ fileFs: getNodeFileFS()
897
+ };
898
+ connection.console.log("[staticbolt] starting server");
899
+ startServer(connection, runtime);
900
+
901
+ //#endregion
902
+ export { };
903
+ //# sourceMappingURL=index.mjs.map