wikiparser-node 1.16.3 → 1.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bundle/bundle.es7.js +28 -28
- package/bundle/bundle.lsp.js +30 -30
- package/bundle/bundle.min.js +29 -29
- package/coverage/badge.svg +1 -1
- package/data/signatures.json +43 -0
- package/dist/addon/table.js +7 -4
- package/dist/addon/token.js +119 -113
- package/dist/addon/transclude.js +7 -5
- package/dist/base.d.mts +99 -30
- package/dist/base.d.ts +99 -30
- package/dist/index.d.ts +10 -4
- package/dist/index.js +59 -14
- package/dist/lib/attributes.js +2 -2
- package/dist/lib/element.d.ts +128 -29
- package/dist/lib/element.js +178 -32
- package/dist/lib/lsp.d.ts +62 -27
- package/dist/lib/lsp.js +395 -304
- package/dist/lib/node.d.ts +123 -54
- package/dist/lib/node.js +216 -87
- package/dist/lib/range.d.ts +83 -31
- package/dist/lib/range.js +83 -31
- package/dist/lib/ranges.d.ts +1 -1
- package/dist/lib/ranges.js +3 -2
- package/dist/lib/text.d.ts +40 -14
- package/dist/lib/text.js +47 -19
- package/dist/lib/title.d.ts +54 -17
- package/dist/lib/title.js +101 -47
- package/dist/mixin/attributesParent.d.ts +37 -21
- package/dist/mixin/attributesParent.js +18 -20
- package/dist/mixin/flagsParent.d.ts +38 -12
- package/dist/mixin/hidden.d.ts +0 -2
- package/dist/mixin/hidden.js +0 -2
- package/dist/mixin/magicLinkParent.d.ts +11 -5
- package/dist/mixin/singleLine.d.ts +0 -2
- package/dist/mixin/singleLine.js +3 -3
- package/dist/mixin/sol.d.ts +0 -2
- package/dist/mixin/sol.js +2 -3
- package/dist/mixin/syntax.d.ts +0 -2
- package/dist/mixin/syntax.js +0 -2
- package/dist/parser/braces.js +4 -2
- package/dist/parser/links.js +13 -7
- package/dist/parser/list.js +3 -1
- package/dist/parser/quotes.js +22 -2
- package/dist/parser/redirect.js +4 -1
- package/dist/parser/selector.js +1 -1
- package/dist/src/arg.d.ts +19 -9
- package/dist/src/arg.js +34 -15
- package/dist/src/atom.d.ts +5 -1
- package/dist/src/atom.js +5 -1
- package/dist/src/attribute.d.ts +26 -8
- package/dist/src/attribute.js +30 -11
- package/dist/src/attributes.d.ts +50 -22
- package/dist/src/attributes.js +54 -26
- package/dist/src/converter.d.ts +4 -2
- package/dist/src/converter.js +12 -9
- package/dist/src/converterFlags.d.ts +45 -15
- package/dist/src/converterFlags.js +45 -15
- package/dist/src/converterRule.d.ts +30 -11
- package/dist/src/converterRule.js +46 -16
- package/dist/src/extLink.d.ts +6 -2
- package/dist/src/extLink.js +14 -11
- package/dist/src/gallery.d.ts +13 -9
- package/dist/src/gallery.js +35 -21
- package/dist/src/heading.d.ts +13 -7
- package/dist/src/heading.js +28 -18
- package/dist/src/hidden.d.ts +5 -1
- package/dist/src/hidden.js +7 -3
- package/dist/src/html.d.ts +12 -10
- package/dist/src/html.js +17 -12
- package/dist/src/imageParameter.d.ts +27 -12
- package/dist/src/imageParameter.js +27 -12
- package/dist/src/imagemap.d.ts +4 -4
- package/dist/src/imagemap.js +9 -5
- package/dist/src/imagemapLink.d.ts +3 -1
- package/dist/src/imagemapLink.js +3 -1
- package/dist/src/index.d.ts +65 -64
- package/dist/src/index.js +111 -132
- package/dist/src/link/base.d.ts +14 -6
- package/dist/src/link/base.js +45 -21
- package/dist/src/link/category.d.ts +9 -4
- package/dist/src/link/category.js +20 -4
- package/dist/src/link/file.d.ts +71 -23
- package/dist/src/link/file.js +100 -36
- package/dist/src/link/galleryImage.d.ts +8 -4
- package/dist/src/link/galleryImage.js +15 -8
- package/dist/src/link/index.d.ts +14 -6
- package/dist/src/link/index.js +15 -5
- package/dist/src/link/redirectTarget.d.ts +4 -3
- package/dist/src/link/redirectTarget.js +5 -3
- package/dist/src/magicLink.d.ts +28 -12
- package/dist/src/magicLink.js +56 -34
- package/dist/src/nested.d.ts +5 -3
- package/dist/src/nested.js +9 -6
- package/dist/src/nowiki/base.d.ts +4 -2
- package/dist/src/nowiki/base.js +6 -5
- package/dist/src/nowiki/comment.d.ts +6 -2
- package/dist/src/nowiki/comment.js +8 -3
- package/dist/src/nowiki/dd.d.ts +0 -3
- package/dist/src/nowiki/dd.js +0 -8
- package/dist/src/nowiki/doubleUnderscore.d.ts +5 -1
- package/dist/src/nowiki/doubleUnderscore.js +5 -1
- package/dist/src/nowiki/index.d.ts +5 -1
- package/dist/src/nowiki/index.js +5 -1
- package/dist/src/nowiki/list.d.ts +5 -1
- package/dist/src/nowiki/list.js +5 -1
- package/dist/src/nowiki/listBase.d.ts +15 -5
- package/dist/src/nowiki/listBase.js +41 -10
- package/dist/src/nowiki/noinclude.d.ts +5 -1
- package/dist/src/nowiki/noinclude.js +5 -1
- package/dist/src/nowiki/quote.d.ts +15 -8
- package/dist/src/nowiki/quote.js +32 -12
- package/dist/src/onlyinclude.d.ts +4 -2
- package/dist/src/onlyinclude.js +4 -2
- package/dist/src/paramTag/index.d.ts +1 -1
- package/dist/src/paramTag/index.js +1 -1
- package/dist/src/parameter.d.ts +24 -12
- package/dist/src/parameter.js +45 -27
- package/dist/src/pre.d.ts +1 -1
- package/dist/src/pre.js +1 -1
- package/dist/src/redirect.d.ts +2 -0
- package/dist/src/redirect.js +2 -0
- package/dist/src/syntax.d.ts +5 -1
- package/dist/src/syntax.js +5 -1
- package/dist/src/table/base.d.ts +5 -1
- package/dist/src/table/base.js +11 -4
- package/dist/src/table/index.d.ts +134 -66
- package/dist/src/table/index.js +149 -81
- package/dist/src/table/td.d.ts +18 -10
- package/dist/src/table/td.js +23 -15
- package/dist/src/table/tr.d.ts +13 -3
- package/dist/src/table/tr.js +14 -6
- package/dist/src/table/trBase.d.ts +28 -12
- package/dist/src/table/trBase.js +28 -18
- package/dist/src/tagPair/ext.d.ts +3 -1
- package/dist/src/tagPair/ext.js +11 -10
- package/dist/src/tagPair/include.d.ts +8 -2
- package/dist/src/tagPair/include.js +8 -2
- package/dist/src/tagPair/index.d.ts +6 -2
- package/dist/src/tagPair/index.js +6 -2
- package/dist/src/transclude.d.ts +107 -33
- package/dist/src/transclude.js +139 -45
- package/dist/util/debug.js +2 -7
- package/dist/util/html.js +1 -14
- package/dist/util/string.js +7 -1
- package/extensions/dist/base.js +25 -8
- package/extensions/dist/codejar.js +2 -1
- package/extensions/dist/lsp.js +5 -10
- package/extensions/es7/base.js +25 -8
- package/extensions/ui.css +1 -162
- package/i18n/zh-hans.json +1 -1
- package/i18n/zh-hant.json +1 -1
- package/package.json +3 -3
- package/extensions/dist/test-page.js +0 -89
package/dist/lib/lsp.js
CHANGED
|
@@ -5,17 +5,32 @@ 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
|
-
const element_1 = require("../lib/element");
|
|
10
10
|
/* NOT FOR BROWSER */
|
|
11
11
|
const constants_1 = require("../util/constants");
|
|
12
|
+
/* NOT FOR BROWSER ONLY END */
|
|
12
13
|
exports.tasks = new WeakMap();
|
|
13
|
-
const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']), nameAttrs = new Set(['name', 'extends', 'follow']), groupAttrs = new Set(['group'])
|
|
14
|
+
const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']), nameAttrs = new Set(['name', 'extends', 'follow']), groupAttrs = new Set(['group']), renameTypes = new Set([
|
|
15
|
+
'arg-name',
|
|
16
|
+
'template-name',
|
|
17
|
+
'magic-word-name',
|
|
18
|
+
'link-target',
|
|
19
|
+
'parameter-key',
|
|
20
|
+
]), referenceTypes = new Set([
|
|
21
|
+
'ext',
|
|
22
|
+
'html',
|
|
23
|
+
'attr-key',
|
|
24
|
+
'image-parameter',
|
|
25
|
+
'heading-title',
|
|
26
|
+
'heading',
|
|
27
|
+
...renameTypes,
|
|
28
|
+
]), plainTypes = new Set(['text', 'comment', 'noinclude', 'include']);
|
|
14
29
|
/**
|
|
15
30
|
* Check if all child nodes are plain text or comments.
|
|
16
31
|
* @param childNodes child nodes
|
|
17
32
|
*/
|
|
18
|
-
const isPlain = (childNodes) => childNodes.every(({ type }) =>
|
|
33
|
+
const isPlain = (childNodes) => childNodes.every(({ type }) => plainTypes.has(type));
|
|
19
34
|
/**
|
|
20
35
|
* Get the position of a character in the document.
|
|
21
36
|
* @param root root token
|
|
@@ -54,8 +69,9 @@ const createNodeRange = (token) => {
|
|
|
54
69
|
* @param pos position
|
|
55
70
|
* @param pos.line line number
|
|
56
71
|
* @param pos.character character number
|
|
72
|
+
* @param extra extra text
|
|
57
73
|
*/
|
|
58
|
-
const getCompletion = (words, kind, mt, { line, character }) => [...new Set(words)].map((w) => ({
|
|
74
|
+
const getCompletion = (words, kind, mt, { line, character }, extra) => [...new Set(words)].map((w) => ({
|
|
59
75
|
label: w,
|
|
60
76
|
kind,
|
|
61
77
|
textEdit: {
|
|
@@ -63,38 +79,19 @@ const getCompletion = (words, kind, mt, { line, character }) => [...new Set(word
|
|
|
63
79
|
start: { line, character: character - mt.length },
|
|
64
80
|
end: { line, character },
|
|
65
81
|
},
|
|
66
|
-
newText: w,
|
|
82
|
+
newText: w + (extra ?? ''),
|
|
67
83
|
},
|
|
68
84
|
}));
|
|
69
85
|
/**
|
|
70
|
-
*
|
|
71
|
-
* @param page page name
|
|
72
|
-
* @param ns namespace
|
|
73
|
-
* @throws `RangeError` Invalid page name.
|
|
74
|
-
* @throws `Error` Article path is not set.
|
|
75
|
-
*/
|
|
76
|
-
const getUrl = (page, ns) => {
|
|
77
|
-
const { title, fragment, valid } = index_1.default.normalizeTitle(page, ns), { articlePath } = index_1.default.getConfig();
|
|
78
|
-
/* istanbul ignore next */
|
|
79
|
-
if (!valid) {
|
|
80
|
-
throw new RangeError('Invalid page name.');
|
|
81
|
-
}
|
|
82
|
-
else if (!articlePath) {
|
|
83
|
-
throw new Error('Article path is not set.');
|
|
84
|
-
}
|
|
85
|
-
const encoded = encodeURIComponent(title) + (fragment === undefined ? '' : `#${encodeURIComponent(fragment)}`);
|
|
86
|
-
return articlePath.includes('$1')
|
|
87
|
-
? articlePath.replace('$1', encoded)
|
|
88
|
-
: articlePath + (articlePath.endsWith('/') ? '' : '/') + encoded;
|
|
89
|
-
};
|
|
90
|
-
/**
|
|
91
|
-
* Get the token at the position from a word.
|
|
86
|
+
* Get the caret position at the position from a word.
|
|
92
87
|
* @param root root token
|
|
93
88
|
* @param pos position
|
|
89
|
+
* @param pos.line line number
|
|
90
|
+
* @param pos.character character number
|
|
94
91
|
*/
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
return root.
|
|
92
|
+
const caretPositionFromWord = (root, { line, character }) => {
|
|
93
|
+
const index = root.indexFromPos(line, character);
|
|
94
|
+
return root.caretPositionFromIndex(index + Number(/\w/u.test(root.toString().charAt(index))));
|
|
98
95
|
};
|
|
99
96
|
/**
|
|
100
97
|
* Get the attribute of a `<ref>` tag.
|
|
@@ -116,6 +113,15 @@ const getRefName = (token) => getRefAttr(token, refTags, nameAttrs);
|
|
|
116
113
|
* @param token `group` attribute token
|
|
117
114
|
*/
|
|
118
115
|
const getRefGroup = (token) => getRefAttr(token, referencesTags, groupAttrs);
|
|
116
|
+
/**
|
|
117
|
+
* Get the attribute of a `<ref>` tag.
|
|
118
|
+
* @param token extension token
|
|
119
|
+
* @param target attribute name
|
|
120
|
+
*/
|
|
121
|
+
const getRefTagAttr = (token, target) => {
|
|
122
|
+
const attr = token?.getAttr(target);
|
|
123
|
+
return attr !== true && attr || false;
|
|
124
|
+
};
|
|
119
125
|
/**
|
|
120
126
|
* Get the effective name of a token.
|
|
121
127
|
* @param token
|
|
@@ -138,6 +144,18 @@ const getName = (token) => {
|
|
|
138
144
|
}
|
|
139
145
|
};
|
|
140
146
|
/* NOT FOR BROWSER ONLY */
|
|
147
|
+
/**
|
|
148
|
+
* Get the quick fix data.
|
|
149
|
+
* @param root root token
|
|
150
|
+
* @param fix lint error fix
|
|
151
|
+
* @param preferred whether it is a preferred fix
|
|
152
|
+
*/
|
|
153
|
+
const getQuickFix = (root, fix, preferred = false) => ({
|
|
154
|
+
range: createRange(root, ...fix.range),
|
|
155
|
+
newText: fix.text,
|
|
156
|
+
title: `${preferred ? 'Fix' : 'Suggestion'}: ${fix.desc}`,
|
|
157
|
+
fix: preferred,
|
|
158
|
+
});
|
|
141
159
|
/**
|
|
142
160
|
* Get the end position of a section.
|
|
143
161
|
* @param section section
|
|
@@ -180,7 +198,7 @@ class LanguageService {
|
|
|
180
198
|
* - 否则开始新的解析
|
|
181
199
|
*/
|
|
182
200
|
async #queue(text) {
|
|
183
|
-
text =
|
|
201
|
+
text = (0, string_1.tidy)(text);
|
|
184
202
|
if (this.#text === text && this.#config === index_1.default.config && !this.#running) {
|
|
185
203
|
return this.#done;
|
|
186
204
|
}
|
|
@@ -195,23 +213,18 @@ class LanguageService {
|
|
|
195
213
|
* - 总是返回最新的解析结果
|
|
196
214
|
*/
|
|
197
215
|
async #parse() {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this.#running = this.#parse();
|
|
211
|
-
/* istanbul ignore next */
|
|
212
|
-
resolve(this.#running);
|
|
213
|
-
}, 0);
|
|
214
|
-
});
|
|
216
|
+
const config = index_1.default.getConfig();
|
|
217
|
+
this.#config = index_1.default.config;
|
|
218
|
+
const text = this.#text, root = await index_1.default.partialParse(text, () => this.#text, true, config);
|
|
219
|
+
if (this.#text === text && this.#config === index_1.default.config) {
|
|
220
|
+
this.#done = root;
|
|
221
|
+
this.#running = undefined;
|
|
222
|
+
return root;
|
|
223
|
+
}
|
|
224
|
+
/* istanbul ignore next */
|
|
225
|
+
this.#running = this.#parse();
|
|
226
|
+
/* istanbul ignore next */
|
|
227
|
+
return this.#running;
|
|
215
228
|
}
|
|
216
229
|
/**
|
|
217
230
|
* 检查是否为签名语言服务器
|
|
@@ -224,10 +237,12 @@ class LanguageService {
|
|
|
224
237
|
}
|
|
225
238
|
}
|
|
226
239
|
/**
|
|
240
|
+
* Provide color decorators
|
|
241
|
+
*
|
|
227
242
|
* 提供颜色指示
|
|
228
|
-
* @param rgba 颜色解析函数
|
|
229
|
-
* @param text 源代码
|
|
230
|
-
* @param hsl 是否允许HSL颜色
|
|
243
|
+
* @param rgba color parser / 颜色解析函数
|
|
244
|
+
* @param text source Wikitext / 源代码
|
|
245
|
+
* @param hsl whether HSL colors are treated / 是否允许HSL颜色
|
|
231
246
|
*/
|
|
232
247
|
async provideDocumentColors(rgba, text, hsl = true) {
|
|
233
248
|
this.#checkSignature();
|
|
@@ -237,7 +252,7 @@ class LanguageService {
|
|
|
237
252
|
if (type !== 'attr-value' && !isPlain(childNodes)) {
|
|
238
253
|
return [];
|
|
239
254
|
}
|
|
240
|
-
return childNodes.filter((child) => child.type === 'text')
|
|
255
|
+
return childNodes.filter((child) => child.type === 'text').reverse()
|
|
241
256
|
.flatMap(child => {
|
|
242
257
|
const parts = (0, common_1.splitColors)(child.data, hsl).filter(([, , , isColor]) => isColor);
|
|
243
258
|
if (parts.length === 0) {
|
|
@@ -259,10 +274,15 @@ class LanguageService {
|
|
|
259
274
|
});
|
|
260
275
|
});
|
|
261
276
|
}
|
|
262
|
-
/**
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Provide color pickers
|
|
279
|
+
*
|
|
280
|
+
* 颜色选择器
|
|
281
|
+
* @param color color information / 颜色信息
|
|
282
|
+
*/
|
|
283
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
284
|
+
provideColorPresentations(color) {
|
|
285
|
+
const { color: { red, green, blue, alpha }, range } = color, newText = `#${(0, common_1.numToHex)(red)}${(0, common_1.numToHex)(green)}${(0, common_1.numToHex)(blue)}${alpha < 1 ? (0, common_1.numToHex)(alpha) : ''}`;
|
|
266
286
|
return [
|
|
267
287
|
{
|
|
268
288
|
label: newText,
|
|
@@ -277,7 +297,7 @@ class LanguageService {
|
|
|
277
297
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions, es-x/no-regexp-unicode-property-escapes
|
|
278
298
|
/(?:<\/?(\w*)|(\{{2,4}|\[\[)\s*([^|{}<>[\]\s][^|{}<>[\]#]*)?|(__(?:(?!__)[\p{L}\d_])*)|(?<!\[)\[([a-z:/]*)|\[\[\s*(?:file|image)\s*:[^[\]{}<>]+\|([^[\]{}<>|=]*)|<(\w+)(?:\s(?:[^<>{}|=\s]+(?:\s*=\s*(?:[^\s"']\S*|(["']).*?\8))?(?=\s))*)?\s(\w*))$/iu;
|
|
279
299
|
const re = new RegExp('(?:' // eslint-disable-line prefer-template
|
|
280
|
-
+ String.raw
|
|
300
|
+
+ String.raw `<(\/?\w*)` // tag
|
|
281
301
|
+ '|'
|
|
282
302
|
+ String.raw `(\{{2,4}|\[\[)\s*([^|{}<>[\]\s][^|{}<>[\]#]*)?` // braces and brackets
|
|
283
303
|
+ '|'
|
|
@@ -309,15 +329,18 @@ class LanguageService {
|
|
|
309
329
|
return this.#completionConfig;
|
|
310
330
|
}
|
|
311
331
|
/**
|
|
332
|
+
* Provide auto-completion
|
|
333
|
+
*
|
|
312
334
|
* 提供自动补全
|
|
313
|
-
* @param text 源代码
|
|
335
|
+
* @param text source Wikitext / 源代码
|
|
314
336
|
* @param position 位置
|
|
315
337
|
*/
|
|
316
338
|
async provideCompletionItems(text, position) {
|
|
317
339
|
this.#checkSignature();
|
|
318
|
-
const { re, allTags, functions, switches, protocols, params, tags, ext } = this.#prepareCompletionConfig(), { line, character } = position,
|
|
340
|
+
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) ?? '');
|
|
319
341
|
if (mt?.[1] !== undefined) { // tag
|
|
320
|
-
|
|
342
|
+
const closing = mt[1].startsWith('/');
|
|
343
|
+
return getCompletion(allTags, 'Class', mt[1].slice(closing ? 1 : 0), position, closing && !curLine?.slice(character).trim().startsWith('>') ? '>' : '');
|
|
321
344
|
}
|
|
322
345
|
else if (mt?.[4]) { // behavior switch
|
|
323
346
|
return getCompletion(switches, 'Constant', mt[4], position);
|
|
@@ -326,34 +349,54 @@ class LanguageService {
|
|
|
326
349
|
return getCompletion(protocols, 'Reference', mt[5], position);
|
|
327
350
|
}
|
|
328
351
|
const root = await this.#queue(text);
|
|
352
|
+
let cur;
|
|
329
353
|
if (mt?.[2]) {
|
|
354
|
+
cur = root.elementFromPoint(mt.index + mt[2].length - 1, line);
|
|
330
355
|
const match = mt[3] ?? '';
|
|
331
356
|
if (mt[2] === '{{{') { // argument
|
|
332
|
-
return getCompletion(root.querySelectorAll('arg').
|
|
357
|
+
return getCompletion(root.querySelectorAll('arg').filter(token => token.name && token !== cur)
|
|
358
|
+
.map(({ name }) => name), 'Variable', match, position);
|
|
333
359
|
}
|
|
334
360
|
const colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
|
|
335
361
|
return mt[2] === '[['
|
|
336
362
|
? getCompletion(// link
|
|
337
|
-
root.querySelectorAll('link,file,category,redirect-target').map(({ name }) => name), 'Folder', str, position)
|
|
363
|
+
root.querySelectorAll('link,file,category,redirect-target').filter(token => token !== cur).map(({ name }) => name), 'Folder', str, position)
|
|
338
364
|
: [
|
|
339
365
|
...getCompletion(functions, 'Function', match, position),
|
|
340
366
|
...match.startsWith('#')
|
|
341
367
|
? []
|
|
342
|
-
: getCompletion(root.querySelectorAll('template')
|
|
343
|
-
.map(
|
|
368
|
+
: getCompletion(root.querySelectorAll('template').filter(token => token !== cur)
|
|
369
|
+
.map(token => {
|
|
370
|
+
const { name } = token;
|
|
371
|
+
if (colon) {
|
|
372
|
+
return name;
|
|
373
|
+
}
|
|
374
|
+
const { ns } = token.getAttribute('title');
|
|
375
|
+
if (ns === 0) {
|
|
376
|
+
return `:${name}`;
|
|
377
|
+
}
|
|
378
|
+
return ns === 10 ? name.slice(9) : name;
|
|
379
|
+
}), 'Folder', str, position),
|
|
344
380
|
];
|
|
345
381
|
}
|
|
346
|
-
|
|
382
|
+
let type, parentNode;
|
|
383
|
+
if (mt?.[7] === undefined) {
|
|
384
|
+
cur = root.elementFromPoint(character, line);
|
|
385
|
+
({ type, parentNode } = cur);
|
|
386
|
+
}
|
|
347
387
|
if (mt?.[6] !== undefined || type === 'image-parameter') { // image parameter
|
|
348
|
-
const match = mt?.[6]?.trimStart()
|
|
349
|
-
?? this.#text.slice(
|
|
388
|
+
const index = root.indexFromPos(line, character), match = mt?.[6]?.trimStart()
|
|
389
|
+
?? this.#text.slice(cur.getAbsoluteIndex(), index).trimStart(), equal = this.#text.charAt(index) === '=';
|
|
350
390
|
return [
|
|
351
|
-
...getCompletion(params, 'Property', match, position)
|
|
352
|
-
|
|
391
|
+
...getCompletion(params, 'Property', match, position)
|
|
392
|
+
.filter(({ label }) => !equal || !/[= ]$/u.test(label)),
|
|
393
|
+
...getCompletion(root.querySelectorAll('image-parameter#width')
|
|
394
|
+
.filter(token => token !== cur)
|
|
395
|
+
.map(width => width.text()), 'Unit', match, position),
|
|
353
396
|
];
|
|
354
397
|
}
|
|
355
398
|
else if (mt?.[7] !== undefined || type === 'attr-key') { // attribute key
|
|
356
|
-
const tag = mt?.[7]?.toLowerCase() ?? parentNode.tag, key = mt?.[9] ??
|
|
399
|
+
const tag = mt?.[7]?.toLowerCase() ?? parentNode.tag, key = mt?.[9] ?? cur.toString();
|
|
357
400
|
if (!tags.has(tag)) {
|
|
358
401
|
return undefined;
|
|
359
402
|
}
|
|
@@ -372,24 +415,41 @@ class LanguageService {
|
|
|
372
415
|
...getCompletion(['xmlns:'], 'Interface', key, position),
|
|
373
416
|
];
|
|
374
417
|
}
|
|
375
|
-
else if (
|
|
376
|
-
|
|
377
|
-
const
|
|
418
|
+
else if (type === 'parameter-key' || type === 'parameter-value' && parentNode.anon) {
|
|
419
|
+
// parameter key
|
|
420
|
+
const transclusion = parentNode.parentNode, { type: t, name: n } = transclusion;
|
|
421
|
+
if (t === 'magic-word' && n !== 'invoke') {
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
const key = cur.toString().trimStart(), [module, func] = t === 'magic-word' ? transclusion.getModule() : [];
|
|
378
425
|
return key
|
|
379
|
-
? getCompletion(root.querySelectorAll('parameter').filter(
|
|
380
|
-
|
|
426
|
+
? getCompletion(root.querySelectorAll('parameter').filter(token => {
|
|
427
|
+
if (token === parentNode
|
|
428
|
+
|| token.anon
|
|
429
|
+
|| token.parentNode.type !== t
|
|
430
|
+
|| token.parentNode.name !== n) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
else if (t === 'template') {
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
const [m, f] = token.parentNode.getModule();
|
|
437
|
+
return m === module && f === func;
|
|
438
|
+
}).map(({ name }) => name), 'Variable', key, position, type === 'parameter-value' ? '=' : '')
|
|
381
439
|
: undefined;
|
|
382
440
|
}
|
|
383
441
|
return undefined;
|
|
384
442
|
}
|
|
385
443
|
/**
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
444
|
+
* Provide grammar check
|
|
445
|
+
*
|
|
446
|
+
* 提供语法检查
|
|
447
|
+
* @param text source Wikitext / 源代码
|
|
448
|
+
* @param warning whether to include warnings / 是否包含警告
|
|
389
449
|
*/
|
|
390
|
-
async provideDiagnostics(
|
|
450
|
+
async provideDiagnostics(text, warning = true) {
|
|
391
451
|
this.#checkSignature();
|
|
392
|
-
const root = await this.#queue(
|
|
452
|
+
const root = await this.#queue(text), errors = root.lint();
|
|
393
453
|
return (warning ? errors : errors.filter(({ severity }) => severity === 'error'))
|
|
394
454
|
.map(({ startLine, startCol, endLine, endCol, severity, rule, message, fix, suggestions }) => ({
|
|
395
455
|
range: {
|
|
@@ -400,86 +460,43 @@ class LanguageService {
|
|
|
400
460
|
source: 'WikiLint',
|
|
401
461
|
code: rule,
|
|
402
462
|
message,
|
|
463
|
+
/* NOT FOR BROWSER ONLY */
|
|
403
464
|
data: [
|
|
404
|
-
...fix
|
|
405
|
-
|
|
406
|
-
{
|
|
407
|
-
range: createRange(root, ...fix.range),
|
|
408
|
-
newText: fix.text,
|
|
409
|
-
title: `Fix: ${fix.desc}`,
|
|
410
|
-
fix: true,
|
|
411
|
-
},
|
|
412
|
-
]
|
|
413
|
-
: [],
|
|
414
|
-
...suggestions
|
|
415
|
-
? suggestions.map(({ range, text, desc }) => ({
|
|
416
|
-
range: createRange(root, ...range),
|
|
417
|
-
newText: text,
|
|
418
|
-
title: `Suggestion: ${desc}`,
|
|
419
|
-
fix: false,
|
|
420
|
-
}))
|
|
421
|
-
: [],
|
|
465
|
+
...fix ? [getQuickFix(root, fix, true)] : [],
|
|
466
|
+
...suggestions ? suggestions.map(suggestion => getQuickFix(root, suggestion)) : [],
|
|
422
467
|
],
|
|
423
468
|
}));
|
|
424
469
|
}
|
|
425
|
-
|
|
470
|
+
/**
|
|
471
|
+
* Provide folding ranges
|
|
472
|
+
*
|
|
473
|
+
* 提供折叠范围
|
|
474
|
+
* @param text source Wikitext / 源代码
|
|
475
|
+
*/
|
|
476
|
+
async provideFoldingRanges(text) {
|
|
426
477
|
this.#checkSignature();
|
|
427
|
-
const ranges = [],
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
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');
|
|
478
|
+
const root = await this.#queue(text), { length } = root.getLines(), ranges = [], levels = new Array(6), tokens = root.querySelectorAll('heading-title,table,template,magic-word');
|
|
479
|
+
for (const token of [...tokens].reverse()) { // 提高 getBoundingClientRect 的性能
|
|
480
|
+
token.getRelativeIndex();
|
|
481
|
+
}
|
|
432
482
|
for (const token of tokens) {
|
|
433
|
-
const { top, height
|
|
434
|
-
/* NOT FOR BROWSER ONLY */
|
|
435
|
-
left, width, } = token.getBoundingClientRect();
|
|
483
|
+
const { top, height } = token.getBoundingClientRect();
|
|
436
484
|
if (token.type === 'heading-title') {
|
|
437
485
|
const { level } = token.parentNode;
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
levels[i] = undefined;
|
|
449
|
-
}
|
|
450
|
-
levels[level - 1] = top + height - 1; // 从标题的最后一行开始折叠
|
|
451
|
-
/* NOT FOR BROWSER ONLY */
|
|
452
|
-
}
|
|
453
|
-
else {
|
|
454
|
-
for (let i = level - 1; i < 6; i++) {
|
|
455
|
-
getSectionEnd(sections[i], lines, top - 1);
|
|
456
|
-
sections[i] = undefined;
|
|
457
|
-
}
|
|
458
|
-
const section = token.text().trim() || ' ', name = names.has(section)
|
|
459
|
-
? new Array(names.size).fill('').map((_, i) => `${section.trim()}_${i + 2}`)
|
|
460
|
-
.find(s => !names.has(s))
|
|
461
|
-
: section, container = sections.slice(0, level - 1).reverse().find(Boolean), selectionRange = {
|
|
462
|
-
start: { line: top, character: left - level },
|
|
463
|
-
end: (0, lint_1.getEndPos)(top, left, height, width + level),
|
|
464
|
-
}, info = {
|
|
465
|
-
name,
|
|
466
|
-
kind: 15,
|
|
467
|
-
range: { start: selectionRange.start },
|
|
468
|
-
selectionRange,
|
|
469
|
-
};
|
|
470
|
-
names.add(name);
|
|
471
|
-
sections[level - 1] = info;
|
|
472
|
-
if (container) {
|
|
473
|
-
container.children ??= [];
|
|
474
|
-
container.children.push(info);
|
|
486
|
+
for (let i = level - 1; i < 6; i++) {
|
|
487
|
+
const startLine = levels[i];
|
|
488
|
+
if (startLine !== undefined && startLine < top - 1) {
|
|
489
|
+
ranges.push({
|
|
490
|
+
startLine,
|
|
491
|
+
endLine: top - 1,
|
|
492
|
+
kind: 'region',
|
|
493
|
+
});
|
|
475
494
|
}
|
|
476
|
-
|
|
477
|
-
symbols.push(info);
|
|
478
|
-
}
|
|
479
|
-
/* NOT FOR BROWSER ONLY END */
|
|
495
|
+
levels[i] = undefined;
|
|
480
496
|
}
|
|
497
|
+
levels[level - 1] = top + height - 1; // 从标题的最后一行开始折叠
|
|
481
498
|
}
|
|
482
|
-
else if (
|
|
499
|
+
else if (height > 2) {
|
|
483
500
|
ranges.push({
|
|
484
501
|
startLine: top, // 从表格或模板的第一行开始折叠
|
|
485
502
|
endLine: top + height - 2,
|
|
@@ -487,208 +504,206 @@ class LanguageService {
|
|
|
487
504
|
});
|
|
488
505
|
}
|
|
489
506
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
/* NOT FOR BROWSER ONLY */
|
|
501
|
-
}
|
|
502
|
-
else {
|
|
503
|
-
for (const section of sections) {
|
|
504
|
-
getSectionEnd(section, lines, length - 1);
|
|
507
|
+
for (const startLine of levels) {
|
|
508
|
+
if (startLine !== undefined && startLine < length - 1) {
|
|
509
|
+
ranges.push({
|
|
510
|
+
startLine,
|
|
511
|
+
endLine: length - 1,
|
|
512
|
+
kind: 'region',
|
|
513
|
+
});
|
|
505
514
|
}
|
|
506
|
-
/* NOT FOR BROWSER ONLY END */
|
|
507
515
|
}
|
|
508
|
-
return
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* 提供折叠范围
|
|
512
|
-
* @param text 源代码
|
|
513
|
-
*/
|
|
514
|
-
async provideFoldingRanges(text) {
|
|
515
|
-
return this.#provideFoldingRangesOrDocumentSymbols(text);
|
|
516
|
+
return ranges;
|
|
516
517
|
}
|
|
517
518
|
/**
|
|
519
|
+
* Provide links
|
|
520
|
+
*
|
|
518
521
|
* 提供链接
|
|
519
|
-
* @param text 源代码
|
|
522
|
+
* @param text source Wikitext / 源代码
|
|
520
523
|
*/
|
|
521
524
|
async provideLinks(text) {
|
|
522
525
|
this.#checkSignature();
|
|
523
526
|
/^(?:http:\/\/|\/\/)/iu; // eslint-disable-line @typescript-eslint/no-unused-expressions
|
|
524
|
-
const protocolRegex = new RegExp(`^(?:${
|
|
525
|
-
|
|
526
|
-
|
|
527
|
+
const { articlePath, protocol } = index_1.default.getConfig(), absolute = articlePath?.includes('//'), protocolRegex = new RegExp(`^(?:${protocol}|//)`, 'iu');
|
|
528
|
+
return (await this.#queue(text))
|
|
529
|
+
.querySelectorAll(`magic-link,ext-link-url,free-ext-link,attr-value,image-parameter#link${absolute ? ',link-target,template-name,invoke-module' : ''}`).reverse()
|
|
530
|
+
.map((token) => {
|
|
527
531
|
const { type, parentNode, firstChild, lastChild, childNodes } = token, { name, tag } = parentNode;
|
|
528
532
|
if (!(type !== 'attr-value'
|
|
529
533
|
|| name === 'src' && ['templatestyles', 'img'].includes(tag)
|
|
530
534
|
|| name === 'cite' && ['blockquote', 'del', 'ins', 'q'].includes(tag))
|
|
531
535
|
|| !isPlain(childNodes)) {
|
|
532
|
-
return
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
let target = childNodes.filter((node) => node.type === 'text')
|
|
539
|
+
.map(({ data }) => data).join('').trim();
|
|
540
|
+
if (!target) {
|
|
541
|
+
return false;
|
|
533
542
|
}
|
|
534
|
-
let target = type === 'image-parameter'
|
|
535
|
-
? element_1.AstElement.prototype.toString.call(token, true).trim()
|
|
536
|
-
: token.toString(true).trim();
|
|
537
543
|
try {
|
|
538
|
-
if (
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
544
|
+
if (token.is('magic-link')
|
|
545
|
+
|| token.is('ext-link-url')
|
|
546
|
+
|| token.is('free-ext-link')) {
|
|
547
|
+
target = token.getUrl(articlePath);
|
|
548
|
+
}
|
|
549
|
+
else if (type === 'link-target' && (parentNode.is('link')
|
|
550
|
+
|| parentNode.is('redirect-target')
|
|
551
|
+
|| parentNode.is('category'))) {
|
|
552
|
+
if (target.startsWith('/')) {
|
|
553
|
+
return false;
|
|
546
554
|
}
|
|
555
|
+
target = parentNode.link.getUrl(articlePath);
|
|
556
|
+
}
|
|
557
|
+
else if (type === 'template-name') {
|
|
558
|
+
target = parentNode.getAttribute('title').getUrl(articlePath);
|
|
547
559
|
}
|
|
548
|
-
else if (['link-target', '
|
|
560
|
+
else if (['link-target', 'invoke-module'].includes(type)
|
|
549
561
|
|| type === 'attr-value' && name === 'src' && tag === 'templatestyles'
|
|
550
562
|
|| type === 'image-parameter' && !protocolRegex.test(target)) {
|
|
551
|
-
if (target.startsWith('/')) {
|
|
552
|
-
return
|
|
563
|
+
if (!absolute || target.startsWith('/')) {
|
|
564
|
+
return false;
|
|
553
565
|
}
|
|
554
566
|
let ns = 0;
|
|
555
|
-
if (type === '
|
|
567
|
+
if (type === 'attr-value') {
|
|
556
568
|
ns = 10;
|
|
557
569
|
}
|
|
558
570
|
else if (type === 'invoke-module') {
|
|
559
571
|
ns = 828;
|
|
560
572
|
}
|
|
561
|
-
|
|
573
|
+
const title = index_1.default
|
|
574
|
+
.normalizeTitle(target, ns, false, undefined, true);
|
|
575
|
+
/* istanbul ignore if */
|
|
576
|
+
if (!title.valid) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
target = title.getUrl();
|
|
562
580
|
}
|
|
563
|
-
if (target.startsWith('//')) {
|
|
581
|
+
if (typeof target === 'string' && target.startsWith('//')) {
|
|
564
582
|
target = `https:${target}`;
|
|
565
583
|
}
|
|
566
584
|
target = new URL(target).href;
|
|
567
585
|
if (type === 'image-parameter') {
|
|
568
|
-
const
|
|
569
|
-
return
|
|
570
|
-
{
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
end: (0, lint_1.getEndPos)(top, left, height, width),
|
|
574
|
-
},
|
|
575
|
-
target,
|
|
586
|
+
const { top, left, height, width } = lastChild.getBoundingClientRect(), rect = firstChild.getBoundingClientRect();
|
|
587
|
+
return {
|
|
588
|
+
range: {
|
|
589
|
+
start: { line: rect.top, character: rect.left },
|
|
590
|
+
end: (0, lint_1.getEndPos)(top, left, height, width),
|
|
576
591
|
},
|
|
577
|
-
|
|
592
|
+
target,
|
|
593
|
+
};
|
|
578
594
|
}
|
|
579
|
-
return
|
|
595
|
+
return { range: createNodeRange(token), target };
|
|
580
596
|
}
|
|
581
597
|
catch {
|
|
582
|
-
return
|
|
598
|
+
return false;
|
|
583
599
|
}
|
|
584
|
-
});
|
|
600
|
+
}).filter(Boolean);
|
|
585
601
|
}
|
|
586
|
-
|
|
602
|
+
/**
|
|
603
|
+
* Provide references
|
|
604
|
+
*
|
|
605
|
+
* 提供引用
|
|
606
|
+
* @param text source Wikitext / 源代码
|
|
607
|
+
* @param position 位置
|
|
608
|
+
*/
|
|
609
|
+
async provideReferences(text, position) {
|
|
587
610
|
this.#checkSignature();
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
'link-target',
|
|
593
|
-
'parameter-key',
|
|
594
|
-
], types = [
|
|
595
|
-
'ext',
|
|
596
|
-
'html',
|
|
597
|
-
'attr-key',
|
|
598
|
-
'image-parameter',
|
|
599
|
-
'heading-title',
|
|
600
|
-
'heading',
|
|
601
|
-
...renameTypes,
|
|
602
|
-
], root = await this.#queue(text), node = elementFromWord(root, position), { type } = node, refName = getRefName(node), refGroup = getRefGroup(node);
|
|
603
|
-
if (usage === 2 && type === 'parameter-key' && /^[1-9]\d*$/u.test(node.parentNode.name)
|
|
604
|
-
|| !refName && (usage === 1
|
|
605
|
-
|| !refGroup && (usage === 0
|
|
606
|
-
? !types.includes(type)
|
|
607
|
-
: !renameTypes.includes(type)
|
|
608
|
-
|| type === 'link-target' && !['link', 'redirect-target'].includes(node.parentNode.type)))) {
|
|
611
|
+
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')
|
|
612
|
+
? element.parentNode.parentNode
|
|
613
|
+
: element, { type } = node, refName = getRefName(node), refGroup = getRefGroup(node);
|
|
614
|
+
if (!refName && !refGroup && !referenceTypes.has(type)) {
|
|
609
615
|
return undefined;
|
|
610
616
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const name = getName(node), refs = root.querySelectorAll(type === 'heading-title' ? 'heading' : type).filter(token => {
|
|
615
|
-
const { name: n, parentNode } = token.parentNode;
|
|
616
|
-
if (usage === 1) {
|
|
617
|
-
return getRefName(token) === refName
|
|
618
|
-
&& n === 'name' && parentNode.parentNode.innerText;
|
|
619
|
-
}
|
|
620
|
-
return type === 'attr-value'
|
|
621
|
-
? getRefName(token) === refName || getRefGroup(token) === refGroup
|
|
622
|
-
: getName(token) === name;
|
|
623
|
-
}).map((token) => ({
|
|
617
|
+
const name = getName(node), refs = root.querySelectorAll(type === 'heading-title' ? 'heading' : type).filter(token => type === 'attr-value'
|
|
618
|
+
? getRefName(token) === refName || getRefGroup(token) === refGroup
|
|
619
|
+
: getName(token) === name).map((token) => ({
|
|
624
620
|
range: createNodeRange(token.type === 'parameter-key' ? token.parentNode : token),
|
|
625
621
|
}));
|
|
626
622
|
return refs.length === 0 ? undefined : refs;
|
|
627
623
|
}
|
|
628
624
|
/**
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
* @param position 位置
|
|
632
|
-
*/
|
|
633
|
-
async provideReferences(text, position) {
|
|
634
|
-
return this.#provideReferencesOrDefinition(text, position, 0);
|
|
635
|
-
}
|
|
636
|
-
/**
|
|
625
|
+
* Provide definitions
|
|
626
|
+
*
|
|
637
627
|
* 提供定义
|
|
638
|
-
* @param text 源代码
|
|
628
|
+
* @param text source Wikitext / 源代码
|
|
639
629
|
* @param position 位置
|
|
640
630
|
*/
|
|
641
631
|
async provideDefinition(text, position) {
|
|
642
|
-
|
|
632
|
+
this.#checkSignature();
|
|
633
|
+
const root = await this.#queue(text), node = root.elementFromPoint(position.character, position.line), ext = node.is('ext') && node.name === 'ref'
|
|
634
|
+
? node
|
|
635
|
+
: node.closest('ext#ref'), refName = getRefTagAttr(ext, 'name');
|
|
636
|
+
if (!refName) {
|
|
637
|
+
return undefined;
|
|
638
|
+
}
|
|
639
|
+
const refGroup = getRefTagAttr(ext, 'group'), refs = root.querySelectorAll('ext#ref').filter(token => token.innerText
|
|
640
|
+
&& getRefTagAttr(token, 'name') === refName
|
|
641
|
+
&& getRefTagAttr(token, 'group') === refGroup).map(({ lastChild }) => ({
|
|
642
|
+
range: createNodeRange(lastChild),
|
|
643
|
+
}));
|
|
644
|
+
return refs.length === 0 ? undefined : refs;
|
|
643
645
|
}
|
|
644
646
|
/**
|
|
647
|
+
* Provide locations for renaming
|
|
648
|
+
*
|
|
645
649
|
* 提供变量更名准备
|
|
646
|
-
* @param text 源代码
|
|
650
|
+
* @param text source Wikitext / 源代码
|
|
647
651
|
* @param position 位置
|
|
648
652
|
*/
|
|
649
653
|
async resolveRenameLocation(text, position) {
|
|
650
|
-
|
|
654
|
+
this.#checkSignature();
|
|
655
|
+
const root = await this.#queue(text), node = root.elementFromPoint(position.character, position.line), { type } = node, refName = getRefName(node), refGroup = getRefGroup(node);
|
|
656
|
+
return !refName && !refGroup && (!renameTypes.has(type)
|
|
657
|
+
|| type === 'parameter-key' && /^[1-9]\d*$/u.test(node.parentNode.name)
|
|
658
|
+
|| type === 'link-target' && !['link', 'redirect-target'].includes(node.parentNode.type))
|
|
659
|
+
? undefined
|
|
660
|
+
: createNodeRange(node);
|
|
651
661
|
}
|
|
652
662
|
/**
|
|
663
|
+
* Provide rename edits
|
|
664
|
+
*
|
|
653
665
|
* 变量更名
|
|
654
|
-
* @param text 源代码
|
|
666
|
+
* @param text source Wikitext / 源代码
|
|
655
667
|
* @param position 位置
|
|
656
|
-
* @param newName 新名称
|
|
668
|
+
* @param newName new name / 新名称
|
|
657
669
|
*/
|
|
658
670
|
async provideRenameEdits(text, position, newName) {
|
|
659
671
|
this.#checkSignature();
|
|
660
|
-
const root = await this.#queue(text), node =
|
|
661
|
-
const name = getName(node), refs = root.querySelectorAll(type).filter(token => {
|
|
672
|
+
const root = await this.#queue(text), node = root.elementFromPoint(position.character, position.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 => {
|
|
662
673
|
const { type: t } = token.parentNode;
|
|
663
674
|
if (type === 'link-target' && t !== 'link' && t !== 'redirect-target') {
|
|
664
675
|
return false;
|
|
665
676
|
}
|
|
666
677
|
return type === 'attr-value'
|
|
667
|
-
?
|
|
678
|
+
? getRefGroup(token) === refGroup
|
|
679
|
+
|| getRefName(token) === refName
|
|
680
|
+
&& getRefTagAttr(token.parentNode.parentNode, 'group') === refNameGroup
|
|
668
681
|
: getName(token) === name;
|
|
669
682
|
});
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
};
|
|
683
|
+
return refs.length === 0
|
|
684
|
+
? undefined
|
|
685
|
+
: {
|
|
686
|
+
changes: {
|
|
687
|
+
'': refs.map((ref) => ({
|
|
688
|
+
range: createNodeRange(ref),
|
|
689
|
+
newText: newName,
|
|
690
|
+
})),
|
|
691
|
+
},
|
|
692
|
+
};
|
|
681
693
|
}
|
|
682
694
|
/**
|
|
683
695
|
* 检索解析器函数
|
|
684
696
|
* @param name 函数名
|
|
685
697
|
*/
|
|
686
698
|
#getParserFunction(name) {
|
|
687
|
-
return this.data.parserFunctions
|
|
699
|
+
return this.data.parserFunctions
|
|
700
|
+
.find(({ aliases }) => aliases.some(alias => alias.replace(/^#/u, '') === name));
|
|
688
701
|
}
|
|
689
702
|
/**
|
|
703
|
+
* Provide hover information
|
|
704
|
+
*
|
|
690
705
|
* 提供悬停信息
|
|
691
|
-
* @param text 源代码
|
|
706
|
+
* @param text source Wikitext / 源代码
|
|
692
707
|
* @param position 位置
|
|
693
708
|
*/
|
|
694
709
|
async provideHover(text, position) {
|
|
@@ -697,14 +712,31 @@ class LanguageService {
|
|
|
697
712
|
return undefined;
|
|
698
713
|
}
|
|
699
714
|
this.#checkSignature();
|
|
700
|
-
const
|
|
701
|
-
let info, f;
|
|
702
|
-
if (token.is('double-underscore')) {
|
|
715
|
+
const root = await this.#queue(text), { offsetNode, offset } = caretPositionFromWord(root, position), token = offsetNode.type === 'text' ? offsetNode.parentNode : offsetNode, { type, parentNode, length, name } = token;
|
|
716
|
+
let info, f, range;
|
|
717
|
+
if (token.is('double-underscore') && offset > 0) {
|
|
703
718
|
info = this.data.behaviorSwitches.find(({ aliases }) => aliases.includes(token.innerText.toLowerCase()));
|
|
704
719
|
}
|
|
705
|
-
else if (
|
|
706
|
-
info = this.#getParserFunction(
|
|
707
|
-
f = token.
|
|
720
|
+
else if (type === 'magic-word-name') {
|
|
721
|
+
info = this.#getParserFunction(parentNode.name);
|
|
722
|
+
f = token.toString(true).trim();
|
|
723
|
+
}
|
|
724
|
+
else if (token.is('magic-word') && !token.modifier && length === 1
|
|
725
|
+
&& (offset > 0 || token.getBoundingClientRect().left === position.character)) {
|
|
726
|
+
info = this.#getParserFunction(name);
|
|
727
|
+
f = token.firstChild.toString(true).trim();
|
|
728
|
+
}
|
|
729
|
+
else if ((token.is('magic-word') || token.is('template'))
|
|
730
|
+
&& token.modifier && offset >= 2 && token.getRelativeIndex(0) > offset) {
|
|
731
|
+
f = token.modifier.trim().slice(0, -1);
|
|
732
|
+
info = this.#getParserFunction(f.toLowerCase());
|
|
733
|
+
if (info) {
|
|
734
|
+
const aIndex = token.getAbsoluteIndex();
|
|
735
|
+
range = {
|
|
736
|
+
start: positionAt(root, aIndex + 2),
|
|
737
|
+
end: positionAt(root, aIndex + token.modifier.trimEnd().length + 1),
|
|
738
|
+
};
|
|
739
|
+
}
|
|
708
740
|
}
|
|
709
741
|
return info && {
|
|
710
742
|
contents: {
|
|
@@ -714,12 +746,14 @@ class LanguageService {
|
|
|
714
746
|
: '')
|
|
715
747
|
+ info.description,
|
|
716
748
|
},
|
|
717
|
-
range: createNodeRange(token),
|
|
749
|
+
range: range ?? createNodeRange(token),
|
|
718
750
|
};
|
|
719
751
|
}
|
|
720
752
|
/**
|
|
753
|
+
* Provide signature help for magic words
|
|
754
|
+
*
|
|
721
755
|
* 提供魔术字帮助
|
|
722
|
-
* @param text 源代码
|
|
756
|
+
* @param text source Wikitext / 源代码
|
|
723
757
|
* @param position 位置
|
|
724
758
|
*/
|
|
725
759
|
async provideSignatureHelp(text, position) {
|
|
@@ -732,36 +766,47 @@ class LanguageService {
|
|
|
732
766
|
throw new Error('This is a regular language server!');
|
|
733
767
|
}
|
|
734
768
|
this.#signature = true;
|
|
735
|
-
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)}}}`)
|
|
736
|
-
if (
|
|
769
|
+
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)}}}`);
|
|
770
|
+
if (!lastChild.is('magic-word') || lastChild.length === 1) {
|
|
737
771
|
return undefined;
|
|
738
772
|
}
|
|
739
|
-
const info = this.#getParserFunction(name);
|
|
773
|
+
const { name, childNodes, firstChild } = lastChild, info = this.#getParserFunction(name);
|
|
740
774
|
if (!info?.signatures) {
|
|
741
775
|
return undefined;
|
|
742
776
|
}
|
|
743
|
-
const
|
|
744
|
-
let activeParameter = childNodes.findIndex(child => child.getRelativeIndex() > character - start) - 2;
|
|
745
|
-
if (activeParameter === -3) {
|
|
746
|
-
activeParameter = n - 1;
|
|
747
|
-
}
|
|
748
|
-
const signatures = info.signatures.filter(params => (params.length >= n || params[params.length - 1]?.rest)
|
|
777
|
+
const n = childNodes.length - 1, candidates = info.signatures.filter(params => (params.length >= n || params[params.length - 1]?.rest)
|
|
749
778
|
&& params.every(({ label, const: c }, i) => {
|
|
750
|
-
const p = c && i < n && childNodes[i + 1]?.
|
|
751
|
-
return !p || label.
|
|
752
|
-
}))
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
779
|
+
const p = c && i < n && childNodes[i + 1]?.toString(true).trim();
|
|
780
|
+
return !p || label.toLowerCase().includes(p.toLowerCase());
|
|
781
|
+
}));
|
|
782
|
+
if (candidates.length === 0) {
|
|
783
|
+
return undefined;
|
|
784
|
+
}
|
|
785
|
+
let j = -1;
|
|
786
|
+
for (let cur = lastChild.getAbsoluteIndex() + lastChild.getAttribute('padding'); j < n; j++) {
|
|
787
|
+
cur += childNodes[j + 1].toString().length + 1;
|
|
788
|
+
if (cur > character) {
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const f = firstChild.toString(true).trim();
|
|
793
|
+
return {
|
|
794
|
+
signatures: candidates.map((params) => ({
|
|
795
|
+
label: `{{${f}${params.length === 0 ? '' : ':'}${params.map(({ label }) => label).join('|')}}}`,
|
|
796
|
+
parameters: params.map(({ label, const: c }) => ({
|
|
797
|
+
label,
|
|
798
|
+
...c ? { documentation: 'Predefined parameter' } : undefined,
|
|
799
|
+
})),
|
|
800
|
+
...params.length < n ? { activeParameter: Math.min(j, params.length - 1) } : undefined,
|
|
757
801
|
})),
|
|
758
|
-
|
|
759
|
-
}
|
|
760
|
-
return { signatures, activeParameter };
|
|
802
|
+
activeParameter: j,
|
|
803
|
+
};
|
|
761
804
|
}
|
|
762
805
|
/**
|
|
806
|
+
* Provide CodeLens
|
|
807
|
+
*
|
|
763
808
|
* 提供 CodeLens
|
|
764
|
-
* @param text 源代码
|
|
809
|
+
* @param text source Wikitext / 源代码
|
|
765
810
|
*/
|
|
766
811
|
async provideInlayHints(text) {
|
|
767
812
|
this.#checkSignature();
|
|
@@ -769,7 +814,7 @@ class LanguageService {
|
|
|
769
814
|
for (const template of root.querySelectorAll('template,magic-word#invoke')) {
|
|
770
815
|
const { type, childNodes } = template;
|
|
771
816
|
hints.push(...childNodes.slice(type === 'template' ? 1 : 3).filter(({ anon }) => anon)
|
|
772
|
-
.map((parameter) => ({
|
|
817
|
+
.reverse().map((parameter) => ({
|
|
773
818
|
position: positionAt(root, parameter.getAbsoluteIndex()),
|
|
774
819
|
label: `${parameter.name}=`,
|
|
775
820
|
kind: 2,
|
|
@@ -778,7 +823,12 @@ class LanguageService {
|
|
|
778
823
|
return hints;
|
|
779
824
|
}
|
|
780
825
|
/* NOT FOR BROWSER ONLY */
|
|
781
|
-
/**
|
|
826
|
+
/**
|
|
827
|
+
* Provide quick fixes
|
|
828
|
+
*
|
|
829
|
+
* 提供快速修复建议
|
|
830
|
+
* @param diagnostics grammar diagnostics / 语法诊断信息
|
|
831
|
+
*/
|
|
782
832
|
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
783
833
|
provideCodeAction(diagnostics) {
|
|
784
834
|
return diagnostics.flatMap(diagnostic => diagnostic.data.map((data) => ({
|
|
@@ -792,11 +842,52 @@ class LanguageService {
|
|
|
792
842
|
})));
|
|
793
843
|
}
|
|
794
844
|
/**
|
|
845
|
+
* Provide document sections
|
|
846
|
+
*
|
|
795
847
|
* 提供章节
|
|
796
|
-
* @param text 源代码
|
|
848
|
+
* @param text source Wikitext / 源代码
|
|
797
849
|
*/
|
|
798
850
|
async provideDocumentSymbols(text) {
|
|
799
|
-
|
|
851
|
+
this.#checkSignature();
|
|
852
|
+
const root = await this.#queue(text), lines = root.getLines(), { length } = lines, symbols = [], names = new Set(), sections = new Array(6), tokens = root.querySelectorAll('heading-title');
|
|
853
|
+
for (const token of [...tokens].reverse()) { // 提高 getBoundingClientRect 的性能
|
|
854
|
+
token.getRelativeIndex();
|
|
855
|
+
}
|
|
856
|
+
for (const token of tokens) {
|
|
857
|
+
const { top, height, left, width } = token.getBoundingClientRect();
|
|
858
|
+
if (token.type === 'heading-title') {
|
|
859
|
+
const { level } = token.parentNode;
|
|
860
|
+
for (let i = level - 1; i < 6; i++) {
|
|
861
|
+
getSectionEnd(sections[i], lines, top - 1);
|
|
862
|
+
sections[i] = undefined;
|
|
863
|
+
}
|
|
864
|
+
const section = token.text().trim() || ' ', name = names.has(section)
|
|
865
|
+
? new Array(names.size).fill('').map((_, i) => `${section.trim()}_${i + 2}`)
|
|
866
|
+
.find(s => !names.has(s))
|
|
867
|
+
: section, container = sections.slice(0, level - 1).reverse().find(Boolean), selectionRange = {
|
|
868
|
+
start: { line: top, character: left - level },
|
|
869
|
+
end: (0, lint_1.getEndPos)(top, left, height, width + level),
|
|
870
|
+
}, info = {
|
|
871
|
+
name,
|
|
872
|
+
kind: 15,
|
|
873
|
+
range: { start: selectionRange.start },
|
|
874
|
+
selectionRange,
|
|
875
|
+
};
|
|
876
|
+
names.add(name);
|
|
877
|
+
sections[level - 1] = info;
|
|
878
|
+
if (container) {
|
|
879
|
+
container.children ??= [];
|
|
880
|
+
container.children.push(info);
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
symbols.push(info);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
for (const section of sections) {
|
|
888
|
+
getSectionEnd(section, lines, length - 1);
|
|
889
|
+
}
|
|
890
|
+
return symbols;
|
|
800
891
|
}
|
|
801
892
|
}
|
|
802
893
|
exports.LanguageService = LanguageService;
|