wikilint 2.16.2 → 2.16.4
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/coverage/badge.svg +1 -1
- package/data/signatures.json +43 -0
- package/dist/lib/element.d.ts +20 -0
- package/dist/lib/element.js +59 -0
- package/dist/lib/lsp.d.ts +16 -10
- package/dist/lib/lsp.js +331 -268
- package/dist/lib/node.d.ts +1 -1
- package/dist/lib/node.js +16 -9
- package/dist/lib/title.d.ts +5 -0
- package/dist/lib/title.js +29 -0
- package/dist/mixin/attributesParent.js +2 -2
- package/dist/src/heading.js +6 -6
- package/dist/src/html.d.ts +3 -0
- package/dist/src/html.js +197 -146
- package/dist/src/index.d.ts +0 -21
- package/dist/src/index.js +0 -55
- package/dist/src/link/base.d.ts +2 -0
- package/dist/src/link/base.js +8 -4
- package/dist/src/link/category.d.ts +2 -0
- package/dist/src/link/index.d.ts +2 -0
- package/dist/src/link/redirectTarget.d.ts +1 -0
- package/dist/src/magicLink.d.ts +5 -0
- package/dist/src/magicLink.js +22 -0
- package/dist/src/tagPair/ext.d.ts +3 -0
- package/dist/src/tagPair/ext.js +149 -98
- package/dist/src/transclude.d.ts +5 -0
- package/dist/src/transclude.js +24 -3
- package/package.json +3 -2
- package/dist/bin/coverage.js +0 -20
package/dist/lib/lsp.js
CHANGED
|
@@ -5,15 +5,30 @@ const path = require("path");
|
|
|
5
5
|
const common_1 = require("@bhsd/common");
|
|
6
6
|
const sharable_1 = require("../util/sharable");
|
|
7
7
|
const lint_1 = require("../util/lint");
|
|
8
|
+
const string_1 = require("../util/string");
|
|
8
9
|
const index_1 = require("../index");
|
|
9
|
-
|
|
10
|
+
/* NOT FOR BROWSER ONLY END */
|
|
10
11
|
exports.tasks = new WeakMap();
|
|
11
|
-
const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']), nameAttrs = new Set(['name', 'extends', 'follow']), groupAttrs = new Set(['group'])
|
|
12
|
+
const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']), nameAttrs = new Set(['name', 'extends', 'follow']), groupAttrs = new Set(['group']), renameTypes = new Set([
|
|
13
|
+
'arg-name',
|
|
14
|
+
'template-name',
|
|
15
|
+
'magic-word-name',
|
|
16
|
+
'link-target',
|
|
17
|
+
'parameter-key',
|
|
18
|
+
]), referenceTypes = new Set([
|
|
19
|
+
'ext',
|
|
20
|
+
'html',
|
|
21
|
+
'attr-key',
|
|
22
|
+
'image-parameter',
|
|
23
|
+
'heading-title',
|
|
24
|
+
'heading',
|
|
25
|
+
...renameTypes,
|
|
26
|
+
]), plainTypes = new Set(['text', 'comment', 'noinclude', 'include']);
|
|
12
27
|
/**
|
|
13
28
|
* Check if all child nodes are plain text or comments.
|
|
14
29
|
* @param childNodes child nodes
|
|
15
30
|
*/
|
|
16
|
-
const isPlain = (childNodes) => childNodes.every(({ type }) =>
|
|
31
|
+
const isPlain = (childNodes) => childNodes.every(({ type }) => plainTypes.has(type));
|
|
17
32
|
/**
|
|
18
33
|
* Get the position of a character in the document.
|
|
19
34
|
* @param root root token
|
|
@@ -52,8 +67,9 @@ const createNodeRange = (token) => {
|
|
|
52
67
|
* @param pos position
|
|
53
68
|
* @param pos.line line number
|
|
54
69
|
* @param pos.character character number
|
|
70
|
+
* @param extra extra text
|
|
55
71
|
*/
|
|
56
|
-
const getCompletion = (words, kind, mt, { line, character }) => [...new Set(words)].map((w) => ({
|
|
72
|
+
const getCompletion = (words, kind, mt, { line, character }, extra) => [...new Set(words)].map((w) => ({
|
|
57
73
|
label: w,
|
|
58
74
|
kind,
|
|
59
75
|
textEdit: {
|
|
@@ -61,38 +77,19 @@ const getCompletion = (words, kind, mt, { line, character }) => [...new Set(word
|
|
|
61
77
|
start: { line, character: character - mt.length },
|
|
62
78
|
end: { line, character },
|
|
63
79
|
},
|
|
64
|
-
newText: w,
|
|
80
|
+
newText: w + (extra ?? ''),
|
|
65
81
|
},
|
|
66
82
|
}));
|
|
67
83
|
/**
|
|
68
|
-
*
|
|
69
|
-
* @param page page name
|
|
70
|
-
* @param ns namespace
|
|
71
|
-
* @throws `RangeError` Invalid page name.
|
|
72
|
-
* @throws `Error` Article path is not set.
|
|
73
|
-
*/
|
|
74
|
-
const getUrl = (page, ns) => {
|
|
75
|
-
const { title, fragment, valid } = index_1.default.normalizeTitle(page, ns), { articlePath } = index_1.default.getConfig();
|
|
76
|
-
/* istanbul ignore next */
|
|
77
|
-
if (!valid) {
|
|
78
|
-
throw new RangeError('Invalid page name.');
|
|
79
|
-
}
|
|
80
|
-
else if (!articlePath) {
|
|
81
|
-
throw new Error('Article path is not set.');
|
|
82
|
-
}
|
|
83
|
-
const encoded = encodeURIComponent(title) + (fragment === undefined ? '' : `#${encodeURIComponent(fragment)}`);
|
|
84
|
-
return articlePath.includes('$1')
|
|
85
|
-
? articlePath.replace('$1', encoded)
|
|
86
|
-
: articlePath + (articlePath.endsWith('/') ? '' : '/') + encoded;
|
|
87
|
-
};
|
|
88
|
-
/**
|
|
89
|
-
* Get the token at the position from a word.
|
|
84
|
+
* Get the caret position at the position from a word.
|
|
90
85
|
* @param root root token
|
|
91
86
|
* @param pos position
|
|
87
|
+
* @param pos.line line number
|
|
88
|
+
* @param pos.character character number
|
|
92
89
|
*/
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
return root.
|
|
90
|
+
const caretPositionFromWord = (root, { line, character }) => {
|
|
91
|
+
const index = root.indexFromPos(line, character);
|
|
92
|
+
return root.caretPositionFromIndex(index + Number(/\w/u.test(root.toString().charAt(index))));
|
|
96
93
|
};
|
|
97
94
|
/**
|
|
98
95
|
* Get the attribute of a `<ref>` tag.
|
|
@@ -114,6 +111,15 @@ const getRefName = (token) => getRefAttr(token, refTags, nameAttrs);
|
|
|
114
111
|
* @param token `group` attribute token
|
|
115
112
|
*/
|
|
116
113
|
const getRefGroup = (token) => getRefAttr(token, referencesTags, groupAttrs);
|
|
114
|
+
/**
|
|
115
|
+
* Get the attribute of a `<ref>` tag.
|
|
116
|
+
* @param token extension token
|
|
117
|
+
* @param target attribute name
|
|
118
|
+
*/
|
|
119
|
+
const getRefTagAttr = (token, target) => {
|
|
120
|
+
const attr = token?.getAttr(target);
|
|
121
|
+
return attr !== true && attr || false;
|
|
122
|
+
};
|
|
117
123
|
/**
|
|
118
124
|
* Get the effective name of a token.
|
|
119
125
|
* @param token
|
|
@@ -136,6 +142,18 @@ const getName = (token) => {
|
|
|
136
142
|
}
|
|
137
143
|
};
|
|
138
144
|
/* NOT FOR BROWSER ONLY */
|
|
145
|
+
/**
|
|
146
|
+
* Get the quick fix data.
|
|
147
|
+
* @param root root token
|
|
148
|
+
* @param fix lint error fix
|
|
149
|
+
* @param preferred whether it is a preferred fix
|
|
150
|
+
*/
|
|
151
|
+
const getQuickFix = (root, fix, preferred = false) => ({
|
|
152
|
+
range: createRange(root, ...fix.range),
|
|
153
|
+
newText: fix.text,
|
|
154
|
+
title: `${preferred ? 'Fix' : 'Suggestion'}: ${fix.desc}`,
|
|
155
|
+
fix: preferred,
|
|
156
|
+
});
|
|
139
157
|
/**
|
|
140
158
|
* Get the end position of a section.
|
|
141
159
|
* @param section section
|
|
@@ -178,7 +196,7 @@ class LanguageService {
|
|
|
178
196
|
* - 否则开始新的解析
|
|
179
197
|
*/
|
|
180
198
|
async #queue(text) {
|
|
181
|
-
text =
|
|
199
|
+
text = (0, string_1.tidy)(text);
|
|
182
200
|
if (this.#text === text && this.#config === index_1.default.config && !this.#running) {
|
|
183
201
|
return this.#done;
|
|
184
202
|
}
|
|
@@ -235,7 +253,7 @@ class LanguageService {
|
|
|
235
253
|
if (type !== 'attr-value' && !isPlain(childNodes)) {
|
|
236
254
|
return [];
|
|
237
255
|
}
|
|
238
|
-
return childNodes.filter((child) => child.type === 'text')
|
|
256
|
+
return childNodes.filter((child) => child.type === 'text').reverse()
|
|
239
257
|
.flatMap(child => {
|
|
240
258
|
const parts = (0, common_1.splitColors)(child.data, hsl).filter(([, , , isColor]) => isColor);
|
|
241
259
|
if (parts.length === 0) {
|
|
@@ -273,7 +291,7 @@ class LanguageService {
|
|
|
273
291
|
if (!this.#completionConfig) {
|
|
274
292
|
const { nsid, ext, html, parserFunction: [insensitive, sensitive, ...other], doubleUnderscore, protocol, img, } = index_1.default.getConfig(), tags = new Set([ext, html].flat(2));
|
|
275
293
|
const re = new RegExp('(?:' // eslint-disable-line prefer-template
|
|
276
|
-
+ String.raw
|
|
294
|
+
+ String.raw `<(\/?\w*)` // tag
|
|
277
295
|
+ '|'
|
|
278
296
|
+ String.raw `(\{{2,4}|\[\[)\s*([^|{}<>[\]\s][^|{}<>[\]#]*)?` // braces and brackets
|
|
279
297
|
+ '|'
|
|
@@ -311,9 +329,10 @@ class LanguageService {
|
|
|
311
329
|
*/
|
|
312
330
|
async provideCompletionItems(text, position) {
|
|
313
331
|
this.#checkSignature();
|
|
314
|
-
const { re, allTags, functions, switches, protocols, params, tags, ext } = this.#prepareCompletionConfig(), { line, character } = position,
|
|
332
|
+
const { re, allTags, functions, switches, protocols, params, tags, ext } = this.#prepareCompletionConfig(), { line, character } = position, curLine = text.split(/\r?\n/u, line + 1)[line], mt = re.exec(curLine?.slice(0, character) ?? '');
|
|
315
333
|
if (mt?.[1] !== undefined) { // tag
|
|
316
|
-
|
|
334
|
+
const closing = mt[1].startsWith('/');
|
|
335
|
+
return getCompletion(allTags, 'Class', mt[1].slice(closing ? 1 : 0), position, closing && !curLine?.slice(character).trim().startsWith('>') ? '>' : '');
|
|
317
336
|
}
|
|
318
337
|
else if (mt?.[4]) { // behavior switch
|
|
319
338
|
return getCompletion(switches, 'Constant', mt[4], position);
|
|
@@ -322,34 +341,53 @@ class LanguageService {
|
|
|
322
341
|
return getCompletion(protocols, 'Reference', mt[5], position);
|
|
323
342
|
}
|
|
324
343
|
const root = await this.#queue(text);
|
|
344
|
+
let cur;
|
|
325
345
|
if (mt?.[2]) {
|
|
346
|
+
cur = root.elementFromPoint(mt.index + mt[2].length - 1, line);
|
|
326
347
|
const match = mt[3] ?? '';
|
|
327
348
|
if (mt[2] === '{{{') { // argument
|
|
328
|
-
return getCompletion(root.querySelectorAll('arg').
|
|
349
|
+
return getCompletion(root.querySelectorAll('arg').filter(token => token.name && token !== cur)
|
|
350
|
+
.map(({ name }) => name), 'Variable', match, position);
|
|
329
351
|
}
|
|
330
352
|
const colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
|
|
331
353
|
return mt[2] === '[['
|
|
332
354
|
? getCompletion(// link
|
|
333
|
-
root.querySelectorAll('link,file,category,redirect-target').map(({ name }) => name), 'Folder', str, position)
|
|
355
|
+
root.querySelectorAll('link,file,category,redirect-target').filter(token => token !== cur).map(({ name }) => name), 'Folder', str, position)
|
|
334
356
|
: [
|
|
335
357
|
...getCompletion(functions, 'Function', match, position),
|
|
336
358
|
...match.startsWith('#')
|
|
337
359
|
? []
|
|
338
|
-
: getCompletion(root.querySelectorAll('template')
|
|
339
|
-
.map(
|
|
360
|
+
: getCompletion(root.querySelectorAll('template').filter(token => token !== cur)
|
|
361
|
+
.map(token => {
|
|
362
|
+
const { name } = token;
|
|
363
|
+
if (colon) {
|
|
364
|
+
return name;
|
|
365
|
+
}
|
|
366
|
+
const { ns } = token.getAttribute('title');
|
|
367
|
+
if (ns === 0) {
|
|
368
|
+
return `:${name}`;
|
|
369
|
+
}
|
|
370
|
+
return ns === 10 ? name.slice(9) : name;
|
|
371
|
+
}), 'Folder', str, position),
|
|
340
372
|
];
|
|
341
373
|
}
|
|
342
|
-
|
|
374
|
+
let type, parentNode;
|
|
375
|
+
if (mt?.[7] === undefined) {
|
|
376
|
+
cur = root.elementFromPoint(character, line);
|
|
377
|
+
({ type, parentNode } = cur);
|
|
378
|
+
}
|
|
343
379
|
if (mt?.[6] !== undefined || type === 'image-parameter') { // image parameter
|
|
344
|
-
const match = mt?.[6]?.trimStart()
|
|
345
|
-
?? this.#text.slice(
|
|
380
|
+
const index = root.indexFromPos(line, character), match = mt?.[6]?.trimStart()
|
|
381
|
+
?? this.#text.slice(cur.getAbsoluteIndex(), index).trimStart(), equal = this.#text.charAt(index) === '=';
|
|
346
382
|
return [
|
|
347
|
-
...getCompletion(params, 'Property', match, position)
|
|
348
|
-
|
|
383
|
+
...getCompletion(params, 'Property', match, position)
|
|
384
|
+
.filter(({ label }) => !equal || !/[= ]$/u.test(label)),
|
|
385
|
+
...getCompletion(root.querySelectorAll('image-parameter#width').filter(token => token !== cur)
|
|
386
|
+
.map(width => width.text()), 'Unit', match, position),
|
|
349
387
|
];
|
|
350
388
|
}
|
|
351
389
|
else if (mt?.[7] !== undefined || type === 'attr-key') { // attribute key
|
|
352
|
-
const tag = mt?.[7]?.toLowerCase() ?? parentNode.tag, key = mt?.[9] ??
|
|
390
|
+
const tag = mt?.[7]?.toLowerCase() ?? parentNode.tag, key = mt?.[9] ?? cur.toString();
|
|
353
391
|
if (!tags.has(tag)) {
|
|
354
392
|
return undefined;
|
|
355
393
|
}
|
|
@@ -368,12 +406,27 @@ class LanguageService {
|
|
|
368
406
|
...getCompletion(['xmlns:'], 'Interface', key, position),
|
|
369
407
|
];
|
|
370
408
|
}
|
|
371
|
-
else if (
|
|
372
|
-
|
|
373
|
-
const
|
|
409
|
+
else if (type === 'parameter-key' || type === 'parameter-value' && parentNode.anon) {
|
|
410
|
+
// parameter key
|
|
411
|
+
const transclusion = parentNode.parentNode;
|
|
412
|
+
if (transclusion.type === 'magic-word' && transclusion.name !== 'invoke') {
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
const key = cur.toString().trimStart(), [module, func] = transclusion.type === 'magic-word' ? transclusion.getModule() : [];
|
|
374
416
|
return key
|
|
375
|
-
? getCompletion(root.querySelectorAll('parameter').filter(
|
|
376
|
-
|
|
417
|
+
? getCompletion(root.querySelectorAll('parameter').filter(token => {
|
|
418
|
+
if (token === parentNode
|
|
419
|
+
|| token.anon
|
|
420
|
+
|| token.parentNode.type !== transclusion.type
|
|
421
|
+
|| token.parentNode.name !== transclusion.name) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
else if (transclusion.type === 'template') {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
const [m, f] = token.parentNode.getModule();
|
|
428
|
+
return m === module && f === func;
|
|
429
|
+
}).map(({ name }) => name), 'Variable', key, position, type === 'parameter-value' ? '=' : '')
|
|
377
430
|
: undefined;
|
|
378
431
|
}
|
|
379
432
|
return undefined;
|
|
@@ -396,86 +449,41 @@ class LanguageService {
|
|
|
396
449
|
source: 'WikiLint',
|
|
397
450
|
code: rule,
|
|
398
451
|
message,
|
|
452
|
+
/* NOT FOR BROWSER ONLY */
|
|
399
453
|
data: [
|
|
400
|
-
...fix
|
|
401
|
-
|
|
402
|
-
{
|
|
403
|
-
range: createRange(root, ...fix.range),
|
|
404
|
-
newText: fix.text,
|
|
405
|
-
title: `Fix: ${fix.desc}`,
|
|
406
|
-
fix: true,
|
|
407
|
-
},
|
|
408
|
-
]
|
|
409
|
-
: [],
|
|
410
|
-
...suggestions
|
|
411
|
-
? suggestions.map(({ range, text, desc }) => ({
|
|
412
|
-
range: createRange(root, ...range),
|
|
413
|
-
newText: text,
|
|
414
|
-
title: `Suggestion: ${desc}`,
|
|
415
|
-
fix: false,
|
|
416
|
-
}))
|
|
417
|
-
: [],
|
|
454
|
+
...fix ? [getQuickFix(root, fix, true)] : [],
|
|
455
|
+
...suggestions ? suggestions.map(suggestion => getQuickFix(root, suggestion)) : [],
|
|
418
456
|
],
|
|
419
457
|
}));
|
|
420
458
|
}
|
|
421
|
-
|
|
459
|
+
/**
|
|
460
|
+
* 提供折叠范围
|
|
461
|
+
* @param text 源代码
|
|
462
|
+
*/
|
|
463
|
+
async provideFoldingRanges(text) {
|
|
422
464
|
this.#checkSignature();
|
|
423
|
-
const ranges = [],
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
root = await this.#queue(text), lines = root.getLines(), { length } = lines, levels = new Array(6), tokens = root.querySelectorAll(fold ? 'heading-title,table,template,magic-word' : 'heading-title');
|
|
465
|
+
const root = await this.#queue(text), { length } = root.getLines(), ranges = [], levels = new Array(6), tokens = root.querySelectorAll('heading-title,table,template,magic-word');
|
|
466
|
+
for (const token of [...tokens].reverse()) { // 提高 getBoundingClientRect 的性能
|
|
467
|
+
token.getRelativeIndex();
|
|
468
|
+
}
|
|
428
469
|
for (const token of tokens) {
|
|
429
|
-
const { top, height
|
|
430
|
-
/* NOT FOR BROWSER ONLY */
|
|
431
|
-
left, width, } = token.getBoundingClientRect();
|
|
470
|
+
const { top, height } = token.getBoundingClientRect();
|
|
432
471
|
if (token.type === 'heading-title') {
|
|
433
472
|
const { level } = token.parentNode;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
levels[i] = undefined;
|
|
473
|
+
for (let i = level - 1; i < 6; i++) {
|
|
474
|
+
const startLine = levels[i];
|
|
475
|
+
if (startLine !== undefined && startLine < top - 1) {
|
|
476
|
+
ranges.push({
|
|
477
|
+
startLine,
|
|
478
|
+
endLine: top - 1,
|
|
479
|
+
kind: 'region',
|
|
480
|
+
});
|
|
445
481
|
}
|
|
446
|
-
levels[
|
|
447
|
-
/* NOT FOR BROWSER ONLY */
|
|
448
|
-
}
|
|
449
|
-
else {
|
|
450
|
-
for (let i = level - 1; i < 6; i++) {
|
|
451
|
-
getSectionEnd(sections[i], lines, top - 1);
|
|
452
|
-
sections[i] = undefined;
|
|
453
|
-
}
|
|
454
|
-
const section = token.text().trim() || ' ', name = names.has(section)
|
|
455
|
-
? new Array(names.size).fill('').map((_, i) => `${section.trim()}_${i + 2}`)
|
|
456
|
-
.find(s => !names.has(s))
|
|
457
|
-
: section, container = sections.slice(0, level - 1).reverse().find(Boolean), selectionRange = {
|
|
458
|
-
start: { line: top, character: left - level },
|
|
459
|
-
end: (0, lint_1.getEndPos)(top, left, height, width + level),
|
|
460
|
-
}, info = {
|
|
461
|
-
name,
|
|
462
|
-
kind: 15,
|
|
463
|
-
range: { start: selectionRange.start },
|
|
464
|
-
selectionRange,
|
|
465
|
-
};
|
|
466
|
-
names.add(name);
|
|
467
|
-
sections[level - 1] = info;
|
|
468
|
-
if (container) {
|
|
469
|
-
container.children ??= [];
|
|
470
|
-
container.children.push(info);
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
symbols.push(info);
|
|
474
|
-
}
|
|
475
|
-
/* NOT FOR BROWSER ONLY END */
|
|
482
|
+
levels[i] = undefined;
|
|
476
483
|
}
|
|
484
|
+
levels[level - 1] = top + height - 1; // 从标题的最后一行开始折叠
|
|
477
485
|
}
|
|
478
|
-
else if (
|
|
486
|
+
else if (height > 2) {
|
|
479
487
|
ranges.push({
|
|
480
488
|
startLine: top, // 从表格或模板的第一行开始折叠
|
|
481
489
|
endLine: top + height - 2,
|
|
@@ -483,32 +491,16 @@ class LanguageService {
|
|
|
483
491
|
});
|
|
484
492
|
}
|
|
485
493
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
/* NOT FOR BROWSER ONLY */
|
|
497
|
-
}
|
|
498
|
-
else {
|
|
499
|
-
for (const section of sections) {
|
|
500
|
-
getSectionEnd(section, lines, length - 1);
|
|
494
|
+
for (const startLine of levels) {
|
|
495
|
+
if (startLine !== undefined && startLine < length - 1) {
|
|
496
|
+
ranges.push({
|
|
497
|
+
startLine,
|
|
498
|
+
endLine: length - 1,
|
|
499
|
+
kind: 'region',
|
|
500
|
+
});
|
|
501
501
|
}
|
|
502
|
-
/* NOT FOR BROWSER ONLY END */
|
|
503
502
|
}
|
|
504
|
-
return
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* 提供折叠范围
|
|
508
|
-
* @param text 源代码
|
|
509
|
-
*/
|
|
510
|
-
async provideFoldingRanges(text) {
|
|
511
|
-
return this.#provideFoldingRangesOrDocumentSymbols(text);
|
|
503
|
+
return ranges;
|
|
512
504
|
}
|
|
513
505
|
/**
|
|
514
506
|
* 提供链接
|
|
@@ -516,109 +508,79 @@ class LanguageService {
|
|
|
516
508
|
*/
|
|
517
509
|
async provideLinks(text) {
|
|
518
510
|
this.#checkSignature();
|
|
519
|
-
const protocolRegex = new RegExp(`^(?:${
|
|
520
|
-
|
|
521
|
-
|
|
511
|
+
const { articlePath, protocol } = index_1.default.getConfig(), absolute = articlePath?.includes('//'), protocolRegex = new RegExp(`^(?:${protocol}|//)`, 'iu');
|
|
512
|
+
return (await this.#queue(text))
|
|
513
|
+
.querySelectorAll(`magic-link,ext-link-url,free-ext-link,attr-value,image-parameter#link${absolute ? ',link-target,template-name,invoke-module' : ''}`).reverse()
|
|
514
|
+
.map((token) => {
|
|
522
515
|
const { type, parentNode, firstChild, lastChild, childNodes } = token, { name, tag } = parentNode;
|
|
523
516
|
if (!(type !== 'attr-value'
|
|
524
517
|
|| name === 'src' && ['templatestyles', 'img'].includes(tag)
|
|
525
518
|
|| name === 'cite' && ['blockquote', 'del', 'ins', 'q'].includes(tag))
|
|
526
519
|
|| !isPlain(childNodes)) {
|
|
527
|
-
return
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
let target = childNodes.filter((node) => node.type === 'text')
|
|
523
|
+
.map(({ data }) => data).join('').trim();
|
|
524
|
+
if (!target) {
|
|
525
|
+
return false;
|
|
528
526
|
}
|
|
529
|
-
let target = type === 'image-parameter'
|
|
530
|
-
? element_1.AstElement.prototype.toString.call(token, true).trim()
|
|
531
|
-
: token.toString(true).trim();
|
|
532
527
|
try {
|
|
533
|
-
if (
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
528
|
+
if (token.is('magic-link')
|
|
529
|
+
|| token.is('ext-link-url')
|
|
530
|
+
|| token.is('free-ext-link')) {
|
|
531
|
+
target = token.getUrl(articlePath);
|
|
532
|
+
}
|
|
533
|
+
else if (type === 'link-target' && (parentNode.is('link')
|
|
534
|
+
|| parentNode.is('redirect-target')
|
|
535
|
+
|| parentNode.is('category'))) {
|
|
536
|
+
if (target.startsWith('/')) {
|
|
537
|
+
return false;
|
|
541
538
|
}
|
|
539
|
+
target = parentNode.link.getUrl(articlePath);
|
|
542
540
|
}
|
|
543
|
-
else if (
|
|
541
|
+
else if (type === 'template-name') {
|
|
542
|
+
target = parentNode.getAttribute('title').getUrl(articlePath);
|
|
543
|
+
}
|
|
544
|
+
else if (['link-target', 'invoke-module'].includes(type)
|
|
544
545
|
|| type === 'attr-value' && name === 'src' && tag === 'templatestyles'
|
|
545
546
|
|| type === 'image-parameter' && !protocolRegex.test(target)) {
|
|
546
|
-
if (target.startsWith('/')) {
|
|
547
|
-
return
|
|
547
|
+
if (!absolute || target.startsWith('/')) {
|
|
548
|
+
return false;
|
|
548
549
|
}
|
|
549
550
|
let ns = 0;
|
|
550
|
-
if (type === '
|
|
551
|
+
if (type === 'attr-value') {
|
|
551
552
|
ns = 10;
|
|
552
553
|
}
|
|
553
554
|
else if (type === 'invoke-module') {
|
|
554
555
|
ns = 828;
|
|
555
556
|
}
|
|
556
|
-
|
|
557
|
+
const title = index_1.default.normalizeTitle(target, ns);
|
|
558
|
+
/* istanbul ignore if */
|
|
559
|
+
if (!title.valid) {
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
target = title.getUrl();
|
|
557
563
|
}
|
|
558
|
-
if (target.startsWith('//')) {
|
|
564
|
+
if (typeof target === 'string' && target.startsWith('//')) {
|
|
559
565
|
target = `https:${target}`;
|
|
560
566
|
}
|
|
561
567
|
target = new URL(target).href;
|
|
562
568
|
if (type === 'image-parameter') {
|
|
563
|
-
const
|
|
564
|
-
return
|
|
565
|
-
{
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
end: (0, lint_1.getEndPos)(top, left, height, width),
|
|
569
|
-
},
|
|
570
|
-
target,
|
|
569
|
+
const { top, left, height, width } = lastChild.getBoundingClientRect(), rect = firstChild.getBoundingClientRect();
|
|
570
|
+
return {
|
|
571
|
+
range: {
|
|
572
|
+
start: { line: rect.top, character: rect.left },
|
|
573
|
+
end: (0, lint_1.getEndPos)(top, left, height, width),
|
|
571
574
|
},
|
|
572
|
-
|
|
575
|
+
target,
|
|
576
|
+
};
|
|
573
577
|
}
|
|
574
|
-
return
|
|
578
|
+
return { range: createNodeRange(token), target };
|
|
575
579
|
}
|
|
576
580
|
catch {
|
|
577
|
-
return
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
async #provideReferencesOrDefinition(text, position, usage) {
|
|
582
|
-
this.#checkSignature();
|
|
583
|
-
const renameTypes = [
|
|
584
|
-
'arg-name',
|
|
585
|
-
'template-name',
|
|
586
|
-
'magic-word-name',
|
|
587
|
-
'link-target',
|
|
588
|
-
'parameter-key',
|
|
589
|
-
], types = [
|
|
590
|
-
'ext',
|
|
591
|
-
'html',
|
|
592
|
-
'attr-key',
|
|
593
|
-
'image-parameter',
|
|
594
|
-
'heading-title',
|
|
595
|
-
'heading',
|
|
596
|
-
...renameTypes,
|
|
597
|
-
], root = await this.#queue(text), node = elementFromWord(root, position), { type } = node, refName = getRefName(node), refGroup = getRefGroup(node);
|
|
598
|
-
if (usage === 2 && type === 'parameter-key' && /^[1-9]\d*$/u.test(node.parentNode.name)
|
|
599
|
-
|| !refName && (usage === 1
|
|
600
|
-
|| !refGroup && (usage === 0
|
|
601
|
-
? !types.includes(type)
|
|
602
|
-
: !renameTypes.includes(type)
|
|
603
|
-
|| type === 'link-target' && !['link', 'redirect-target'].includes(node.parentNode.type)))) {
|
|
604
|
-
return undefined;
|
|
605
|
-
}
|
|
606
|
-
else if (usage === 2) {
|
|
607
|
-
return createNodeRange(node);
|
|
608
|
-
}
|
|
609
|
-
const name = getName(node), refs = root.querySelectorAll(type === 'heading-title' ? 'heading' : type).filter(token => {
|
|
610
|
-
const { name: n, parentNode } = token.parentNode;
|
|
611
|
-
if (usage === 1) {
|
|
612
|
-
return getRefName(token) === refName
|
|
613
|
-
&& n === 'name' && parentNode.parentNode.innerText;
|
|
581
|
+
return false;
|
|
614
582
|
}
|
|
615
|
-
|
|
616
|
-
? getRefName(token) === refName || getRefGroup(token) === refGroup
|
|
617
|
-
: getName(token) === name;
|
|
618
|
-
}).map((token) => ({
|
|
619
|
-
range: createNodeRange(token.type === 'parameter-key' ? token.parentNode : token),
|
|
620
|
-
}));
|
|
621
|
-
return refs.length === 0 ? undefined : refs;
|
|
583
|
+
}).filter(Boolean);
|
|
622
584
|
}
|
|
623
585
|
/**
|
|
624
586
|
* 提供引用
|
|
@@ -626,53 +588,87 @@ class LanguageService {
|
|
|
626
588
|
* @param position 位置
|
|
627
589
|
*/
|
|
628
590
|
async provideReferences(text, position) {
|
|
629
|
-
|
|
591
|
+
this.#checkSignature();
|
|
592
|
+
const root = await this.#queue(text), { offsetNode, offset } = caretPositionFromWord(root, position), element = offsetNode.type === 'text' ? offsetNode.parentNode : offsetNode, node = offset === 0 && (element.type === 'ext-attr-dirty' || element.type === 'html-attr-dirty')
|
|
593
|
+
? element.parentNode.parentNode
|
|
594
|
+
: element, { type } = node, refName = getRefName(node), refGroup = getRefGroup(node);
|
|
595
|
+
if (!refName && !refGroup && !referenceTypes.has(type)) {
|
|
596
|
+
return undefined;
|
|
597
|
+
}
|
|
598
|
+
const name = getName(node), refs = root.querySelectorAll(type === 'heading-title' ? 'heading' : type).filter(token => type === 'attr-value'
|
|
599
|
+
? getRefName(token) === refName || getRefGroup(token) === refGroup
|
|
600
|
+
: getName(token) === name).map((token) => ({
|
|
601
|
+
range: createNodeRange(token.type === 'parameter-key' ? token.parentNode : token),
|
|
602
|
+
}));
|
|
603
|
+
return refs.length === 0 ? undefined : refs;
|
|
630
604
|
}
|
|
631
605
|
/**
|
|
632
606
|
* 提供定义
|
|
633
607
|
* @param text 源代码
|
|
634
|
-
* @param
|
|
608
|
+
* @param pos 位置
|
|
609
|
+
* @param pos.line 行号
|
|
610
|
+
* @param pos.character 列号
|
|
635
611
|
*/
|
|
636
|
-
async provideDefinition(text,
|
|
637
|
-
|
|
612
|
+
async provideDefinition(text, { line, character }) {
|
|
613
|
+
this.#checkSignature();
|
|
614
|
+
const root = await this.#queue(text), node = root.elementFromPoint(character, line), ext = node.is('ext') && node.name === 'ref' ? node : node.closest('ext#ref'), refName = getRefTagAttr(ext, 'name');
|
|
615
|
+
if (!refName) {
|
|
616
|
+
return undefined;
|
|
617
|
+
}
|
|
618
|
+
const refGroup = getRefTagAttr(ext, 'group'), refs = root.querySelectorAll('ext#ref').filter(token => token.innerText
|
|
619
|
+
&& getRefTagAttr(token, 'name') === refName
|
|
620
|
+
&& getRefTagAttr(token, 'group') === refGroup).map(({ lastChild }) => ({
|
|
621
|
+
range: createNodeRange(lastChild),
|
|
622
|
+
}));
|
|
623
|
+
return refs.length === 0 ? undefined : refs;
|
|
638
624
|
}
|
|
639
625
|
/**
|
|
640
626
|
* 提供变量更名准备
|
|
641
627
|
* @param text 源代码
|
|
642
|
-
* @param
|
|
628
|
+
* @param pos 位置
|
|
629
|
+
* @param pos.line 行号
|
|
630
|
+
* @param pos.character 列号
|
|
643
631
|
*/
|
|
644
|
-
async resolveRenameLocation(text,
|
|
645
|
-
|
|
632
|
+
async resolveRenameLocation(text, { line, character }) {
|
|
633
|
+
this.#checkSignature();
|
|
634
|
+
const root = await this.#queue(text), node = root.elementFromPoint(character, line), { type } = node, refName = getRefName(node), refGroup = getRefGroup(node);
|
|
635
|
+
return !refName && !refGroup && (!renameTypes.has(type)
|
|
636
|
+
|| type === 'parameter-key' && /^[1-9]\d*$/u.test(node.parentNode.name)
|
|
637
|
+
|| type === 'link-target' && !['link', 'redirect-target'].includes(node.parentNode.type))
|
|
638
|
+
? undefined
|
|
639
|
+
: createNodeRange(node);
|
|
646
640
|
}
|
|
647
641
|
/**
|
|
648
642
|
* 变量更名
|
|
649
643
|
* @param text 源代码
|
|
650
|
-
* @param
|
|
644
|
+
* @param pos 位置
|
|
645
|
+
* @param pos.line 行号
|
|
646
|
+
* @param pos.character 列号
|
|
651
647
|
* @param newName 新名称
|
|
652
648
|
*/
|
|
653
|
-
async provideRenameEdits(text,
|
|
649
|
+
async provideRenameEdits(text, { line, character }, newName) {
|
|
654
650
|
this.#checkSignature();
|
|
655
|
-
const root = await this.#queue(text), node =
|
|
656
|
-
const name = getName(node), refs = root.querySelectorAll(type).filter(token => {
|
|
651
|
+
const root = await this.#queue(text), node = root.elementFromPoint(character, line), { type } = node, refName = getRefName(node), refNameGroup = refName && getRefTagAttr(node.parentNode.parentNode, 'group'), refGroup = getRefGroup(node), name = getName(node), refs = root.querySelectorAll(type).filter(token => {
|
|
657
652
|
const { type: t } = token.parentNode;
|
|
658
653
|
if (type === 'link-target' && t !== 'link' && t !== 'redirect-target') {
|
|
659
654
|
return false;
|
|
660
655
|
}
|
|
661
656
|
return type === 'attr-value'
|
|
662
|
-
?
|
|
657
|
+
? getRefGroup(token) === refGroup
|
|
658
|
+
|| getRefName(token) === refName
|
|
659
|
+
&& getRefTagAttr(token.parentNode.parentNode, 'group') === refNameGroup
|
|
663
660
|
: getName(token) === name;
|
|
664
661
|
});
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
};
|
|
662
|
+
return refs.length === 0
|
|
663
|
+
? undefined
|
|
664
|
+
: {
|
|
665
|
+
changes: {
|
|
666
|
+
'': refs.map((ref) => ({
|
|
667
|
+
range: createNodeRange(ref),
|
|
668
|
+
newText: newName,
|
|
669
|
+
})),
|
|
670
|
+
},
|
|
671
|
+
};
|
|
676
672
|
}
|
|
677
673
|
/**
|
|
678
674
|
* 检索解析器函数
|
|
@@ -692,14 +688,33 @@ class LanguageService {
|
|
|
692
688
|
return undefined;
|
|
693
689
|
}
|
|
694
690
|
this.#checkSignature();
|
|
695
|
-
const
|
|
696
|
-
let info, f;
|
|
691
|
+
const root = await this.#queue(text), { offsetNode, offset } = caretPositionFromWord(root, position), token = offsetNode.type === 'text' ? offsetNode.parentNode : offsetNode, { type, parentNode, length, name } = token;
|
|
692
|
+
let info, f, range;
|
|
697
693
|
if (token.is('double-underscore')) {
|
|
694
|
+
if (offset === 0 && token.getBoundingClientRect().left > position.character) {
|
|
695
|
+
return undefined;
|
|
696
|
+
}
|
|
698
697
|
info = this.data.behaviorSwitches.find(({ aliases }) => aliases.includes(token.innerText.toLowerCase()));
|
|
699
698
|
}
|
|
700
|
-
else if (
|
|
701
|
-
info = this.#getParserFunction(
|
|
702
|
-
f = token.
|
|
699
|
+
else if (type === 'magic-word-name') {
|
|
700
|
+
info = this.#getParserFunction(parentNode.name);
|
|
701
|
+
f = token.toString(true).trim();
|
|
702
|
+
}
|
|
703
|
+
else if (token.is('magic-word') && length === 1 && !token.modifier) {
|
|
704
|
+
info = this.#getParserFunction(name);
|
|
705
|
+
f = token.firstChild.toString(true).trim();
|
|
706
|
+
}
|
|
707
|
+
else if ((token.is('magic-word') || token.is('template'))
|
|
708
|
+
&& token.modifier && offset >= 2 && token.getRelativeIndex(0) > offset) {
|
|
709
|
+
f = token.modifier.trim().slice(0, -1);
|
|
710
|
+
info = this.#getParserFunction(f.toLowerCase());
|
|
711
|
+
if (info) {
|
|
712
|
+
const aIndex = token.getAbsoluteIndex();
|
|
713
|
+
range = {
|
|
714
|
+
start: positionAt(root, aIndex + 2),
|
|
715
|
+
end: positionAt(root, aIndex + token.modifier.trimEnd().length + 1),
|
|
716
|
+
};
|
|
717
|
+
}
|
|
703
718
|
}
|
|
704
719
|
return info && {
|
|
705
720
|
contents: {
|
|
@@ -709,7 +724,7 @@ class LanguageService {
|
|
|
709
724
|
: '')
|
|
710
725
|
+ info.description,
|
|
711
726
|
},
|
|
712
|
-
range: createNodeRange(token),
|
|
727
|
+
range: range ?? createNodeRange(token),
|
|
713
728
|
};
|
|
714
729
|
}
|
|
715
730
|
/**
|
|
@@ -727,32 +742,41 @@ class LanguageService {
|
|
|
727
742
|
throw new Error('This is a regular language server!');
|
|
728
743
|
}
|
|
729
744
|
this.#signature = true;
|
|
730
|
-
const { line, character } = position, curLine = text.split(/\r?\n/u, line + 1)[line], { lastChild } = await this.#queue(`${curLine.slice(0, character + /^[^{}<]*/u.exec(curLine.slice(character))[0].length)}}}`)
|
|
731
|
-
if (
|
|
745
|
+
const { line, character } = position, curLine = text.split(/\r?\n/u, line + 1)[line], { lastChild } = await this.#queue(`${curLine.slice(0, character + /^[^{}<]*/u.exec(curLine.slice(character))[0].length)}}}`);
|
|
746
|
+
if (!lastChild.is('magic-word') || lastChild.length === 1) {
|
|
732
747
|
return undefined;
|
|
733
748
|
}
|
|
734
|
-
const info = this.#getParserFunction(name);
|
|
749
|
+
const { name, childNodes, firstChild } = lastChild, info = this.#getParserFunction(name);
|
|
735
750
|
if (!info?.signatures) {
|
|
736
751
|
return undefined;
|
|
737
752
|
}
|
|
738
|
-
const
|
|
739
|
-
let activeParameter = childNodes.findIndex(child => child.getRelativeIndex() > character - start) - 2;
|
|
740
|
-
if (activeParameter === -3) {
|
|
741
|
-
activeParameter = n - 1;
|
|
742
|
-
}
|
|
743
|
-
const signatures = info.signatures.filter(params => (params.length >= n || params[params.length - 1]?.rest)
|
|
753
|
+
const n = childNodes.length - 1, candidates = info.signatures.filter(params => (params.length >= n || params[params.length - 1]?.rest)
|
|
744
754
|
&& params.every(({ label, const: c }, i) => {
|
|
745
|
-
const p = c && i < n && childNodes[i + 1]?.
|
|
746
|
-
return !p || label.
|
|
747
|
-
}))
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
755
|
+
const p = c && i < n && childNodes[i + 1]?.toString(true).trim();
|
|
756
|
+
return !p || label.toLowerCase().includes(p.toLowerCase());
|
|
757
|
+
}));
|
|
758
|
+
if (candidates.length === 0) {
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
let j = -1;
|
|
762
|
+
for (let cur = lastChild.getAbsoluteIndex() + lastChild.getAttribute('padding'); j < n; j++) {
|
|
763
|
+
cur += childNodes[j + 1].toString().length + 1;
|
|
764
|
+
if (cur > character) {
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
const f = firstChild.toString(true).trim();
|
|
769
|
+
return {
|
|
770
|
+
signatures: candidates.map((params) => ({
|
|
771
|
+
label: `{{${f}${params.length === 0 ? '' : ':'}${params.map(({ label }) => label).join('|')}}}`,
|
|
772
|
+
parameters: params.map(({ label, const: c }) => ({
|
|
773
|
+
label,
|
|
774
|
+
...c ? { documentation: 'Predefined parameter' } : undefined,
|
|
775
|
+
})),
|
|
776
|
+
...params.length < n ? { activeParameter: Math.min(j, params.length - 1) } : undefined,
|
|
752
777
|
})),
|
|
753
|
-
|
|
754
|
-
}
|
|
755
|
-
return { signatures, activeParameter };
|
|
778
|
+
activeParameter: j,
|
|
779
|
+
};
|
|
756
780
|
}
|
|
757
781
|
/**
|
|
758
782
|
* 提供 CodeLens
|
|
@@ -764,7 +788,7 @@ class LanguageService {
|
|
|
764
788
|
for (const template of root.querySelectorAll('template,magic-word#invoke')) {
|
|
765
789
|
const { type, childNodes } = template;
|
|
766
790
|
hints.push(...childNodes.slice(type === 'template' ? 1 : 3).filter(({ anon }) => anon)
|
|
767
|
-
.map((parameter) => ({
|
|
791
|
+
.reverse().map((parameter) => ({
|
|
768
792
|
position: positionAt(root, parameter.getAbsoluteIndex()),
|
|
769
793
|
label: `${parameter.name}=`,
|
|
770
794
|
kind: 2,
|
|
@@ -791,7 +815,46 @@ class LanguageService {
|
|
|
791
815
|
* @param text 源代码
|
|
792
816
|
*/
|
|
793
817
|
async provideDocumentSymbols(text) {
|
|
794
|
-
|
|
818
|
+
this.#checkSignature();
|
|
819
|
+
const root = await this.#queue(text), lines = root.getLines(), { length } = lines, symbols = [], names = new Set(), sections = new Array(6), tokens = root.querySelectorAll('heading-title');
|
|
820
|
+
for (const token of [...tokens].reverse()) { // 提高 getBoundingClientRect 的性能
|
|
821
|
+
token.getRelativeIndex();
|
|
822
|
+
}
|
|
823
|
+
for (const token of tokens) {
|
|
824
|
+
const { top, height, left, width } = token.getBoundingClientRect();
|
|
825
|
+
if (token.type === 'heading-title') {
|
|
826
|
+
const { level } = token.parentNode;
|
|
827
|
+
for (let i = level - 1; i < 6; i++) {
|
|
828
|
+
getSectionEnd(sections[i], lines, top - 1);
|
|
829
|
+
sections[i] = undefined;
|
|
830
|
+
}
|
|
831
|
+
const section = token.text().trim() || ' ', name = names.has(section)
|
|
832
|
+
? new Array(names.size).fill('').map((_, i) => `${section.trim()}_${i + 2}`)
|
|
833
|
+
.find(s => !names.has(s))
|
|
834
|
+
: section, container = sections.slice(0, level - 1).reverse().find(Boolean), selectionRange = {
|
|
835
|
+
start: { line: top, character: left - level },
|
|
836
|
+
end: (0, lint_1.getEndPos)(top, left, height, width + level),
|
|
837
|
+
}, info = {
|
|
838
|
+
name,
|
|
839
|
+
kind: 15,
|
|
840
|
+
range: { start: selectionRange.start },
|
|
841
|
+
selectionRange,
|
|
842
|
+
};
|
|
843
|
+
names.add(name);
|
|
844
|
+
sections[level - 1] = info;
|
|
845
|
+
if (container) {
|
|
846
|
+
container.children ??= [];
|
|
847
|
+
container.children.push(info);
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
symbols.push(info);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
for (const section of sections) {
|
|
855
|
+
getSectionEnd(section, lines, length - 1);
|
|
856
|
+
}
|
|
857
|
+
return symbols;
|
|
795
858
|
}
|
|
796
859
|
}
|
|
797
860
|
exports.LanguageService = LanguageService;
|