@staticbolt/lsp 1.0.0-beta.26 → 1.0.0-beta.27
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 +124 -155
- package/lib/index.mjs.map +1 -1
- package/package.json +4 -3
- package/src/helpers/config-loader.ts +14 -12
- package/src/html-server.ts +8 -17
- package/src/modes/embedded-support.ts +40 -172
- package/src/modes/language-modes.ts +8 -69
- package/src/modes/markdown-regions.ts +92 -0
package/lib/index.mjs
CHANGED
|
@@ -12,10 +12,12 @@ import * as path$1 from "node:path";
|
|
|
12
12
|
import path, { join } from "node:path";
|
|
13
13
|
import { pathToFileURL } from "node:url";
|
|
14
14
|
import vscodeHtml from "vscode-html-languageservice";
|
|
15
|
+
import { markdownToMdast } from "satteri";
|
|
15
16
|
import { Resolver } from "@staticbolt/core";
|
|
16
17
|
|
|
17
18
|
//#region src/helpers/config-loader.ts
|
|
18
|
-
|
|
19
|
+
/** Iterated in order, so the first one that exists wins. */
|
|
20
|
+
const CONFIG_CANDIDATES = /* @__PURE__ */ new Set([".staticbolt.ts", ".staticbolt.js"]);
|
|
19
21
|
async function findConfigFile(workspaceRoot) {
|
|
20
22
|
for (const name of CONFIG_CANDIDATES) {
|
|
21
23
|
const full = join(workspaceRoot, name);
|
|
@@ -47,7 +49,7 @@ var ConfigLoader = class extends EventEmitter {
|
|
|
47
49
|
this.configPath = await findConfigFile(this.workspaceRoot);
|
|
48
50
|
if (!this.configPath) return null;
|
|
49
51
|
await this.load();
|
|
50
|
-
this.watch(
|
|
52
|
+
this.watch();
|
|
51
53
|
return this.current;
|
|
52
54
|
}
|
|
53
55
|
/** Stop watching and release all resources. */
|
|
@@ -69,9 +71,13 @@ var ConfigLoader = class extends EventEmitter {
|
|
|
69
71
|
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The directory is watched rather than the file. A watch on the file itself is bound to the inode, so an editor saving through
|
|
76
|
+
* a temporary file and a rename leaves it watching a replaced file, and on macOS it never reports a plain write either.
|
|
77
|
+
*/
|
|
78
|
+
watch() {
|
|
79
|
+
this.watcher = watch(this.workspaceRoot, { persistent: false }, (_event, filename) => {
|
|
80
|
+
if (typeof filename === "string" && CONFIG_CANDIDATES.has(filename)) this.scheduleReload();
|
|
75
81
|
});
|
|
76
82
|
this.watcher.on("error", (error) => this.emit("error", error));
|
|
77
83
|
}
|
|
@@ -79,11 +85,9 @@ var ConfigLoader = class extends EventEmitter {
|
|
|
79
85
|
if (this.debounceTimer !== null) clearTimeout(this.debounceTimer);
|
|
80
86
|
this.debounceTimer = setTimeout(async () => {
|
|
81
87
|
this.debounceTimer = null;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
} catch {
|
|
86
|
-
this.emit("error", /* @__PURE__ */ new Error(`Config file was removed: ${this.configPath}`));
|
|
88
|
+
this.configPath = await findConfigFile(this.workspaceRoot);
|
|
89
|
+
if (!this.configPath) {
|
|
90
|
+
this.emit("error", /* @__PURE__ */ new Error(`Config file was removed from: ${this.workspaceRoot}`));
|
|
87
91
|
return;
|
|
88
92
|
}
|
|
89
93
|
await this.load();
|
|
@@ -247,17 +251,92 @@ function getLanguageModelCache(maxEntries, cleanupIntervalTimeInSec, parse) {
|
|
|
247
251
|
};
|
|
248
252
|
}
|
|
249
253
|
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/modes/markdown-regions.ts
|
|
256
|
+
/**
|
|
257
|
+
* Markdown allows raw HTML anywhere, so a ".md" file is served as HTML with the parts that can never be HTML — front matter, code
|
|
258
|
+
* blocks and code spans — blanked out first. Blanking keeps every offset, so positions still point at the same place in the
|
|
259
|
+
* file.
|
|
260
|
+
*/
|
|
261
|
+
/** None of these nest, so the regions they produce never overlap. */
|
|
262
|
+
const NON_HTML_NODES = /* @__PURE__ */ new Set([
|
|
263
|
+
"code",
|
|
264
|
+
"inlineCode",
|
|
265
|
+
"yaml",
|
|
266
|
+
"toml"
|
|
267
|
+
]);
|
|
268
|
+
/** The regions of a markdown document that must not be treated as HTML, sorted by start offset. */
|
|
269
|
+
function findMarkdownNonHtmlRegions(text) {
|
|
270
|
+
let tree;
|
|
271
|
+
try {
|
|
272
|
+
tree = markdownToMdast(text);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.warn("[staticbolt] could not parse markdown, its whole content is handled as HTML:", error);
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
const regions = [];
|
|
278
|
+
const pending = [tree];
|
|
279
|
+
while (pending.length > 0) {
|
|
280
|
+
const node = pending.pop();
|
|
281
|
+
if (NON_HTML_NODES.has(node.type)) {
|
|
282
|
+
const start = node.position?.start.offset;
|
|
283
|
+
const end = node.position?.end.offset;
|
|
284
|
+
if (start !== void 0 && end !== void 0) regions.push({
|
|
285
|
+
start,
|
|
286
|
+
end
|
|
287
|
+
});
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if ("children" in node) pending.push(...node.children);
|
|
291
|
+
}
|
|
292
|
+
return toUtf16Offsets(text, regions.toSorted((a, b) => a.start - b.start));
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* The parser counts code points, the editor counts UTF-16 code units. The two only drift apart once a character outside the basic
|
|
296
|
+
* plane, an emoji most of the time, sits before a region.
|
|
297
|
+
*/
|
|
298
|
+
function toUtf16Offsets(text, regions) {
|
|
299
|
+
if (!/[\uD800-\uDBFF]/.test(text)) return regions;
|
|
300
|
+
const astral = [];
|
|
301
|
+
let codePoint = 0;
|
|
302
|
+
for (const character of text) {
|
|
303
|
+
if (character.length === 2) astral.push(codePoint);
|
|
304
|
+
codePoint++;
|
|
305
|
+
}
|
|
306
|
+
const toUtf16 = (offset) => offset + astral.filter((position) => position < offset).length;
|
|
307
|
+
return regions.map((region) => ({
|
|
308
|
+
start: toUtf16(region.start),
|
|
309
|
+
end: toUtf16(region.end)
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
/** Replaces every region with spaces, keeping the length of the text and its line breaks. Regions must be sorted and disjoint. */
|
|
313
|
+
function blankRegions(text, regions) {
|
|
314
|
+
if (regions.length === 0) return text;
|
|
315
|
+
let result = "";
|
|
316
|
+
let cursor = 0;
|
|
317
|
+
for (const region of regions) {
|
|
318
|
+
result += text.slice(cursor, region.start) + text.slice(region.start, region.end).replaceAll(/[^\n\r]/g, " ");
|
|
319
|
+
cursor = region.end;
|
|
320
|
+
}
|
|
321
|
+
return result + text.slice(cursor);
|
|
322
|
+
}
|
|
323
|
+
|
|
250
324
|
//#endregion
|
|
251
325
|
//#region src/modes/embedded-support.ts
|
|
252
326
|
const TokenType = vscodeHtml.TokenType;
|
|
253
|
-
const Position = vscodeHtml.Position;
|
|
254
327
|
function getDocumentRegions(languageService, document) {
|
|
328
|
+
const source = document.getText();
|
|
329
|
+
const isMarkdown = document.languageId === "markdown";
|
|
330
|
+
const markdownRegions = isMarkdown ? findMarkdownNonHtmlRegions(source).map((region) => ({
|
|
331
|
+
...region,
|
|
332
|
+
languageId: void 0
|
|
333
|
+
})) : [];
|
|
334
|
+
const htmlText = isMarkdown ? blankRegions(source, markdownRegions) : source;
|
|
255
335
|
const regions = [];
|
|
256
|
-
const scanner = languageService.createScanner(
|
|
336
|
+
const scanner = languageService.createScanner(htmlText);
|
|
257
337
|
let lastTagName = "";
|
|
258
338
|
let lastAttributeName = null;
|
|
259
339
|
let languageIdFromType;
|
|
260
|
-
const importedScripts = [];
|
|
261
340
|
let token = scanner.scan();
|
|
262
341
|
while (token !== TokenType.EOS) {
|
|
263
342
|
switch (token) {
|
|
@@ -284,11 +363,7 @@ function getDocumentRegions(languageService, document) {
|
|
|
284
363
|
lastAttributeName = scanner.getTokenText();
|
|
285
364
|
break;
|
|
286
365
|
case TokenType.AttributeValue:
|
|
287
|
-
if (lastAttributeName === "
|
|
288
|
-
let value = scanner.getTokenText();
|
|
289
|
-
if (value[0] === "'" || value[0] === "\"") value = value.slice(1, -1);
|
|
290
|
-
importedScripts.push(value);
|
|
291
|
-
} else if (lastAttributeName === "type" && lastTagName.toLowerCase() === "script") {
|
|
366
|
+
if (lastAttributeName === "type" && lastTagName.toLowerCase() === "script") {
|
|
292
367
|
const token = scanner.getTokenText();
|
|
293
368
|
if (/["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test(token) || token === "module") languageIdFromType = "javascript";
|
|
294
369
|
else if (/["']text\/typescript["']/.test(token)) languageIdFromType = "typescript";
|
|
@@ -298,7 +373,7 @@ function getDocumentRegions(languageService, document) {
|
|
|
298
373
|
if (attributeLanguageId) {
|
|
299
374
|
let start = scanner.getTokenOffset();
|
|
300
375
|
let end = scanner.getTokenEnd();
|
|
301
|
-
const firstChar =
|
|
376
|
+
const firstChar = htmlText[start];
|
|
302
377
|
if (firstChar === "'" || firstChar === "\"") {
|
|
303
378
|
start++;
|
|
304
379
|
end--;
|
|
@@ -306,8 +381,7 @@ function getDocumentRegions(languageService, document) {
|
|
|
306
381
|
regions.push({
|
|
307
382
|
languageId: attributeLanguageId,
|
|
308
383
|
start,
|
|
309
|
-
end
|
|
310
|
-
attributeValue: true
|
|
384
|
+
end
|
|
311
385
|
});
|
|
312
386
|
}
|
|
313
387
|
}
|
|
@@ -315,58 +389,30 @@ function getDocumentRegions(languageService, document) {
|
|
|
315
389
|
}
|
|
316
390
|
token = scanner.scan();
|
|
317
391
|
}
|
|
392
|
+
const allRegions = mergeRegions(regions, markdownRegions);
|
|
393
|
+
const htmlDocument = isMarkdown ? TextDocument.create(document.uri, "html", document.version, htmlText) : document;
|
|
318
394
|
return {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
getLanguagesInDocument: () => getLanguagesInDocument(document, regions),
|
|
323
|
-
getImportedScripts: () => importedScripts
|
|
395
|
+
getLanguageAtPosition: (position) => getLanguageAtPosition(document, allRegions, position),
|
|
396
|
+
getLanguagesInDocument: () => getLanguagesInDocument(allRegions),
|
|
397
|
+
getHtmlDocument: () => htmlDocument
|
|
324
398
|
};
|
|
325
399
|
}
|
|
326
|
-
|
|
400
|
+
/** The lookups below read the regions in order, so an overlap is dropped: a scanned region always wins over a markdown one. */
|
|
401
|
+
function mergeRegions(scanned, markdown) {
|
|
402
|
+
if (markdown.length === 0) return scanned;
|
|
403
|
+
const merged = [...scanned, ...markdown].toSorted((a, b) => a.start - b.start || b.end - a.end);
|
|
327
404
|
const result = [];
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (!(region.end > currentOffset && region.start < endOffset)) continue;
|
|
333
|
-
const start = Math.max(region.start, currentOffset);
|
|
334
|
-
const startPos = document.positionAt(start);
|
|
335
|
-
if (currentOffset < region.start) result.push({
|
|
336
|
-
start: currentPos,
|
|
337
|
-
end: startPos,
|
|
338
|
-
languageId: "html"
|
|
339
|
-
});
|
|
340
|
-
const end = Math.min(region.end, endOffset);
|
|
341
|
-
const endPos = document.positionAt(end);
|
|
342
|
-
if (end > region.start) result.push({
|
|
343
|
-
start: startPos,
|
|
344
|
-
end: endPos,
|
|
345
|
-
languageId: region.languageId,
|
|
346
|
-
attributeValue: region.attributeValue
|
|
347
|
-
});
|
|
348
|
-
currentOffset = end;
|
|
349
|
-
currentPos = endPos;
|
|
350
|
-
}
|
|
351
|
-
if (currentOffset < endOffset) {
|
|
352
|
-
const endPos = range ? range.end : document.positionAt(endOffset);
|
|
353
|
-
result.push({
|
|
354
|
-
start: currentPos,
|
|
355
|
-
end: endPos,
|
|
356
|
-
languageId: "html"
|
|
357
|
-
});
|
|
405
|
+
for (const region of merged) {
|
|
406
|
+
const previous = result.at(-1);
|
|
407
|
+
if (previous && region.start < previous.end) continue;
|
|
408
|
+
result.push(region);
|
|
358
409
|
}
|
|
359
410
|
return result;
|
|
360
411
|
}
|
|
361
|
-
function getLanguagesInDocument(
|
|
362
|
-
const
|
|
363
|
-
for (const region of regions)
|
|
364
|
-
|
|
365
|
-
result.push(region.languageId);
|
|
366
|
-
if (result.length === 3) return result;
|
|
367
|
-
}
|
|
368
|
-
result.push("html");
|
|
369
|
-
return result;
|
|
412
|
+
function getLanguagesInDocument(regions) {
|
|
413
|
+
const languages = /* @__PURE__ */ new Set(["html"]);
|
|
414
|
+
for (const region of regions) if (region.languageId) languages.add(region.languageId);
|
|
415
|
+
return [...languages];
|
|
370
416
|
}
|
|
371
417
|
function getLanguageAtPosition(document, regions, position) {
|
|
372
418
|
const offset = document.offsetAt(position);
|
|
@@ -375,66 +421,6 @@ function getLanguageAtPosition(document, regions, position) {
|
|
|
375
421
|
} else break;
|
|
376
422
|
return "html";
|
|
377
423
|
}
|
|
378
|
-
function getEmbeddedDocument(document, contents, languageId, shouldIgnoreAttributeValues) {
|
|
379
|
-
let currentPos = 0;
|
|
380
|
-
const oldContent = document.getText();
|
|
381
|
-
let result = "";
|
|
382
|
-
let lastSuffix = "";
|
|
383
|
-
for (const c of contents) {
|
|
384
|
-
if (!(c.languageId === languageId && (!shouldIgnoreAttributeValues || !c.attributeValue))) continue;
|
|
385
|
-
result = substituteWithWhitespace(result, currentPos, c.start, oldContent, lastSuffix, getPrefix(c));
|
|
386
|
-
result += updateContent(c, oldContent.slice(c.start, c.end));
|
|
387
|
-
currentPos = c.end;
|
|
388
|
-
lastSuffix = getSuffix(c);
|
|
389
|
-
}
|
|
390
|
-
result = substituteWithWhitespace(result, currentPos, oldContent.length, oldContent, lastSuffix, "");
|
|
391
|
-
return TextDocument.create(document.uri, languageId, document.version, result);
|
|
392
|
-
}
|
|
393
|
-
function getPrefix(c) {
|
|
394
|
-
if (c.attributeValue) switch (c.languageId) {
|
|
395
|
-
case "css": return "__{";
|
|
396
|
-
}
|
|
397
|
-
return "";
|
|
398
|
-
}
|
|
399
|
-
function getSuffix(c) {
|
|
400
|
-
if (c.attributeValue) switch (c.languageId) {
|
|
401
|
-
case "css": return "}";
|
|
402
|
-
case "javascript": return ";";
|
|
403
|
-
}
|
|
404
|
-
return "";
|
|
405
|
-
}
|
|
406
|
-
function updateContent(c, content) {
|
|
407
|
-
if (!c.attributeValue && c.languageId === "javascript") return content.replace(`<!--`, `/* `).replace(`-->`, ` */`);
|
|
408
|
-
if (c.languageId === "css") return content.replace(/("|")/g, (match, _, offset) => {
|
|
409
|
-
const spaces = " ".repeat(match.length - 1);
|
|
410
|
-
const afterChar = content[offset + match.length];
|
|
411
|
-
if (!afterChar || afterChar.includes(" ")) return `${spaces}"`;
|
|
412
|
-
return `"${spaces}`;
|
|
413
|
-
});
|
|
414
|
-
return content;
|
|
415
|
-
}
|
|
416
|
-
function substituteWithWhitespace(result, start, end, oldContent, before, after) {
|
|
417
|
-
result += before;
|
|
418
|
-
let accumulatedWS = -before.length;
|
|
419
|
-
for (let index = start; index < end; index++) {
|
|
420
|
-
const ch = oldContent[index];
|
|
421
|
-
if (ch === "\n" || ch === "\r") {
|
|
422
|
-
accumulatedWS = 0;
|
|
423
|
-
result += ch;
|
|
424
|
-
} else accumulatedWS++;
|
|
425
|
-
}
|
|
426
|
-
result = append(result, " ", accumulatedWS - after.length);
|
|
427
|
-
result += after;
|
|
428
|
-
return result;
|
|
429
|
-
}
|
|
430
|
-
function append(result, string_, n) {
|
|
431
|
-
while (n > 0) {
|
|
432
|
-
if (n & 1) result += string_;
|
|
433
|
-
n >>= 1;
|
|
434
|
-
string_ += string_;
|
|
435
|
-
}
|
|
436
|
-
return result;
|
|
437
|
-
}
|
|
438
424
|
function getAttributeLanguage(attributeName) {
|
|
439
425
|
const match = attributeName.match(/^(style)$|^(on\w+)$/i);
|
|
440
426
|
if (!match) return null;
|
|
@@ -672,6 +658,9 @@ function getHTMLMode(htmlLanguageService) {
|
|
|
672
658
|
|
|
673
659
|
//#endregion
|
|
674
660
|
//#region src/modes/language-modes.ts
|
|
661
|
+
function isCompletionItemData(value) {
|
|
662
|
+
return value && typeof value.languageId === "string" && typeof value.uri === "string" && typeof value.offset === "number";
|
|
663
|
+
}
|
|
675
664
|
const FILE_PROTOCOL = "staticbolt-server";
|
|
676
665
|
function getLanguageModes(clientCapabilities, requestService) {
|
|
677
666
|
const htmlLanguageService = vscodeHtml.getLanguageService({
|
|
@@ -713,20 +702,13 @@ function getLanguageModes(clientCapabilities, requestService) {
|
|
|
713
702
|
async updateDataProviders(dataProviders) {
|
|
714
703
|
htmlLanguageService.setDataProviders(true, dataProviders);
|
|
715
704
|
},
|
|
705
|
+
getHtmlDocument(document) {
|
|
706
|
+
return documentRegions.get(document).getHtmlDocument();
|
|
707
|
+
},
|
|
716
708
|
getModeAtPosition(document, position) {
|
|
717
709
|
const languageId = documentRegions.get(document).getLanguageAtPosition(position);
|
|
718
710
|
if (languageId) return modes[languageId];
|
|
719
711
|
},
|
|
720
|
-
getModesInRange(document, range) {
|
|
721
|
-
return documentRegions.get(document).getLanguageRanges(range).map((r) => {
|
|
722
|
-
return {
|
|
723
|
-
start: r.start,
|
|
724
|
-
end: r.end,
|
|
725
|
-
mode: r.languageId ? modes[r.languageId] : void 0,
|
|
726
|
-
attributeValue: r.attributeValue
|
|
727
|
-
};
|
|
728
|
-
});
|
|
729
|
-
},
|
|
730
712
|
getAllModesInDocument(document) {
|
|
731
713
|
const result = [];
|
|
732
714
|
for (const languageId of documentRegions.get(document).getLanguagesInDocument()) {
|
|
@@ -735,14 +717,6 @@ function getLanguageModes(clientCapabilities, requestService) {
|
|
|
735
717
|
}
|
|
736
718
|
return result;
|
|
737
719
|
},
|
|
738
|
-
getAllModes() {
|
|
739
|
-
const result = [];
|
|
740
|
-
for (const languageId in modes) {
|
|
741
|
-
const mode = modes[languageId];
|
|
742
|
-
if (mode) result.push(mode);
|
|
743
|
-
}
|
|
744
|
-
return result;
|
|
745
|
-
},
|
|
746
720
|
getMode(languageId) {
|
|
747
721
|
return modes[languageId];
|
|
748
722
|
},
|
|
@@ -884,10 +858,6 @@ function startServer(connection, runtime) {
|
|
|
884
858
|
let workspaceFolders = [];
|
|
885
859
|
let configManager;
|
|
886
860
|
let languageModes;
|
|
887
|
-
const documentSettings = {};
|
|
888
|
-
documents.onDidClose((document) => {
|
|
889
|
-
delete documentSettings[document.document.uri];
|
|
890
|
-
});
|
|
891
861
|
connection.onInitialize((parameters) => {
|
|
892
862
|
if (Array.isArray(parameters.workspaceFolders)) lspSearchRoots = parameters.workspaceFolders;
|
|
893
863
|
else {
|
|
@@ -942,8 +912,9 @@ function startServer(connection, runtime) {
|
|
|
942
912
|
isIncomplete: true,
|
|
943
913
|
items: []
|
|
944
914
|
};
|
|
915
|
+
const htmlDocument = languageModes.getHtmlDocument(document);
|
|
945
916
|
const documentContext = getDocumentContext(document.uri, workspaceFolders);
|
|
946
|
-
return mode.doComplete(
|
|
917
|
+
return mode.doComplete(htmlDocument, textDocumentPosition.position, documentContext, configManager.lspHtmlData);
|
|
947
918
|
}, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, token);
|
|
948
919
|
});
|
|
949
920
|
connection.onCompletionResolve((item, token) => {
|
|
@@ -954,7 +925,7 @@ function startServer(connection, runtime) {
|
|
|
954
925
|
if (!document) return item;
|
|
955
926
|
const mode = languageModes.getMode(data.languageId);
|
|
956
927
|
if (!mode?.doResolve) return item;
|
|
957
|
-
return mode.doResolve(document, item);
|
|
928
|
+
return mode.doResolve(languageModes.getHtmlDocument(document), item);
|
|
958
929
|
}, item, `Error while resolving completion proposal`, token);
|
|
959
930
|
});
|
|
960
931
|
connection.onHover((textDocumentPosition, token) => {
|
|
@@ -966,7 +937,7 @@ function startServer(connection, runtime) {
|
|
|
966
937
|
const mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);
|
|
967
938
|
if (!mode?.doHover) return null;
|
|
968
939
|
if (!await configManager.get(projectRoot)) return null;
|
|
969
|
-
return mode.doHover(document, textDocumentPosition.position, configManager.lspHtmlData);
|
|
940
|
+
return mode.doHover(languageModes.getHtmlDocument(document), textDocumentPosition.position, configManager.lspHtmlData);
|
|
970
941
|
}, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`, token);
|
|
971
942
|
});
|
|
972
943
|
connection.onDocumentLinks((documentLinkParameter, token) => {
|
|
@@ -976,16 +947,14 @@ function startServer(connection, runtime) {
|
|
|
976
947
|
const document = documents.get(documentLinkParameter.textDocument.uri);
|
|
977
948
|
if (!document) return [];
|
|
978
949
|
const links = [];
|
|
950
|
+
const htmlDocument = languageModes.getHtmlDocument(document);
|
|
979
951
|
const documentContext = getDocumentContext(document.uri, workspaceFolders);
|
|
980
|
-
for (const mode of languageModes.getAllModesInDocument(document)) if (mode.findDocumentLinks) pushAll(links, await mode.findDocumentLinks(
|
|
952
|
+
for (const mode of languageModes.getAllModesInDocument(document)) if (mode.findDocumentLinks) pushAll(links, await mode.findDocumentLinks(htmlDocument, documentContext, projectRoot));
|
|
981
953
|
return links;
|
|
982
954
|
}, [], `Error while document links for ${documentLinkParameter.textDocument.uri}`, token);
|
|
983
955
|
});
|
|
984
956
|
connection.listen();
|
|
985
957
|
}
|
|
986
|
-
function isCompletionItemData(value) {
|
|
987
|
-
return value && typeof value.languageId === "string" && typeof value.uri === "string" && typeof value.offset === "number";
|
|
988
|
-
}
|
|
989
958
|
|
|
990
959
|
//#endregion
|
|
991
960
|
//#region src/utils/node-fs.ts
|