wikiparser-node 1.17.1 → 1.18.1
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 +19 -0
- package/bin/config.js +5 -0
- package/bundle/bundle-es7.min.js +34 -0
- package/bundle/bundle-lsp.min.js +38 -0
- package/bundle/bundle.min.js +22 -22
- package/config/.schema.json +2 -2
- package/config/default.json +86 -23
- package/config/enwiki.json +20 -13
- package/config/llwiki.json +99 -7
- package/config/minimum.json +6 -3
- package/config/moegirl.json +12 -12
- package/config/zhwiki.json +52 -21
- package/data/ext/score.json +1033 -0
- package/data/signatures.json +141 -353
- package/dist/base.d.mts +9 -1
- package/dist/base.d.ts +9 -1
- package/dist/bin/config.js +118 -0
- package/dist/index.js +4 -4
- package/dist/lib/document.d.ts +2 -1
- package/dist/lib/document.js +10 -5
- package/dist/lib/lsp.d.ts +21 -2
- package/dist/lib/lsp.js +372 -89
- package/dist/src/attribute.js +10 -4
- package/dist/src/index.js +2 -2
- package/dist/src/nowiki/doubleUnderscore.js +3 -2
- package/dist/util/diff.js +1 -1
- package/dist/util/lint.js +31 -1
- package/extensions/dist/base.js +2 -2
- package/extensions/es7/base.js +2 -2
- package/i18n/zh-hans.json +1 -0
- package/i18n/zh-hant.json +1 -0
- package/package.json +25 -16
- package/bundle/bundle.es7.js +0 -34
- package/bundle/bundle.lsp.js +0 -38
- package/dist/util/sharable.d.ts +0 -1
package/dist/lib/lsp.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.LanguageService = exports.tasks = void 0;
|
|
6
|
+
exports.LanguageService = exports.isAttr = exports.tasks = void 0;
|
|
7
7
|
const common_1 = require("@bhsd/common");
|
|
8
8
|
const sharable_1 = require("../util/sharable");
|
|
9
9
|
const lint_1 = require("../util/lint");
|
|
@@ -13,15 +13,19 @@ const index_1 = __importDefault(require("../index"));
|
|
|
13
13
|
const constants_1 = require("../util/constants");
|
|
14
14
|
/* NOT FOR BROWSER END */
|
|
15
15
|
/* NOT FOR BROWSER ONLY */
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
17
|
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const util_1 = __importDefault(require("util"));
|
|
19
|
+
const child_process_1 = require("child_process");
|
|
20
|
+
const crypto_1 = require("crypto");
|
|
17
21
|
const stylelint_1 = require("@bhsd/common/dist/stylelint");
|
|
22
|
+
const config_1 = __importDefault(require("../bin/config"));
|
|
18
23
|
const document_1 = require("./document");
|
|
19
|
-
/* NOT FOR BROWSER ONLY */
|
|
20
24
|
/** @see https://www.npmjs.com/package/stylelint-config-recommended */
|
|
21
25
|
const cssRules = {
|
|
22
26
|
'block-no-empty': null,
|
|
23
27
|
'property-no-unknown': null,
|
|
24
|
-
}, jsonSelector = document_1.jsonTags.map(s => `ext-inner#${s}`).join();
|
|
28
|
+
}, jsonSelector = document_1.jsonTags.map(s => `ext-inner#${s}`).join(), scores = new Map();
|
|
25
29
|
/* NOT FOR BROWSER ONLY END */
|
|
26
30
|
exports.tasks = new WeakMap();
|
|
27
31
|
const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']), nameAttrs = new Set(['name', 'extends', 'follow']), groupAttrs = new Set(['group']), renameTypes = new Set([
|
|
@@ -39,11 +43,30 @@ const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']
|
|
|
39
43
|
'heading',
|
|
40
44
|
...renameTypes,
|
|
41
45
|
]), plainTypes = new Set(['text', 'comment', 'noinclude', 'include']), cssSelector = ['ext', 'html', 'table'].map(s => `${s}-attr#style`).join();
|
|
46
|
+
/**
|
|
47
|
+
* Check if a token is a plain attribute.
|
|
48
|
+
* @param token
|
|
49
|
+
* @param token.type
|
|
50
|
+
* @param token.parentNode
|
|
51
|
+
* @param token.length
|
|
52
|
+
* @param token.firstChild
|
|
53
|
+
* @param style whether it is a style attribute
|
|
54
|
+
*/
|
|
55
|
+
const isAttr = ({ type, parentNode, length, firstChild }, style) => type === 'attr-value' && length === 1 && firstChild.type === 'text'
|
|
56
|
+
&& (!style
|
|
57
|
+
|| parentNode.name === 'style'
|
|
58
|
+
&& Boolean(document_1.cssLSP));
|
|
59
|
+
exports.isAttr = isAttr;
|
|
60
|
+
/**
|
|
61
|
+
* Check if a token is an HTML attribute.
|
|
62
|
+
* @param token
|
|
63
|
+
*/
|
|
64
|
+
const isHtmlAttr = (token) => token.type === 'html-attr' || token.type === 'table-attr';
|
|
42
65
|
/**
|
|
43
66
|
* Check if all child nodes are plain text or comments.
|
|
44
|
-
* @param
|
|
67
|
+
* @param token
|
|
45
68
|
*/
|
|
46
|
-
const isPlain = (
|
|
69
|
+
const isPlain = (token) => token.childNodes.every(({ type }) => plainTypes.has(type));
|
|
47
70
|
/**
|
|
48
71
|
* Get the position of a character in the document.
|
|
49
72
|
* @param root root token
|
|
@@ -83,28 +106,39 @@ const createNodeRange = (token) => {
|
|
|
83
106
|
* @param pos.line line number
|
|
84
107
|
* @param pos.character character number
|
|
85
108
|
* @param extra extra text
|
|
109
|
+
* @param getDoc documentation method
|
|
86
110
|
*/
|
|
87
|
-
const getCompletion = (words, kind, mt, { line, character }, extra) => [...new Set(words)].map((w) =>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
111
|
+
const getCompletion = (words, kind, mt, { line, character }, extra, getDoc) => [...new Set(words)].map((w) => {
|
|
112
|
+
const doc = getDoc?.(w)?.description;
|
|
113
|
+
return {
|
|
114
|
+
label: w,
|
|
115
|
+
kind,
|
|
116
|
+
textEdit: {
|
|
117
|
+
range: {
|
|
118
|
+
start: { line, character: character - mt.length },
|
|
119
|
+
end: { line, character },
|
|
120
|
+
},
|
|
121
|
+
newText: w + (extra ?? ''),
|
|
94
122
|
},
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
123
|
+
...doc && {
|
|
124
|
+
documentation: {
|
|
125
|
+
kind: 'markdown',
|
|
126
|
+
value: doc,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
});
|
|
98
131
|
/**
|
|
99
132
|
* Get the caret position at the position from a word.
|
|
100
133
|
* @param root root token
|
|
134
|
+
* @param text source code
|
|
101
135
|
* @param pos position
|
|
102
136
|
* @param pos.line line number
|
|
103
137
|
* @param pos.character character number
|
|
104
138
|
*/
|
|
105
|
-
const caretPositionFromWord = (root, { line, character }) => {
|
|
139
|
+
const caretPositionFromWord = (root, text, { line, character }) => {
|
|
106
140
|
const index = root.indexFromPos(line, character);
|
|
107
|
-
return root.caretPositionFromIndex(index + Number(/\w/u.test(
|
|
141
|
+
return root.caretPositionFromIndex(index + Number(/\w/u.test(text.charAt(index))));
|
|
108
142
|
};
|
|
109
143
|
/**
|
|
110
144
|
* Get the attribute of a `<ref>` tag.
|
|
@@ -170,15 +204,13 @@ const getQuickFix = (root, fix, preferred = false) => ({
|
|
|
170
204
|
});
|
|
171
205
|
/* NOT FOR BROWSER ONLY */
|
|
172
206
|
/**
|
|
173
|
-
*
|
|
174
|
-
* @param
|
|
175
|
-
* @param
|
|
176
|
-
* @param line line number
|
|
177
|
-
* @param column column number
|
|
207
|
+
* Correct the position of an error.
|
|
208
|
+
* @param height
|
|
209
|
+
* @param width
|
|
210
|
+
* @param line 0-based line number
|
|
211
|
+
* @param column 0-based column number
|
|
178
212
|
*/
|
|
179
|
-
const
|
|
180
|
-
const { top, left, height, width } = rect, start = bottom - height - 1;
|
|
181
|
-
line -= start;
|
|
213
|
+
const adjustPos = (height, width, line, column) => {
|
|
182
214
|
if (line === 0) {
|
|
183
215
|
line = 1;
|
|
184
216
|
column = 0;
|
|
@@ -187,8 +219,39 @@ const getStylelintPos = (rect, bottom, line, column) => {
|
|
|
187
219
|
line = height;
|
|
188
220
|
column = width;
|
|
189
221
|
}
|
|
222
|
+
return [line, column];
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Get the position of a Stylelint error.
|
|
226
|
+
* @param rect bounding client rect of the token
|
|
227
|
+
* @param bottom bottom of the style block
|
|
228
|
+
* @param line line number
|
|
229
|
+
* @param column column number
|
|
230
|
+
*/
|
|
231
|
+
const getStylelintPos = (rect, bottom, line, column) => {
|
|
232
|
+
const { top, left, height, width } = rect, start = bottom - height - 1;
|
|
233
|
+
line -= start;
|
|
234
|
+
[line, column] = adjustPos(height, width, line, column);
|
|
190
235
|
return (0, lint_1.getEndPos)(top, left, line, column);
|
|
191
236
|
};
|
|
237
|
+
/**
|
|
238
|
+
* Convert LilyPond errors to VSCode diagnostics.
|
|
239
|
+
* @param root root token
|
|
240
|
+
* @param token `<score>` extension token
|
|
241
|
+
* @param errors LilyPond errors
|
|
242
|
+
*/
|
|
243
|
+
const getLilyPondDiagnostics = (root, token, errors) => {
|
|
244
|
+
const { top, left } = root.posFromIndex(token.lastChild.getAbsoluteIndex());
|
|
245
|
+
return errors.map(({ line, col, message }) => {
|
|
246
|
+
const pos = (0, lint_1.getEndPos)(top, left, line, col);
|
|
247
|
+
return {
|
|
248
|
+
range: { start: pos, end: pos },
|
|
249
|
+
severity: 1,
|
|
250
|
+
source: 'LilyPond',
|
|
251
|
+
message,
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
};
|
|
192
255
|
/**
|
|
193
256
|
* Get the end position of a section.
|
|
194
257
|
* @param section section
|
|
@@ -215,23 +278,49 @@ class LanguageService {
|
|
|
215
278
|
#completionConfig;
|
|
216
279
|
include = true;
|
|
217
280
|
/** @private */
|
|
281
|
+
config;
|
|
282
|
+
/** @private */
|
|
218
283
|
data;
|
|
284
|
+
/* NOT FOR BROWSER ONLY */
|
|
285
|
+
lilypond;
|
|
286
|
+
/** @private */
|
|
287
|
+
lilypondData;
|
|
288
|
+
/* NOT FOR BROWSER ONLY END */
|
|
219
289
|
/** @param uri 任务标识 */
|
|
220
290
|
constructor(uri) {
|
|
221
291
|
exports.tasks.set(uri, this);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
292
|
+
Object.defineProperties(this, {
|
|
293
|
+
config: { enumerable: false },
|
|
294
|
+
/* NOT FOR BROWSER ONLY */
|
|
295
|
+
data: {
|
|
296
|
+
value: require(path_1.default.join('..', '..', 'data', 'signatures')),
|
|
297
|
+
enumerable: false,
|
|
298
|
+
},
|
|
299
|
+
lilypondData: {
|
|
300
|
+
value: require(path_1.default.join('..', '..', 'data', 'ext', 'score')),
|
|
301
|
+
enumerable: false,
|
|
302
|
+
},
|
|
226
303
|
});
|
|
227
304
|
}
|
|
228
305
|
/** @implements */
|
|
229
306
|
destroy() {
|
|
230
307
|
Object.setPrototypeOf(this, null);
|
|
308
|
+
/* NOT FOR BROWSER ONLY */
|
|
309
|
+
const dir = path_1.default.join(__dirname, 'lilypond');
|
|
310
|
+
if (fs_1.default.existsSync(dir)) {
|
|
311
|
+
for (const file of fs_1.default.readdirSync(dir)) {
|
|
312
|
+
(async () => {
|
|
313
|
+
try {
|
|
314
|
+
await fs_1.default.promises.unlink(path_1.default.join(dir, file));
|
|
315
|
+
}
|
|
316
|
+
catch { }
|
|
317
|
+
})();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
231
320
|
}
|
|
232
321
|
/** 检查解析设置有无更新 */
|
|
233
322
|
#checkConfig() {
|
|
234
|
-
return this.#config ===
|
|
323
|
+
return this.#config === this.config && this.#include === this.include;
|
|
235
324
|
}
|
|
236
325
|
/**
|
|
237
326
|
* 提交解析任务
|
|
@@ -257,10 +346,10 @@ class LanguageService {
|
|
|
257
346
|
* - 总是返回最新的解析结果
|
|
258
347
|
*/
|
|
259
348
|
async #parse() {
|
|
260
|
-
|
|
261
|
-
this.#config =
|
|
349
|
+
this.config ??= index_1.default.getConfig();
|
|
350
|
+
this.#config = this.config;
|
|
262
351
|
this.#include = this.include;
|
|
263
|
-
const text = this.#text, root = await index_1.default.partialParse(text, () => this.#text, this.include, config);
|
|
352
|
+
const text = this.#text, root = await index_1.default.partialParse(text, () => this.#text, this.include, this.config);
|
|
264
353
|
if (this.#checkConfig() && this.#text === text) {
|
|
265
354
|
this.#done = root;
|
|
266
355
|
this.#running = undefined;
|
|
@@ -295,10 +384,10 @@ class LanguageService {
|
|
|
295
384
|
* - 总是返回最新的解析结果
|
|
296
385
|
*/
|
|
297
386
|
async #parseSignature() {
|
|
298
|
-
|
|
299
|
-
this.#config =
|
|
387
|
+
this.config ??= index_1.default.getConfig();
|
|
388
|
+
this.#config = this.config;
|
|
300
389
|
this.#include = this.include;
|
|
301
|
-
const text = this.#text2, root = await index_1.default.partialParse(text, () => this.#text2, this.include, config);
|
|
390
|
+
const text = this.#text2, root = await index_1.default.partialParse(text, () => this.#text2, this.include, this.config);
|
|
302
391
|
if (this.#checkConfig() && this.#text2 === text) {
|
|
303
392
|
this.#done2 = root;
|
|
304
393
|
this.#running2 = undefined;
|
|
@@ -319,14 +408,38 @@ class LanguageService {
|
|
|
319
408
|
*/
|
|
320
409
|
async provideDocumentColors(rgba, text, hsl = true) {
|
|
321
410
|
const root = await this.#queue(text);
|
|
411
|
+
/* NOT FOR BROWSER ONLY */
|
|
412
|
+
let colors;
|
|
413
|
+
try {
|
|
414
|
+
colors = new RegExp(String.raw `\b${Object.keys((await import('color-name')).default).join('|')}\b`, 'giu');
|
|
415
|
+
}
|
|
416
|
+
catch { }
|
|
417
|
+
/* NOT FOR BROWSER ONLY END */
|
|
322
418
|
return root.querySelectorAll('attr-value,parameter-value,arg-default').reverse()
|
|
323
|
-
.flatMap(
|
|
324
|
-
|
|
419
|
+
.flatMap(token => {
|
|
420
|
+
const { type, childNodes,
|
|
421
|
+
/* NOT FOR BROWSER ONLY */
|
|
422
|
+
parentNode, } = token;
|
|
423
|
+
if (type !== 'attr-value' && !isPlain(token)) {
|
|
325
424
|
return [];
|
|
425
|
+
/* NOT FOR BROWSER ONLY */
|
|
326
426
|
}
|
|
427
|
+
else if ((0, exports.isAttr)(token, true)) {
|
|
428
|
+
const textDoc = new document_1.EmbeddedCSSDocument(root, token);
|
|
429
|
+
return document_1.cssLSP.findDocumentColors(textDoc, textDoc.styleSheet);
|
|
430
|
+
/* NOT FOR BROWSER ONLY END */
|
|
431
|
+
}
|
|
432
|
+
/* NOT FOR BROWSER ONLY */
|
|
433
|
+
const isStyle = colors && type === 'attr-value' && parentNode.name === 'style';
|
|
434
|
+
/* NOT FOR BROWSER ONLY END */
|
|
327
435
|
return childNodes.filter((child) => child.type === 'text').reverse()
|
|
328
436
|
.flatMap(child => {
|
|
329
|
-
const parts = (0, common_1.splitColors)(
|
|
437
|
+
const { data } = child, parts = (0, common_1.splitColors)(data, hsl).filter(([, , , isColor]) => isColor);
|
|
438
|
+
/* NOT FOR BROWSER ONLY */
|
|
439
|
+
if (isStyle) {
|
|
440
|
+
parts.push(...[...data.matchAll(colors)].map(({ index, 0: s }) => [s, index, index + s.length, true]));
|
|
441
|
+
}
|
|
442
|
+
/* NOT FOR BROWSER ONLY END */
|
|
330
443
|
if (parts.length === 0) {
|
|
331
444
|
return [];
|
|
332
445
|
}
|
|
@@ -364,8 +477,9 @@ class LanguageService {
|
|
|
364
477
|
}
|
|
365
478
|
/** 准备自动补全设置 */
|
|
366
479
|
#prepareCompletionConfig() {
|
|
367
|
-
if (!this.#completionConfig) {
|
|
368
|
-
|
|
480
|
+
if (!this.#completionConfig || this.#completionConfig[1] !== this.config) {
|
|
481
|
+
this.config ??= index_1.default.getConfig();
|
|
482
|
+
const { nsid, ext, html, parserFunction: [insensitive, sensitive, ...other], doubleUnderscore, protocol, img, } = this.config, tags = new Set([ext, html].flat(2));
|
|
369
483
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions, es-x/no-regexp-unicode-property-escapes
|
|
370
484
|
/(?:<\/?(\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;
|
|
371
485
|
const re = new RegExp('(?:' // eslint-disable-line prefer-template
|
|
@@ -382,23 +496,27 @@ class LanguageService {
|
|
|
382
496
|
// attribute key
|
|
383
497
|
+ String.raw `<(\w+)(?:\s(?:[^<>{}|=\s]+(?:\s*=\s*(?:[^\s"']\S*|(["']).*?\8))?(?=\s))*)?\s(\w*)`
|
|
384
498
|
+ ')$', 'iu');
|
|
385
|
-
this.#completionConfig =
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
499
|
+
this.#completionConfig = [
|
|
500
|
+
{
|
|
501
|
+
re,
|
|
502
|
+
ext,
|
|
503
|
+
tags,
|
|
504
|
+
allTags: [...tags, 'onlyinclude', 'includeonly', 'noinclude'],
|
|
505
|
+
functions: [
|
|
506
|
+
Object.keys(insensitive),
|
|
507
|
+
Array.isArray(sensitive) ? /* istanbul ignore next */ sensitive : Object.keys(sensitive),
|
|
508
|
+
other,
|
|
509
|
+
].flat(2),
|
|
510
|
+
switches: doubleUnderscore.slice(0, 2).flat().map(w => `__${w}__`),
|
|
511
|
+
protocols: protocol.split('|'),
|
|
512
|
+
params: Object.keys(img)
|
|
513
|
+
.filter(k => k.endsWith('$1') || !k.includes('$1'))
|
|
514
|
+
.map(k => k.replace(/\$1$/u, '')),
|
|
515
|
+
},
|
|
516
|
+
this.config,
|
|
517
|
+
];
|
|
400
518
|
}
|
|
401
|
-
return this.#completionConfig;
|
|
519
|
+
return this.#completionConfig[0];
|
|
402
520
|
}
|
|
403
521
|
/**
|
|
404
522
|
* Provide auto-completion
|
|
@@ -408,13 +526,25 @@ class LanguageService {
|
|
|
408
526
|
* @param position 位置
|
|
409
527
|
*/
|
|
410
528
|
async provideCompletionItems(text, position) {
|
|
411
|
-
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) ?? '');
|
|
529
|
+
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) ?? ''), [, , iAlias = {}, sAlias = {}] = this.config.doubleUnderscore;
|
|
412
530
|
if (mt?.[1] !== undefined) { // tag
|
|
413
531
|
const closing = mt[1].startsWith('/');
|
|
414
532
|
return getCompletion(allTags, 'Class', mt[1].slice(closing ? 1 : 0), position, closing && !curLine?.slice(character).trim().startsWith('>') ? '>' : '');
|
|
415
533
|
}
|
|
416
534
|
else if (mt?.[4]) { // behavior switch
|
|
417
|
-
return getCompletion(switches, 'Constant', mt[4], position
|
|
535
|
+
return getCompletion(switches, 'Constant', mt[4], position, '', name => {
|
|
536
|
+
if (!this.data) {
|
|
537
|
+
return undefined;
|
|
538
|
+
}
|
|
539
|
+
name = name.slice(2, -2);
|
|
540
|
+
if (name in iAlias) {
|
|
541
|
+
name = iAlias[name];
|
|
542
|
+
}
|
|
543
|
+
else if (name in sAlias) {
|
|
544
|
+
name = sAlias[name];
|
|
545
|
+
}
|
|
546
|
+
return this.#getBehaviorSwitch(name.toLowerCase());
|
|
547
|
+
});
|
|
418
548
|
}
|
|
419
549
|
else if (mt?.[5] !== undefined) { // protocol
|
|
420
550
|
return getCompletion(protocols, 'Reference', mt[5], position);
|
|
@@ -428,12 +558,23 @@ class LanguageService {
|
|
|
428
558
|
return getCompletion(root.querySelectorAll('arg').filter(token => token.name && token !== cur)
|
|
429
559
|
.map(({ name }) => name), 'Variable', match, position);
|
|
430
560
|
}
|
|
431
|
-
const colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
|
|
561
|
+
const [insensitive, sensitive] = this.config.parserFunction, isOld = Array.isArray(sensitive), colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
|
|
432
562
|
return mt[2] === '[['
|
|
433
563
|
? getCompletion(// link
|
|
434
564
|
root.querySelectorAll('link,file,category,redirect-target').filter(token => token !== cur).map(({ name }) => name), 'Folder', str, position)
|
|
435
565
|
: [
|
|
436
|
-
...getCompletion(functions, 'Function', match, position
|
|
566
|
+
...getCompletion(functions, 'Function', match, position, '', name => {
|
|
567
|
+
if (!this.data) {
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
else if (name in insensitive) {
|
|
571
|
+
name = insensitive[name];
|
|
572
|
+
}
|
|
573
|
+
else if (!isOld && name in sensitive) {
|
|
574
|
+
name = sensitive[name];
|
|
575
|
+
}
|
|
576
|
+
return this.#getParserFunction(name.toLowerCase());
|
|
577
|
+
}),
|
|
437
578
|
...match.startsWith('#')
|
|
438
579
|
? []
|
|
439
580
|
: getCompletion(root.querySelectorAll('template').filter(token => token !== cur)
|
|
@@ -493,7 +634,7 @@ class LanguageService {
|
|
|
493
634
|
if (t === 'magic-word' && n !== 'invoke') {
|
|
494
635
|
return undefined;
|
|
495
636
|
}
|
|
496
|
-
const
|
|
637
|
+
const key = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)).trimStart(), [module, func] = t === 'magic-word' ? transclusion.getModule() : [];
|
|
497
638
|
return key
|
|
498
639
|
? getCompletion(root.querySelectorAll('parameter').filter(token => {
|
|
499
640
|
if (token === parentNode
|
|
@@ -511,7 +652,7 @@ class LanguageService {
|
|
|
511
652
|
: undefined;
|
|
512
653
|
/* NOT FOR BROWSER ONLY */
|
|
513
654
|
}
|
|
514
|
-
else if (
|
|
655
|
+
else if ((0, exports.isAttr)(cur, true)) {
|
|
515
656
|
const textDoc = new document_1.EmbeddedCSSDocument(root, cur);
|
|
516
657
|
return document_1.cssLSP.doComplete(textDoc, position, textDoc.styleSheet).items.map((item) => ({
|
|
517
658
|
...item,
|
|
@@ -524,8 +665,37 @@ class LanguageService {
|
|
|
524
665
|
else if (document_1.jsonLSP && type === 'ext-inner' && document_1.jsonTags.includes(cur.name)) {
|
|
525
666
|
const textDoc = new document_1.EmbeddedJSONDocument(root, cur);
|
|
526
667
|
return (await document_1.jsonLSP.doComplete(textDoc, position, textDoc.jsonDoc))?.items;
|
|
668
|
+
}
|
|
669
|
+
else if (type === 'ext-inner' && cur.name === 'score') {
|
|
670
|
+
const lang = parentNode.getAttr('lang');
|
|
671
|
+
if (lang !== undefined && lang !== 'lilypond') {
|
|
672
|
+
return undefined;
|
|
673
|
+
}
|
|
674
|
+
const before = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)), comment = before.lastIndexOf('%');
|
|
675
|
+
if (comment !== -1
|
|
676
|
+
&& (before.charAt(comment + 1) === '{' || !before.slice(comment).includes('\n'))) {
|
|
677
|
+
return undefined;
|
|
678
|
+
}
|
|
679
|
+
const word = /\\?\b(?:\w|\b(?:->?|\.)|\bly:)+$/u.exec(curLine.slice(0, character))?.[0];
|
|
680
|
+
if (word) {
|
|
681
|
+
const { lilypondData } = this;
|
|
682
|
+
return word.startsWith('\\')
|
|
683
|
+
? getCompletion(lilypondData.filter(w => w.startsWith('\\')), 'Function', word, position)
|
|
684
|
+
: [
|
|
685
|
+
...getCompletion(lilypondData.filter(w => /^[a-z]/u.test(w)), 'Variable', word, position),
|
|
686
|
+
...getCompletion(lilypondData.filter(w => /^[A-Z]/u.test(w)), 'Class', word, position),
|
|
687
|
+
];
|
|
688
|
+
}
|
|
527
689
|
/* NOT FOR BROWSER ONLY END */
|
|
528
690
|
}
|
|
691
|
+
else if ((0, exports.isAttr)(cur) && isHtmlAttr(parentNode)) {
|
|
692
|
+
const data = lint_1.htmlData.provideValues(parentNode.tag, parentNode.name);
|
|
693
|
+
if (data.length === 0) {
|
|
694
|
+
return undefined;
|
|
695
|
+
}
|
|
696
|
+
const val = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)).trimStart();
|
|
697
|
+
return getCompletion(data.map(({ name }) => name), 'Value', val, position);
|
|
698
|
+
}
|
|
529
699
|
return undefined;
|
|
530
700
|
}
|
|
531
701
|
/**
|
|
@@ -559,13 +729,13 @@ class LanguageService {
|
|
|
559
729
|
],
|
|
560
730
|
})),
|
|
561
731
|
/* eslint-disable @stylistic/operator-linebreak */
|
|
562
|
-
cssDiagnostics = document_1.stylelint ?
|
|
732
|
+
cssDiagnostics = await document_1.stylelint ?
|
|
563
733
|
await (async () => {
|
|
564
734
|
const tokens = this.findStyleTokens();
|
|
565
735
|
if (tokens.length === 0) {
|
|
566
736
|
return [];
|
|
567
737
|
}
|
|
568
|
-
const cssErrors = await (0, stylelint_1.styleLint)(await document_1.stylelint, tokens.map(({ type, tag, lastChild }, i) => `${type === 'ext-attr' ? 'div' : tag}#${i}{\n${(0, common_1.sanitizeInlineStyle)(lastChild.toString())}\n}`).join('\n'), cssRules);
|
|
738
|
+
const cssErrors = await (0, stylelint_1.styleLint)((await document_1.stylelint), tokens.map(({ type, tag, lastChild }, i) => `${type === 'ext-attr' ? 'div' : tag}#${i}{\n${(0, common_1.sanitizeInlineStyle)(lastChild.toString())}\n}`).join('\n'), cssRules);
|
|
569
739
|
if (cssErrors.length === 0) {
|
|
570
740
|
return [];
|
|
571
741
|
}
|
|
@@ -602,7 +772,58 @@ class LanguageService {
|
|
|
602
772
|
})) :
|
|
603
773
|
[];
|
|
604
774
|
/* eslint-enable @stylistic/operator-linebreak */
|
|
605
|
-
|
|
775
|
+
/* NOT FOR BROWSER ONLY */
|
|
776
|
+
let lilypondDiagnostics = [];
|
|
777
|
+
if (this.lilypond) {
|
|
778
|
+
const tokens = root.querySelectorAll('ext#score').filter(token => {
|
|
779
|
+
const lang = token.getAttr('lang');
|
|
780
|
+
return (lang === undefined || lang === 'lilypond') && token.innerText;
|
|
781
|
+
});
|
|
782
|
+
if (tokens.length > 0) {
|
|
783
|
+
const dir = path_1.default.join(__dirname, 'lilypond');
|
|
784
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
785
|
+
fs_1.default.mkdirSync(dir);
|
|
786
|
+
}
|
|
787
|
+
lilypondDiagnostics = await Promise.all(tokens.map(async (token) => {
|
|
788
|
+
const { innerText } = token, score = `showLastLength = R1${token.getAttr('raw') === undefined ? ` \\score {\n${innerText}\n}` : `\n${innerText}`}`;
|
|
789
|
+
if (scores.has(score)) {
|
|
790
|
+
return getLilyPondDiagnostics(root, token, scores.get(score));
|
|
791
|
+
}
|
|
792
|
+
const hash = (0, crypto_1.createHash)('sha256');
|
|
793
|
+
hash.update(score);
|
|
794
|
+
const file = path_1.default.join(dir, `${hash.digest('hex')}.ly`);
|
|
795
|
+
fs_1.default.writeFileSync(file, score);
|
|
796
|
+
try {
|
|
797
|
+
await util_1.default.promisify(child_process_1.execFile)(this.lilypond, ['-s', '-o', dir, file]);
|
|
798
|
+
scores.set(score, []);
|
|
799
|
+
}
|
|
800
|
+
catch (e) {
|
|
801
|
+
const { stderr } = e;
|
|
802
|
+
if (stderr) {
|
|
803
|
+
const re = new RegExp(String.raw `^${file}:(\d+):(\d+): error: (.+)$`, 'gmu'), lilypondErrors = [...stderr.matchAll(re)].map(([, line, col, msg]) => {
|
|
804
|
+
const { offsetHeight, offsetWidth } = token.lastChild, pos = adjustPos(offsetHeight, offsetWidth, Number(line) - 1, Number(col) - 1);
|
|
805
|
+
return {
|
|
806
|
+
line: pos[0],
|
|
807
|
+
col: pos[1],
|
|
808
|
+
message: msg,
|
|
809
|
+
};
|
|
810
|
+
});
|
|
811
|
+
scores.set(score, lilypondErrors);
|
|
812
|
+
return getLilyPondDiagnostics(root, token, lilypondErrors);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return [];
|
|
816
|
+
}));
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
/* NOT FOR BROWSER ONLY END */
|
|
820
|
+
return [
|
|
821
|
+
diagnostics,
|
|
822
|
+
cssDiagnostics,
|
|
823
|
+
jsonDiagnostics,
|
|
824
|
+
/* NOT FOR BROWSER ONLY */
|
|
825
|
+
lilypondDiagnostics,
|
|
826
|
+
].flat(2);
|
|
606
827
|
}
|
|
607
828
|
/**
|
|
608
829
|
* Provide folding ranges
|
|
@@ -668,8 +889,9 @@ class LanguageService {
|
|
|
668
889
|
* @param text source Wikitext / 源代码
|
|
669
890
|
*/
|
|
670
891
|
async provideLinks(text) {
|
|
892
|
+
this.config ??= index_1.default.getConfig();
|
|
671
893
|
/^(?:http:\/\/|\/\/)/iu; // eslint-disable-line @typescript-eslint/no-unused-expressions
|
|
672
|
-
const { articlePath, protocol } =
|
|
894
|
+
const { articlePath, protocol } = this.config, absolute = articlePath?.includes('//'), protocolRegex = new RegExp(`^(?:${protocol}|//)`, 'iu');
|
|
673
895
|
return (await this.#queue(text))
|
|
674
896
|
.querySelectorAll(`magic-link,ext-link-url,free-ext-link,attr-value,image-parameter#link${absolute ? ',link-target,template-name,invoke-module' : ''}`)
|
|
675
897
|
.reverse()
|
|
@@ -678,7 +900,7 @@ class LanguageService {
|
|
|
678
900
|
if (!(type !== 'attr-value'
|
|
679
901
|
|| name === 'src' && ['templatestyles', 'img'].includes(tag)
|
|
680
902
|
|| name === 'cite' && ['blockquote', 'del', 'ins', 'q'].includes(tag))
|
|
681
|
-
|| !isPlain(
|
|
903
|
+
|| !isPlain(token)) {
|
|
682
904
|
return false;
|
|
683
905
|
}
|
|
684
906
|
let target = childNodes.filter((node) => node.type === 'text')
|
|
@@ -718,8 +940,7 @@ class LanguageService {
|
|
|
718
940
|
else if (type === 'invoke-module') {
|
|
719
941
|
ns = 828;
|
|
720
942
|
}
|
|
721
|
-
const title = index_1.default
|
|
722
|
-
.normalizeTitle(target, ns, false, undefined, true);
|
|
943
|
+
const title = index_1.default.normalizeTitle(target, ns, false, this.config, true);
|
|
723
944
|
/* istanbul ignore if */
|
|
724
945
|
if (!title.valid) {
|
|
725
946
|
return false;
|
|
@@ -756,7 +977,7 @@ class LanguageService {
|
|
|
756
977
|
* @param position 位置
|
|
757
978
|
*/
|
|
758
979
|
async provideReferences(text, position) {
|
|
759
|
-
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')
|
|
980
|
+
const root = await this.#queue(text), { offsetNode, offset } = caretPositionFromWord(root, this.#text, position), element = offsetNode.type === 'text' ? offsetNode.parentNode : offsetNode, node = offset === 0 && (element.type === 'ext-attr-dirty' || element.type === 'html-attr-dirty')
|
|
760
981
|
? element.parentNode.parentNode
|
|
761
982
|
: element, { type } = node, refName = getRefName(node), refGroup = getRefGroup(node);
|
|
762
983
|
if (!refName && !refGroup && !referenceTypes.has(type)) {
|
|
@@ -836,6 +1057,13 @@ class LanguageService {
|
|
|
836
1057
|
},
|
|
837
1058
|
};
|
|
838
1059
|
}
|
|
1060
|
+
/**
|
|
1061
|
+
* 检索状态开关
|
|
1062
|
+
* @param name 魔术字名
|
|
1063
|
+
*/
|
|
1064
|
+
#getBehaviorSwitch(name) {
|
|
1065
|
+
return this.data.behaviorSwitches.find(({ aliases }) => aliases.includes(name));
|
|
1066
|
+
}
|
|
839
1067
|
/**
|
|
840
1068
|
* 检索解析器函数
|
|
841
1069
|
* @param name 函数名
|
|
@@ -856,40 +1084,74 @@ class LanguageService {
|
|
|
856
1084
|
if (!this.data) {
|
|
857
1085
|
return undefined;
|
|
858
1086
|
}
|
|
859
|
-
const root = await this.#queue(text)
|
|
1087
|
+
const root = await this.#queue(text);
|
|
1088
|
+
let { offsetNode, offset } = caretPositionFromWord(root, this.#text, position);
|
|
1089
|
+
if (offsetNode.type === 'text') {
|
|
1090
|
+
offset += offsetNode.getRelativeIndex();
|
|
1091
|
+
offsetNode = offsetNode.parentNode;
|
|
1092
|
+
}
|
|
1093
|
+
const { type, parentNode, length, name } = offsetNode;
|
|
860
1094
|
let info, f, range;
|
|
861
|
-
if (
|
|
862
|
-
info = this
|
|
1095
|
+
if (offsetNode.is('double-underscore') && offset > 0) {
|
|
1096
|
+
info = this.#getBehaviorSwitch(offsetNode.name);
|
|
863
1097
|
}
|
|
864
1098
|
else if (type === 'magic-word-name') {
|
|
865
1099
|
info = this.#getParserFunction(parentNode.name);
|
|
866
|
-
f =
|
|
1100
|
+
f = offsetNode.toString(true).trim();
|
|
867
1101
|
}
|
|
868
|
-
else if (
|
|
869
|
-
&& (offset > 0 || root.posFromIndex(
|
|
1102
|
+
else if (offsetNode.is('magic-word') && !offsetNode.modifier && length === 1
|
|
1103
|
+
&& (offset > 0 || root.posFromIndex(offsetNode.getAbsoluteIndex()).left === position.character)) {
|
|
870
1104
|
info = this.#getParserFunction(name);
|
|
871
|
-
f =
|
|
1105
|
+
f = offsetNode.firstChild.toString(true).trim();
|
|
872
1106
|
}
|
|
873
|
-
else if ((
|
|
874
|
-
&&
|
|
875
|
-
f =
|
|
1107
|
+
else if ((offsetNode.is('magic-word') || offsetNode.is('template'))
|
|
1108
|
+
&& offsetNode.modifier && offset >= 2 && offsetNode.getRelativeIndex(0) > offset) {
|
|
1109
|
+
f = offsetNode.modifier.trim().slice(0, -1);
|
|
876
1110
|
info = this.#getParserFunction(f.toLowerCase());
|
|
877
1111
|
if (info) {
|
|
878
|
-
const aIndex =
|
|
1112
|
+
const aIndex = offsetNode.getAbsoluteIndex();
|
|
879
1113
|
range = {
|
|
880
1114
|
start: positionAt(root, aIndex + 2),
|
|
881
|
-
end: positionAt(root, aIndex +
|
|
1115
|
+
end: positionAt(root, aIndex + offsetNode.modifier.trimEnd().length + 1),
|
|
882
1116
|
};
|
|
883
1117
|
}
|
|
884
1118
|
/* NOT FOR BROWSER ONLY */
|
|
885
1119
|
}
|
|
886
|
-
else if (
|
|
887
|
-
const textDoc = new document_1.EmbeddedCSSDocument(root,
|
|
1120
|
+
else if ((0, exports.isAttr)(offsetNode, true)) {
|
|
1121
|
+
const textDoc = new document_1.EmbeddedCSSDocument(root, offsetNode);
|
|
888
1122
|
return document_1.cssLSP.doHover(textDoc, position, textDoc.styleSheet) ?? undefined;
|
|
889
1123
|
}
|
|
890
1124
|
else if (document_1.jsonLSP && type === 'ext-inner' && document_1.jsonTags.includes(name)) {
|
|
891
|
-
const textDoc = new document_1.EmbeddedJSONDocument(root,
|
|
1125
|
+
const textDoc = new document_1.EmbeddedJSONDocument(root, offsetNode);
|
|
892
1126
|
return await document_1.jsonLSP.doHover(textDoc, position, textDoc.jsonDoc) ?? undefined;
|
|
1127
|
+
}
|
|
1128
|
+
else if (lint_1.htmlData.provideTags && lint_1.htmlData.provideAttributes) {
|
|
1129
|
+
if (type === 'html' && offset <= offsetNode.getRelativeIndex(0)
|
|
1130
|
+
|| type === 'html-attr-dirty' && offset === 0 && parentNode.firstChild === offsetNode) {
|
|
1131
|
+
const token = type === 'html' ? offsetNode : parentNode.parentNode, data = lint_1.htmlData.provideTags().find(({ name: n }) => n === token.name);
|
|
1132
|
+
if (data?.description) {
|
|
1133
|
+
const start = positionAt(root, token.getAbsoluteIndex());
|
|
1134
|
+
return {
|
|
1135
|
+
contents: data.description,
|
|
1136
|
+
range: {
|
|
1137
|
+
start,
|
|
1138
|
+
end: {
|
|
1139
|
+
line: start.line,
|
|
1140
|
+
character: start.character + token.getRelativeIndex(0),
|
|
1141
|
+
},
|
|
1142
|
+
},
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
else if (type === 'attr-key' && isHtmlAttr(parentNode)) {
|
|
1147
|
+
const data = lint_1.htmlData.provideAttributes(parentNode.tag).find(({ name: n }) => n === parentNode.name);
|
|
1148
|
+
if (data?.description) {
|
|
1149
|
+
return {
|
|
1150
|
+
contents: data.description,
|
|
1151
|
+
range: createNodeRange(offsetNode),
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
893
1155
|
/* NOT FOR BROWSER ONLY END */
|
|
894
1156
|
}
|
|
895
1157
|
return info && {
|
|
@@ -900,7 +1162,7 @@ class LanguageService {
|
|
|
900
1162
|
: '')
|
|
901
1163
|
+ info.description,
|
|
902
1164
|
},
|
|
903
|
-
range: range ?? createNodeRange(
|
|
1165
|
+
range: range ?? createNodeRange(offsetNode),
|
|
904
1166
|
};
|
|
905
1167
|
}
|
|
906
1168
|
/**
|
|
@@ -973,8 +1235,7 @@ class LanguageService {
|
|
|
973
1235
|
}
|
|
974
1236
|
/** @private */
|
|
975
1237
|
findStyleTokens() {
|
|
976
|
-
return this.#done.querySelectorAll(cssSelector)
|
|
977
|
-
.filter(({ lastChild: { length, firstChild } }) => length === 1 && firstChild.type === 'text');
|
|
1238
|
+
return this.#done.querySelectorAll(cssSelector).filter(({ lastChild }) => (0, exports.isAttr)(lastChild));
|
|
978
1239
|
}
|
|
979
1240
|
/* NOT FOR BROWSER ONLY */
|
|
980
1241
|
/**
|
|
@@ -1036,6 +1297,28 @@ class LanguageService {
|
|
|
1036
1297
|
}
|
|
1037
1298
|
return symbols;
|
|
1038
1299
|
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Set the target Wikipedia
|
|
1302
|
+
*
|
|
1303
|
+
* 设置目标维基百科
|
|
1304
|
+
* @param wiki Wikipedia URL / 维基百科网址
|
|
1305
|
+
* @throws `RangeError` 不是有效的维基百科网址
|
|
1306
|
+
*/
|
|
1307
|
+
async setTargetWikipedia(wiki) {
|
|
1308
|
+
const mt = /^https?:\/\/([^./]+)\.wikipedia\.org/iu.exec(wiki);
|
|
1309
|
+
if (!mt) {
|
|
1310
|
+
throw new RangeError('Invalid Wikipedia URL!');
|
|
1311
|
+
}
|
|
1312
|
+
const site = `${mt[1].toLowerCase()}wiki`;
|
|
1313
|
+
try {
|
|
1314
|
+
const config = require(path_1.default.join('..', '..', 'config', site));
|
|
1315
|
+
this.config = index_1.default.getConfig(config);
|
|
1316
|
+
}
|
|
1317
|
+
catch {
|
|
1318
|
+
this.config = index_1.default.getConfig(await (0, config_1.default)(site, `${mt[0]}/w`));
|
|
1319
|
+
}
|
|
1320
|
+
Object.assign(this.config, { articlePath: `${mt[0]}/wiki/` });
|
|
1321
|
+
}
|
|
1039
1322
|
}
|
|
1040
1323
|
exports.LanguageService = LanguageService;
|
|
1041
1324
|
constants_1.classes['LanguageService'] = __filename;
|