wikilint 2.18.0 → 2.18.2

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/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");
@@ -16,6 +16,7 @@ const util_1 = __importDefault(require("util"));
16
16
  const child_process_1 = require("child_process");
17
17
  const crypto_1 = require("crypto");
18
18
  const stylelint_1 = require("@bhsd/common/dist/stylelint");
19
+ const config_1 = __importDefault(require("../bin/config"));
19
20
  const document_1 = require("./document");
20
21
  /** @see https://www.npmjs.com/package/stylelint-config-recommended */
21
22
  const cssRules = {
@@ -27,7 +28,6 @@ exports.tasks = new WeakMap();
27
28
  const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']), nameAttrs = new Set(['name', 'extends', 'follow']), groupAttrs = new Set(['group']), renameTypes = new Set([
28
29
  'arg-name',
29
30
  'template-name',
30
- 'magic-word-name',
31
31
  'link-target',
32
32
  'parameter-key',
33
33
  ]), referenceTypes = new Set([
@@ -37,13 +37,33 @@ const refTags = new Set(['ref']), referencesTags = new Set(['ref', 'references']
37
37
  'image-parameter',
38
38
  'heading-title',
39
39
  'heading',
40
+ 'magic-word-name',
40
41
  ...renameTypes,
41
42
  ]), plainTypes = new Set(['text', 'comment', 'noinclude', 'include']), cssSelector = ['ext', 'html', 'table'].map(s => `${s}-attr#style`).join();
43
+ /**
44
+ * Check if a token is a plain attribute.
45
+ * @param token
46
+ * @param token.type
47
+ * @param token.parentNode
48
+ * @param token.length
49
+ * @param token.firstChild
50
+ * @param style whether it is a style attribute
51
+ */
52
+ const isAttr = ({ type, parentNode, length, firstChild }, style) => type === 'attr-value' && length === 1 && firstChild.type === 'text'
53
+ && (!style
54
+ || parentNode.name === 'style'
55
+ && Boolean(document_1.cssLSP));
56
+ exports.isAttr = isAttr;
57
+ /**
58
+ * Check if a token is an HTML attribute.
59
+ * @param token
60
+ */
61
+ const isHtmlAttr = (token) => token.type === 'html-attr' || token.type === 'table-attr';
42
62
  /**
43
63
  * Check if all child nodes are plain text or comments.
44
- * @param childNodes child nodes
64
+ * @param token
45
65
  */
46
- const isPlain = (childNodes) => childNodes.every(({ type }) => plainTypes.has(type));
66
+ const isPlain = (token) => token.childNodes.every(({ type }) => plainTypes.has(type));
47
67
  /**
48
68
  * Get the position of a character in the document.
49
69
  * @param root root token
@@ -213,11 +233,12 @@ const getStylelintPos = (rect, bottom, line, column) => {
213
233
  };
214
234
  /**
215
235
  * Convert LilyPond errors to VSCode diagnostics.
236
+ * @param root root token
216
237
  * @param token `<score>` extension token
217
238
  * @param errors LilyPond errors
218
239
  */
219
- const getLilyPondDiagnostics = (token, errors) => {
220
- const { top, left } = token.lastChild.getBoundingClientRect();
240
+ const getLilyPondDiagnostics = (root, token, errors) => {
241
+ const { top, left } = root.posFromIndex(token.lastChild.getAbsoluteIndex());
221
242
  return errors.map(({ line, col, message }) => {
222
243
  const pos = (0, lint_1.getEndPos)(top, left, line, col);
223
244
  return {
@@ -254,20 +275,24 @@ class LanguageService {
254
275
  #completionConfig;
255
276
  include = true;
256
277
  /** @private */
278
+ config;
279
+ /** @private */
257
280
  data;
258
281
  /* NOT FOR BROWSER ONLY */
259
282
  lilypond;
283
+ /** @private */
260
284
  lilypondData;
261
285
  /* NOT FOR BROWSER ONLY END */
262
286
  /** @param uri 任务标识 */
263
287
  constructor(uri) {
264
288
  exports.tasks.set(uri, this);
265
- /* NOT FOR BROWSER ONLY */
266
289
  Object.defineProperties(this, {
290
+ config: { enumerable: false },
267
291
  data: {
268
292
  value: require(path_1.default.join('..', '..', 'data', 'signatures')),
269
293
  enumerable: false,
270
294
  },
295
+ /* NOT FOR BROWSER ONLY */
271
296
  lilypondData: {
272
297
  value: require(path_1.default.join('..', '..', 'data', 'ext', 'score')),
273
298
  enumerable: false,
@@ -281,16 +306,18 @@ class LanguageService {
281
306
  const dir = path_1.default.join(__dirname, 'lilypond');
282
307
  if (fs_1.default.existsSync(dir)) {
283
308
  for (const file of fs_1.default.readdirSync(dir)) {
284
- try {
285
- void fs_1.default.promises.unlink(path_1.default.join(dir, file));
286
- }
287
- catch { }
309
+ (async () => {
310
+ try {
311
+ await fs_1.default.promises.unlink(path_1.default.join(dir, file));
312
+ }
313
+ catch { }
314
+ })();
288
315
  }
289
316
  }
290
317
  }
291
318
  /** 检查解析设置有无更新 */
292
319
  #checkConfig() {
293
- return this.#config === index_1.default.config && this.#include === this.include;
320
+ return this.#config === this.config && this.#include === this.include;
294
321
  }
295
322
  /**
296
323
  * 提交解析任务
@@ -316,10 +343,10 @@ class LanguageService {
316
343
  * - 总是返回最新的解析结果
317
344
  */
318
345
  async #parse() {
319
- const config = index_1.default.getConfig();
320
- this.#config = index_1.default.config;
346
+ this.config ??= index_1.default.getConfig();
347
+ this.#config = this.config;
321
348
  this.#include = this.include;
322
- const text = this.#text, root = await index_1.default.partialParse(text, () => this.#text, this.include, config);
349
+ const text = this.#text, root = await index_1.default.partialParse(text, () => this.#text, this.include, this.config);
323
350
  if (this.#checkConfig() && this.#text === text) {
324
351
  this.#done = root;
325
352
  this.#running = undefined;
@@ -354,10 +381,10 @@ class LanguageService {
354
381
  * - 总是返回最新的解析结果
355
382
  */
356
383
  async #parseSignature() {
357
- const config = index_1.default.getConfig();
358
- this.#config = index_1.default.config;
384
+ this.config ??= index_1.default.getConfig();
385
+ this.#config = this.config;
359
386
  this.#include = this.include;
360
- const text = this.#text2, root = await index_1.default.partialParse(text, () => this.#text2, this.include, config);
387
+ const text = this.#text2, root = await index_1.default.partialParse(text, () => this.#text2, this.include, this.config);
361
388
  if (this.#checkConfig() && this.#text2 === text) {
362
389
  this.#done2 = root;
363
390
  this.#running2 = undefined;
@@ -378,14 +405,38 @@ class LanguageService {
378
405
  */
379
406
  async provideDocumentColors(rgba, text, hsl = true) {
380
407
  const root = await this.#queue(text);
408
+ /* NOT FOR BROWSER ONLY */
409
+ let colors;
410
+ try {
411
+ colors = new RegExp(String.raw `\b${Object.keys((await import('color-name')).default).join('|')}\b`, 'giu');
412
+ }
413
+ catch { }
414
+ /* NOT FOR BROWSER ONLY END */
381
415
  return root.querySelectorAll('attr-value,parameter-value,arg-default').reverse()
382
- .flatMap(({ type, childNodes }) => {
383
- if (type !== 'attr-value' && !isPlain(childNodes)) {
416
+ .flatMap(token => {
417
+ const { type, childNodes,
418
+ /* NOT FOR BROWSER ONLY */
419
+ parentNode, } = token;
420
+ if (type !== 'attr-value' && !isPlain(token)) {
384
421
  return [];
422
+ /* NOT FOR BROWSER ONLY */
385
423
  }
424
+ else if ((0, exports.isAttr)(token, true)) {
425
+ const textDoc = new document_1.EmbeddedCSSDocument(root, token);
426
+ return document_1.cssLSP.findDocumentColors(textDoc, textDoc.styleSheet);
427
+ /* NOT FOR BROWSER ONLY END */
428
+ }
429
+ /* NOT FOR BROWSER ONLY */
430
+ const isStyle = colors && type === 'attr-value' && parentNode.name === 'style';
431
+ /* NOT FOR BROWSER ONLY END */
386
432
  return childNodes.filter((child) => child.type === 'text').reverse()
387
433
  .flatMap(child => {
388
- const parts = (0, common_1.splitColors)(child.data, hsl).filter(([, , , isColor]) => isColor);
434
+ const { data } = child, parts = (0, common_1.splitColors)(data, hsl).filter(([, , , isColor]) => isColor);
435
+ /* NOT FOR BROWSER ONLY */
436
+ if (isStyle) {
437
+ parts.push(...[...data.matchAll(colors)].map(({ index, 0: s }) => [s, index, index + s.length, true]));
438
+ }
439
+ /* NOT FOR BROWSER ONLY END */
389
440
  if (parts.length === 0) {
390
441
  return [];
391
442
  }
@@ -423,8 +474,9 @@ class LanguageService {
423
474
  }
424
475
  /** 准备自动补全设置 */
425
476
  #prepareCompletionConfig() {
426
- if (!this.#completionConfig) {
427
- const { nsid, ext, html, parserFunction: [insensitive, sensitive, ...other], doubleUnderscore, protocol, img, } = index_1.default.getConfig(), tags = new Set([ext, html].flat(2));
477
+ if (!this.#completionConfig || this.#completionConfig[1] !== this.config) {
478
+ this.config ??= index_1.default.getConfig();
479
+ const { nsid, ext, html, parserFunction: [insensitive, sensitive, ...other], doubleUnderscore, protocol, img, } = this.config, tags = new Set([ext, html].flat(2));
428
480
  const re = new RegExp('(?:' // eslint-disable-line prefer-template
429
481
  + String.raw `<(\/?\w*)` // tag
430
482
  + '|'
@@ -439,23 +491,27 @@ class LanguageService {
439
491
  // attribute key
440
492
  + String.raw `<(\w+)(?:\s(?:[^<>{}|=\s]+(?:\s*=\s*(?:[^\s"']\S*|(["']).*?\8))?(?=\s))*)?\s(\w*)`
441
493
  + ')$', 'iu');
442
- this.#completionConfig = {
443
- re,
444
- ext,
445
- tags,
446
- allTags: [...tags, 'onlyinclude', 'includeonly', 'noinclude'],
447
- functions: [
448
- Object.keys(insensitive),
449
- Array.isArray(sensitive) ? /* istanbul ignore next */ sensitive : Object.keys(sensitive),
450
- other,
451
- ].flat(2),
452
- switches: doubleUnderscore.slice(0, 2).flat().map(w => `__${w}__`),
453
- protocols: protocol.split('|'),
454
- params: Object.keys(img).filter(k => k.endsWith('$1') || !k.includes('$1'))
455
- .map(k => k.replace(/\$1$/u, '')),
456
- };
494
+ this.#completionConfig = [
495
+ {
496
+ re,
497
+ ext,
498
+ tags,
499
+ allTags: [...tags, 'onlyinclude', 'includeonly', 'noinclude'],
500
+ functions: [
501
+ Object.keys(insensitive),
502
+ Array.isArray(sensitive) ? /* istanbul ignore next */ sensitive : Object.keys(sensitive),
503
+ other,
504
+ ].flat(2),
505
+ switches: doubleUnderscore.slice(0, 2).flat().map(w => `__${w}__`),
506
+ protocols: protocol.split('|'),
507
+ params: Object.keys(img)
508
+ .filter(k => k.endsWith('$1') || !k.includes('$1'))
509
+ .map(k => k.replace(/\$1$/u, '')),
510
+ },
511
+ this.config,
512
+ ];
457
513
  }
458
- return this.#completionConfig;
514
+ return this.#completionConfig[0];
459
515
  }
460
516
  /**
461
517
  * Provide auto-completion
@@ -465,13 +521,25 @@ class LanguageService {
465
521
  * @param position 位置
466
522
  */
467
523
  async provideCompletionItems(text, position) {
468
- 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) ?? '');
524
+ 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;
469
525
  if (mt?.[1] !== undefined) { // tag
470
526
  const closing = mt[1].startsWith('/');
471
527
  return getCompletion(allTags, 'Class', mt[1].slice(closing ? 1 : 0), position, closing && !curLine?.slice(character).trim().startsWith('>') ? '>' : '');
472
528
  }
473
529
  else if (mt?.[4]) { // behavior switch
474
- return getCompletion(switches, 'Constant', mt[4], position, '', name => this.data && this.#getBehaviorSwitch(name.slice(2, -2)));
530
+ return getCompletion(switches, 'Constant', mt[4], position, '', name => {
531
+ if (!this.data) {
532
+ return undefined;
533
+ }
534
+ name = name.slice(2, -2);
535
+ if (name in iAlias) {
536
+ name = iAlias[name];
537
+ }
538
+ else if (name in sAlias) {
539
+ name = sAlias[name];
540
+ }
541
+ return this.#getBehaviorSwitch(name.toLowerCase());
542
+ });
475
543
  }
476
544
  else if (mt?.[5] !== undefined) { // protocol
477
545
  return getCompletion(protocols, 'Reference', mt[5], position);
@@ -485,27 +553,46 @@ class LanguageService {
485
553
  return getCompletion(root.querySelectorAll('arg').filter(token => token.name && token !== cur)
486
554
  .map(({ name }) => name), 'Variable', match, position);
487
555
  }
488
- const colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
489
- return mt[2] === '[['
490
- ? getCompletion(// link
491
- root.querySelectorAll('link,file,category,redirect-target').filter(token => token !== cur).map(({ name }) => name), 'Folder', str, position)
492
- : [
493
- ...getCompletion(functions, 'Function', match, position, '', name => this.data && this.#getParserFunction(name.replace(/^#/u, '').toLowerCase())),
494
- ...match.startsWith('#')
495
- ? []
496
- : getCompletion(root.querySelectorAll('template').filter(token => token !== cur)
497
- .map(token => {
498
- const { name } = token;
499
- if (colon) {
500
- return name;
501
- }
502
- const { ns } = token.getAttribute('title');
503
- if (ns === 0) {
504
- return `:${name}`;
505
- }
506
- return ns === 10 ? name.slice(9) : name;
507
- }), 'Folder', str, position),
508
- ];
556
+ const [insensitive, sensitive] = this.config.parserFunction, isOld = Array.isArray(sensitive), next = curLine.charAt(character), colon = match.startsWith(':'), str = colon ? match.slice(1).trimStart() : match;
557
+ if (mt[2] === '[[') { // link
558
+ return getCompletion(root.querySelectorAll('link,file,category,redirect-target').filter(token => token !== cur).map(({ name }) => name), 'Folder', str, position);
559
+ }
560
+ // parser function or template
561
+ let words = functions;
562
+ if (next === ':') {
563
+ words = functions.filter(s => !s.endsWith(':'));
564
+ }
565
+ else if (next === ':') {
566
+ words = functions.filter(s => s.endsWith(':')).map(s => s.slice(0, -1));
567
+ }
568
+ return [
569
+ ...getCompletion(words, 'Function', match, position, '', name => {
570
+ if (!this.data) {
571
+ return undefined;
572
+ }
573
+ else if (name in insensitive) {
574
+ name = insensitive[name];
575
+ }
576
+ else if (!isOld && name in sensitive) {
577
+ name = sensitive[name];
578
+ }
579
+ return this.#getParserFunction(name.toLowerCase());
580
+ }),
581
+ ...match.startsWith('#')
582
+ ? []
583
+ : getCompletion(root.querySelectorAll('template').filter(token => token !== cur)
584
+ .map(token => {
585
+ const { name } = token;
586
+ if (colon) {
587
+ return name;
588
+ }
589
+ const { ns } = token.getAttribute('title');
590
+ if (ns === 0) {
591
+ return `:${name}`;
592
+ }
593
+ return ns === 10 ? name.slice(9) : name;
594
+ }), 'Folder', str, position),
595
+ ];
509
596
  }
510
597
  let type, parentNode;
511
598
  if (mt?.[7] === undefined) {
@@ -514,7 +601,7 @@ class LanguageService {
514
601
  }
515
602
  if (mt?.[6] !== undefined || type === 'image-parameter') { // image parameter
516
603
  const index = root.indexFromPos(line, character), match = mt?.[6]?.trimStart()
517
- ?? this.#text.slice(cur.getAbsoluteIndex(), index).trimStart(), equal = this.#text.charAt(index) === '=';
604
+ ?? this.#text.slice(cur.getAbsoluteIndex(), index).trimStart(), equal = this.#text[index] === '=';
518
605
  return [
519
606
  ...getCompletion(params, 'Property', match, position)
520
607
  .filter(({ label }) => !equal || !/[= ]$/u.test(label)),
@@ -550,7 +637,7 @@ class LanguageService {
550
637
  if (t === 'magic-word' && n !== 'invoke') {
551
638
  return undefined;
552
639
  }
553
- const index = root.indexFromPos(line, character), key = this.#text.slice(cur.getAbsoluteIndex(), index).trimStart(), [module, func] = t === 'magic-word' ? transclusion.getModule() : [];
640
+ const key = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)).trimStart(), [module, func] = t === 'magic-word' ? transclusion.getModule() : [];
554
641
  return key
555
642
  ? getCompletion(root.querySelectorAll('parameter').filter(token => {
556
643
  if (token === parentNode
@@ -568,7 +655,7 @@ class LanguageService {
568
655
  : undefined;
569
656
  /* NOT FOR BROWSER ONLY */
570
657
  }
571
- else if (document_1.cssLSP && type === 'attr-value' && parentNode.name === 'style' && cur.length === 1) {
658
+ else if ((0, exports.isAttr)(cur, true)) {
572
659
  const textDoc = new document_1.EmbeddedCSSDocument(root, cur);
573
660
  return document_1.cssLSP.doComplete(textDoc, position, textDoc.styleSheet).items.map((item) => ({
574
661
  ...item,
@@ -587,9 +674,9 @@ class LanguageService {
587
674
  if (lang !== undefined && lang !== 'lilypond') {
588
675
  return undefined;
589
676
  }
590
- const j = root.indexFromPos(line, character), i = cur.getAbsoluteIndex(), before = this.#text.slice(i, j), comment = before.lastIndexOf('%');
677
+ const before = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)), comment = before.lastIndexOf('%');
591
678
  if (comment !== -1
592
- && (before.charAt(comment + 1) === '{' || !before.slice(comment).includes('\n'))) {
679
+ && (before[comment + 1] === '{' || !before.slice(comment).includes('\n'))) {
593
680
  return undefined;
594
681
  }
595
682
  const word = /\\?\b(?:\w|\b(?:->?|\.)|\bly:)+$/u.exec(curLine.slice(0, character))?.[0];
@@ -604,6 +691,14 @@ class LanguageService {
604
691
  }
605
692
  /* NOT FOR BROWSER ONLY END */
606
693
  }
694
+ else if ((0, exports.isAttr)(cur) && isHtmlAttr(parentNode)) {
695
+ const data = lint_1.htmlData.provideValues(parentNode.tag, parentNode.name);
696
+ if (data.length === 0) {
697
+ return undefined;
698
+ }
699
+ const val = this.#text.slice(cur.getAbsoluteIndex(), root.indexFromPos(line, character)).trimStart();
700
+ return getCompletion(data.map(({ name }) => name), 'Value', val, position);
701
+ }
607
702
  return undefined;
608
703
  }
609
704
  /**
@@ -695,7 +790,7 @@ class LanguageService {
695
790
  lilypondDiagnostics = await Promise.all(tokens.map(async (token) => {
696
791
  const { innerText } = token, score = `showLastLength = R1${token.getAttr('raw') === undefined ? ` \\score {\n${innerText}\n}` : `\n${innerText}`}`;
697
792
  if (scores.has(score)) {
698
- return getLilyPondDiagnostics(token, scores.get(score));
793
+ return getLilyPondDiagnostics(root, token, scores.get(score));
699
794
  }
700
795
  const hash = (0, crypto_1.createHash)('sha256');
701
796
  hash.update(score);
@@ -717,7 +812,7 @@ class LanguageService {
717
812
  };
718
813
  });
719
814
  scores.set(score, lilypondErrors);
720
- return getLilyPondDiagnostics(token, lilypondErrors);
815
+ return getLilyPondDiagnostics(root, token, lilypondErrors);
721
816
  }
722
817
  }
723
818
  return [];
@@ -797,16 +892,23 @@ class LanguageService {
797
892
  * @param text source Wikitext / 源代码
798
893
  */
799
894
  async provideLinks(text) {
800
- const { articlePath, protocol } = index_1.default.getConfig(), absolute = articlePath?.includes('//'), protocolRegex = new RegExp(`^(?:${protocol}|//)`, 'iu');
895
+ this.config ??= index_1.default.getConfig();
896
+ const { articlePath, protocol } = this.config, absolute = articlePath?.includes('//'), protocolRegex = new RegExp(`^(?:${protocol}|//)`, 'iu');
801
897
  return (await this.#queue(text))
802
- .querySelectorAll(`magic-link,ext-link-url,free-ext-link,attr-value,image-parameter#link${absolute ? ',link-target,template-name,invoke-module' : ''}`)
898
+ .querySelectorAll(`magic-link,ext-link-url,free-ext-link,attr-value,image-parameter#link${absolute ? ',link-target,template-name,invoke-module,magic-word#filepath,magic-word#widget' : ''}`)
803
899
  .reverse()
804
900
  .map((token) => {
805
- const { type, parentNode, firstChild, lastChild, childNodes, length } = token, { name, tag } = parentNode;
901
+ let name;
902
+ if (token.is('magic-word')) {
903
+ ({ name } = token);
904
+ token = token.childNodes[1].lastChild; // eslint-disable-line no-param-reassign
905
+ }
906
+ const { type, parentNode, firstChild, lastChild, childNodes, length } = token, { tag } = parentNode;
907
+ name ??= parentNode.name;
806
908
  if (!(type !== 'attr-value'
807
909
  || name === 'src' && ['templatestyles', 'img'].includes(tag)
808
910
  || name === 'cite' && ['blockquote', 'del', 'ins', 'q'].includes(tag))
809
- || !isPlain(childNodes)) {
911
+ || !isPlain(token)) {
810
912
  return false;
811
913
  }
812
914
  let target = childNodes.filter((node) => node.type === 'text')
@@ -833,21 +935,25 @@ class LanguageService {
833
935
  else if (type === 'template-name') {
834
936
  target = parentNode.getAttribute('title').getUrl(articlePath);
835
937
  }
836
- else if (['link-target', 'invoke-module'].includes(type)
938
+ else if (['link-target', 'invoke-module', 'parameter-value'].includes(type)
837
939
  || type === 'attr-value' && name === 'src' && tag === 'templatestyles'
838
940
  || type === 'image-parameter' && !protocolRegex.test(target)) {
839
941
  if (!absolute || target.startsWith('/')) {
840
942
  return false;
841
943
  }
842
944
  let ns = 0;
843
- if (type === 'attr-value') {
844
- ns = 10;
945
+ switch (type) {
946
+ case 'attr-value':
947
+ ns = 10;
948
+ break;
949
+ case 'invoke-module':
950
+ ns = 828;
951
+ break;
952
+ case 'parameter-value':
953
+ ns = name === 'filepath' ? 6 : 274;
954
+ // no default
845
955
  }
846
- else if (type === 'invoke-module') {
847
- ns = 828;
848
- }
849
- const title = index_1.default
850
- .normalizeTitle(target, ns, false, undefined, true);
956
+ const title = index_1.default.normalizeTitle(target, ns, false, this.config, true);
851
957
  /* istanbul ignore if */
852
958
  if (!title.valid) {
853
959
  return false;
@@ -991,51 +1097,88 @@ class LanguageService {
991
1097
  if (!this.data) {
992
1098
  return undefined;
993
1099
  }
994
- const root = await this.#queue(text), { offsetNode, offset } = caretPositionFromWord(root, this.#text, position), token = offsetNode.type === 'text' ? offsetNode.parentNode : offsetNode, { type, parentNode, length, name } = token;
995
- let info, f, range;
996
- if (token.is('double-underscore') && offset > 0) {
997
- info = this.#getBehaviorSwitch(token.innerText.toLowerCase());
1100
+ const root = await this.#queue(text);
1101
+ let { offsetNode, offset } = caretPositionFromWord(root, this.#text, position);
1102
+ if (offsetNode.type === 'text') {
1103
+ offset += offsetNode.getRelativeIndex();
1104
+ offsetNode = offsetNode.parentNode;
1105
+ }
1106
+ const { type, parentNode, length, name } = offsetNode;
1107
+ let info, f, colon, range;
1108
+ if (offsetNode.is('double-underscore') && offset > 0) {
1109
+ info = this.#getBehaviorSwitch(offsetNode.name);
998
1110
  }
999
1111
  else if (type === 'magic-word-name') {
1000
1112
  info = this.#getParserFunction(parentNode.name);
1001
- f = token.toString(true).trim();
1113
+ f = offsetNode.toString(true).trim();
1114
+ colon = parentNode.getAttribute('colon');
1002
1115
  }
1003
- else if (token.is('magic-word') && !token.modifier && length === 1
1004
- && (offset > 0 || root.posFromIndex(token.getAbsoluteIndex()).left === position.character)) {
1116
+ else if (offsetNode.is('magic-word') && !offsetNode.modifier && length === 1
1117
+ && (offset > 0 || root.posFromIndex(offsetNode.getAbsoluteIndex()).left === position.character)) {
1005
1118
  info = this.#getParserFunction(name);
1006
- f = token.firstChild.toString(true).trim();
1119
+ f = offsetNode.firstChild.toString(true).trim();
1120
+ colon = offsetNode.getAttribute('colon');
1007
1121
  }
1008
- else if ((token.is('magic-word') || token.is('template'))
1009
- && token.modifier && offset >= 2 && token.getRelativeIndex(0) > offset) {
1010
- f = token.modifier.trim().slice(0, -1);
1122
+ else if ((offsetNode.is('magic-word') || offsetNode.is('template'))
1123
+ && offsetNode.modifier && offset >= 2 && offsetNode.getRelativeIndex(0) > offset) {
1124
+ f = offsetNode.modifier.trim().slice(0, -1);
1011
1125
  info = this.#getParserFunction(f.toLowerCase());
1126
+ colon = ':';
1012
1127
  if (info) {
1013
- const aIndex = token.getAbsoluteIndex();
1128
+ const aIndex = offsetNode.getAbsoluteIndex();
1014
1129
  range = {
1015
1130
  start: positionAt(root, aIndex + 2),
1016
- end: positionAt(root, aIndex + token.modifier.trimEnd().length + 1),
1131
+ end: positionAt(root, aIndex + offsetNode.modifier.trimEnd().length + 1),
1017
1132
  };
1018
1133
  }
1019
1134
  /* NOT FOR BROWSER ONLY */
1020
1135
  }
1021
- else if (document_1.cssLSP && type === 'attr-value' && length === 1 && parentNode.name === 'style') {
1022
- const textDoc = new document_1.EmbeddedCSSDocument(root, token);
1136
+ else if ((0, exports.isAttr)(offsetNode, true)) {
1137
+ const textDoc = new document_1.EmbeddedCSSDocument(root, offsetNode);
1023
1138
  return document_1.cssLSP.doHover(textDoc, position, textDoc.styleSheet) ?? undefined;
1024
1139
  }
1025
1140
  else if (document_1.jsonLSP && type === 'ext-inner' && document_1.jsonTags.includes(name)) {
1026
- const textDoc = new document_1.EmbeddedJSONDocument(root, token);
1141
+ const textDoc = new document_1.EmbeddedJSONDocument(root, offsetNode);
1027
1142
  return await document_1.jsonLSP.doHover(textDoc, position, textDoc.jsonDoc) ?? undefined;
1143
+ }
1144
+ else if (lint_1.htmlData.provideTags && lint_1.htmlData.provideAttributes) {
1145
+ if (type === 'html' && offset <= offsetNode.getRelativeIndex(0)
1146
+ || type === 'html-attr-dirty' && offset === 0 && parentNode.firstChild === offsetNode) {
1147
+ const token = type === 'html' ? offsetNode : parentNode.parentNode, data = lint_1.htmlData.provideTags().find(({ name: n }) => n === token.name);
1148
+ if (data?.description) {
1149
+ const start = positionAt(root, token.getAbsoluteIndex());
1150
+ return {
1151
+ contents: data.description,
1152
+ range: {
1153
+ start,
1154
+ end: {
1155
+ line: start.line,
1156
+ character: start.character + token.getRelativeIndex(0),
1157
+ },
1158
+ },
1159
+ };
1160
+ }
1161
+ }
1162
+ else if (type === 'attr-key' && isHtmlAttr(parentNode)) {
1163
+ const data = lint_1.htmlData.provideAttributes(parentNode.tag).find(({ name: n }) => n === parentNode.name);
1164
+ if (data?.description) {
1165
+ return {
1166
+ contents: data.description,
1167
+ range: createNodeRange(offsetNode),
1168
+ };
1169
+ }
1170
+ }
1028
1171
  /* NOT FOR BROWSER ONLY END */
1029
1172
  }
1030
1173
  return info && {
1031
1174
  contents: {
1032
1175
  kind: 'markdown',
1033
1176
  value: (info.signatures
1034
- ? `${info.signatures.map(params => `- **{{ ${f}${params.length === 0 ? '**' : ':** '}${params.map(({ label, const: c }) => c ? label : `*${label}*`).join(' **|** ')} **}}**`).join('\n')}\n\n`
1177
+ ? `${info.signatures.map(params => `- **{{ ${f}${params.length === 0 ? '**' : `${colon}** `}${params.map(({ label, const: c }) => c ? label : `*${label}*`).join(' **|** ')} **}}**`).join('\n')}\n\n`
1035
1178
  : '')
1036
1179
  + info.description,
1037
1180
  },
1038
- range: range ?? createNodeRange(token),
1181
+ range: range ?? createNodeRange(offsetNode),
1039
1182
  };
1040
1183
  }
1041
1184
  /**
@@ -1073,10 +1216,10 @@ class LanguageService {
1073
1216
  break;
1074
1217
  }
1075
1218
  }
1076
- const f = firstChild.toString(true).trim();
1219
+ const f = firstChild.toString(true).trim(), colon = lastChild.getAttribute('colon');
1077
1220
  return {
1078
1221
  signatures: candidates.map((params) => ({
1079
- label: `{{${f}${params.length === 0 ? '' : ':'}${params.map(({ label }) => label).join('|')}}}`,
1222
+ label: `{{${f}${params.length === 0 ? '' : colon}${params.map(({ label }) => label).join('|')}}}`,
1080
1223
  parameters: params.map(({ label, const: c }) => ({
1081
1224
  label,
1082
1225
  ...c ? { documentation: 'Predefined parameter' } : undefined,
@@ -1108,8 +1251,7 @@ class LanguageService {
1108
1251
  }
1109
1252
  /** @private */
1110
1253
  findStyleTokens() {
1111
- return this.#done.querySelectorAll(cssSelector)
1112
- .filter(({ lastChild: { length, firstChild } }) => length === 1 && firstChild.type === 'text');
1254
+ return this.#done.querySelectorAll(cssSelector).filter(({ lastChild }) => (0, exports.isAttr)(lastChild));
1113
1255
  }
1114
1256
  /* NOT FOR BROWSER ONLY */
1115
1257
  /**
@@ -1171,5 +1313,27 @@ class LanguageService {
1171
1313
  }
1172
1314
  return symbols;
1173
1315
  }
1316
+ /**
1317
+ * Set the target Wikipedia
1318
+ *
1319
+ * 设置目标维基百科
1320
+ * @param wiki Wikipedia URL / 维基百科网址
1321
+ * @throws `RangeError` 不是有效的维基百科网址
1322
+ */
1323
+ async setTargetWikipedia(wiki) {
1324
+ const mt = /^https?:\/\/([^./]+)\.wikipedia\.org/iu.exec(wiki);
1325
+ if (!mt) {
1326
+ throw new RangeError('Invalid Wikipedia URL!');
1327
+ }
1328
+ const site = `${mt[1].toLowerCase()}wiki`;
1329
+ try {
1330
+ const config = require(path_1.default.join('..', '..', 'config', site));
1331
+ this.config = index_1.default.getConfig(config);
1332
+ }
1333
+ catch {
1334
+ this.config = index_1.default.getConfig(await (0, config_1.default)(site, `${mt[0]}/w`));
1335
+ }
1336
+ Object.assign(this.config, { articlePath: `${mt[0]}/wiki/` });
1337
+ }
1174
1338
  }
1175
1339
  exports.LanguageService = LanguageService;