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.
Files changed (154) hide show
  1. package/README.md +1 -1
  2. package/bundle/bundle.es7.js +28 -28
  3. package/bundle/bundle.lsp.js +30 -30
  4. package/bundle/bundle.min.js +29 -29
  5. package/coverage/badge.svg +1 -1
  6. package/data/signatures.json +43 -0
  7. package/dist/addon/table.js +7 -4
  8. package/dist/addon/token.js +119 -113
  9. package/dist/addon/transclude.js +7 -5
  10. package/dist/base.d.mts +99 -30
  11. package/dist/base.d.ts +99 -30
  12. package/dist/index.d.ts +10 -4
  13. package/dist/index.js +59 -14
  14. package/dist/lib/attributes.js +2 -2
  15. package/dist/lib/element.d.ts +128 -29
  16. package/dist/lib/element.js +178 -32
  17. package/dist/lib/lsp.d.ts +62 -27
  18. package/dist/lib/lsp.js +395 -304
  19. package/dist/lib/node.d.ts +123 -54
  20. package/dist/lib/node.js +216 -87
  21. package/dist/lib/range.d.ts +83 -31
  22. package/dist/lib/range.js +83 -31
  23. package/dist/lib/ranges.d.ts +1 -1
  24. package/dist/lib/ranges.js +3 -2
  25. package/dist/lib/text.d.ts +40 -14
  26. package/dist/lib/text.js +47 -19
  27. package/dist/lib/title.d.ts +54 -17
  28. package/dist/lib/title.js +101 -47
  29. package/dist/mixin/attributesParent.d.ts +37 -21
  30. package/dist/mixin/attributesParent.js +18 -20
  31. package/dist/mixin/flagsParent.d.ts +38 -12
  32. package/dist/mixin/hidden.d.ts +0 -2
  33. package/dist/mixin/hidden.js +0 -2
  34. package/dist/mixin/magicLinkParent.d.ts +11 -5
  35. package/dist/mixin/singleLine.d.ts +0 -2
  36. package/dist/mixin/singleLine.js +3 -3
  37. package/dist/mixin/sol.d.ts +0 -2
  38. package/dist/mixin/sol.js +2 -3
  39. package/dist/mixin/syntax.d.ts +0 -2
  40. package/dist/mixin/syntax.js +0 -2
  41. package/dist/parser/braces.js +4 -2
  42. package/dist/parser/links.js +13 -7
  43. package/dist/parser/list.js +3 -1
  44. package/dist/parser/quotes.js +22 -2
  45. package/dist/parser/redirect.js +4 -1
  46. package/dist/parser/selector.js +1 -1
  47. package/dist/src/arg.d.ts +19 -9
  48. package/dist/src/arg.js +34 -15
  49. package/dist/src/atom.d.ts +5 -1
  50. package/dist/src/atom.js +5 -1
  51. package/dist/src/attribute.d.ts +26 -8
  52. package/dist/src/attribute.js +30 -11
  53. package/dist/src/attributes.d.ts +50 -22
  54. package/dist/src/attributes.js +54 -26
  55. package/dist/src/converter.d.ts +4 -2
  56. package/dist/src/converter.js +12 -9
  57. package/dist/src/converterFlags.d.ts +45 -15
  58. package/dist/src/converterFlags.js +45 -15
  59. package/dist/src/converterRule.d.ts +30 -11
  60. package/dist/src/converterRule.js +46 -16
  61. package/dist/src/extLink.d.ts +6 -2
  62. package/dist/src/extLink.js +14 -11
  63. package/dist/src/gallery.d.ts +13 -9
  64. package/dist/src/gallery.js +35 -21
  65. package/dist/src/heading.d.ts +13 -7
  66. package/dist/src/heading.js +28 -18
  67. package/dist/src/hidden.d.ts +5 -1
  68. package/dist/src/hidden.js +7 -3
  69. package/dist/src/html.d.ts +12 -10
  70. package/dist/src/html.js +17 -12
  71. package/dist/src/imageParameter.d.ts +27 -12
  72. package/dist/src/imageParameter.js +27 -12
  73. package/dist/src/imagemap.d.ts +4 -4
  74. package/dist/src/imagemap.js +9 -5
  75. package/dist/src/imagemapLink.d.ts +3 -1
  76. package/dist/src/imagemapLink.js +3 -1
  77. package/dist/src/index.d.ts +65 -64
  78. package/dist/src/index.js +111 -132
  79. package/dist/src/link/base.d.ts +14 -6
  80. package/dist/src/link/base.js +45 -21
  81. package/dist/src/link/category.d.ts +9 -4
  82. package/dist/src/link/category.js +20 -4
  83. package/dist/src/link/file.d.ts +71 -23
  84. package/dist/src/link/file.js +100 -36
  85. package/dist/src/link/galleryImage.d.ts +8 -4
  86. package/dist/src/link/galleryImage.js +15 -8
  87. package/dist/src/link/index.d.ts +14 -6
  88. package/dist/src/link/index.js +15 -5
  89. package/dist/src/link/redirectTarget.d.ts +4 -3
  90. package/dist/src/link/redirectTarget.js +5 -3
  91. package/dist/src/magicLink.d.ts +28 -12
  92. package/dist/src/magicLink.js +56 -34
  93. package/dist/src/nested.d.ts +5 -3
  94. package/dist/src/nested.js +9 -6
  95. package/dist/src/nowiki/base.d.ts +4 -2
  96. package/dist/src/nowiki/base.js +6 -5
  97. package/dist/src/nowiki/comment.d.ts +6 -2
  98. package/dist/src/nowiki/comment.js +8 -3
  99. package/dist/src/nowiki/dd.d.ts +0 -3
  100. package/dist/src/nowiki/dd.js +0 -8
  101. package/dist/src/nowiki/doubleUnderscore.d.ts +5 -1
  102. package/dist/src/nowiki/doubleUnderscore.js +5 -1
  103. package/dist/src/nowiki/index.d.ts +5 -1
  104. package/dist/src/nowiki/index.js +5 -1
  105. package/dist/src/nowiki/list.d.ts +5 -1
  106. package/dist/src/nowiki/list.js +5 -1
  107. package/dist/src/nowiki/listBase.d.ts +15 -5
  108. package/dist/src/nowiki/listBase.js +41 -10
  109. package/dist/src/nowiki/noinclude.d.ts +5 -1
  110. package/dist/src/nowiki/noinclude.js +5 -1
  111. package/dist/src/nowiki/quote.d.ts +15 -8
  112. package/dist/src/nowiki/quote.js +32 -12
  113. package/dist/src/onlyinclude.d.ts +4 -2
  114. package/dist/src/onlyinclude.js +4 -2
  115. package/dist/src/paramTag/index.d.ts +1 -1
  116. package/dist/src/paramTag/index.js +1 -1
  117. package/dist/src/parameter.d.ts +24 -12
  118. package/dist/src/parameter.js +45 -27
  119. package/dist/src/pre.d.ts +1 -1
  120. package/dist/src/pre.js +1 -1
  121. package/dist/src/redirect.d.ts +2 -0
  122. package/dist/src/redirect.js +2 -0
  123. package/dist/src/syntax.d.ts +5 -1
  124. package/dist/src/syntax.js +5 -1
  125. package/dist/src/table/base.d.ts +5 -1
  126. package/dist/src/table/base.js +11 -4
  127. package/dist/src/table/index.d.ts +134 -66
  128. package/dist/src/table/index.js +149 -81
  129. package/dist/src/table/td.d.ts +18 -10
  130. package/dist/src/table/td.js +23 -15
  131. package/dist/src/table/tr.d.ts +13 -3
  132. package/dist/src/table/tr.js +14 -6
  133. package/dist/src/table/trBase.d.ts +28 -12
  134. package/dist/src/table/trBase.js +28 -18
  135. package/dist/src/tagPair/ext.d.ts +3 -1
  136. package/dist/src/tagPair/ext.js +11 -10
  137. package/dist/src/tagPair/include.d.ts +8 -2
  138. package/dist/src/tagPair/include.js +8 -2
  139. package/dist/src/tagPair/index.d.ts +6 -2
  140. package/dist/src/tagPair/index.js +6 -2
  141. package/dist/src/transclude.d.ts +107 -33
  142. package/dist/src/transclude.js +139 -45
  143. package/dist/util/debug.js +2 -7
  144. package/dist/util/html.js +1 -14
  145. package/dist/util/string.js +7 -1
  146. package/extensions/dist/base.js +25 -8
  147. package/extensions/dist/codejar.js +2 -1
  148. package/extensions/dist/lsp.js +5 -10
  149. package/extensions/es7/base.js +25 -8
  150. package/extensions/ui.css +1 -162
  151. package/i18n/zh-hans.json +1 -1
  152. package/i18n/zh-hant.json +1 -1
  153. package/package.json +3 -3
  154. 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 }) => ['text', 'comment', 'noinclude', 'include'].includes(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
- * Create the URL of a page.
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 elementFromWord = (root, pos) => {
96
- const { line, character } = pos, index = root.indexFromPos(line, character), offset = Number(/[\w!#]/u.test(root.toString().charAt(index)));
97
- return root.elementFromIndex(index + offset);
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 = text.replace(/\r$/gmu, '');
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
- return new Promise(resolve => {
199
- (typeof setImmediate === 'function' ? setImmediate : /* istanbul ignore next */ setTimeout)(() => {
200
- const config = index_1.default.getConfig();
201
- this.#config = index_1.default.config;
202
- const text = this.#text, root = index_1.default.parse(text, true, undefined, config);
203
- if (this.#text === text && this.#config === index_1.default.config) {
204
- this.#done = root;
205
- this.#running = undefined;
206
- resolve(root);
207
- return;
208
- }
209
- /* istanbul ignore next */
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
- /** @implements */
263
- provideColorPresentations(// eslint-disable-line @typescript-eslint/class-methods-use-this
264
- { color: { red, green, blue, alpha }, range }) {
265
- const newText = `#${(0, common_1.numToHex)(red)}${(0, common_1.numToHex)(green)}${(0, common_1.numToHex)(blue)}${alpha < 1 ? (0, common_1.numToHex)(alpha) : ''}`;
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 `<\/?(\w*)` // tag
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, mt = re.exec(text.split(/\r?\n/u, line + 1)[line]?.slice(0, character) ?? '');
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
- return getCompletion(allTags, 'Class', mt[1], position);
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').map(({ name }) => name).filter(Boolean), 'Variable', match, position);
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(({ name }) => colon ? name : name.replace(/^Template:/u, '')), 'Folder', str, position),
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
- const token = root.elementFromPoint(character, line), { type, parentNode } = token;
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(token.getAbsoluteIndex(), root.indexFromPos(position.line, position.character)).trimStart();
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
- ...getCompletion(root.querySelectorAll('image-parameter#width').map(width => width.text()), 'Unit', match, position),
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] ?? token.toString();
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 ((type === 'parameter-key' || type === 'parameter-value' && parentNode.anon)
376
- && parentNode.parentNode.type === 'template') { // parameter key
377
- const key = token.toString().trimStart();
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(({ anon, parentNode: parent }) => !anon && parent.type === 'template'
380
- && parent.name === parentNode.parentNode.name).map(({ name }) => name), 'Variable', key, position)
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
- * @param wikitext 源代码
388
- * @param warning 是否提供警告
444
+ * Provide grammar check
445
+ *
446
+ * 提供语法检查
447
+ * @param text source Wikitext / 源代码
448
+ * @param warning whether to include warnings / 是否包含警告
389
449
  */
390
- async provideDiagnostics(wikitext, warning = true) {
450
+ async provideDiagnostics(text, warning = true) {
391
451
  this.#checkSignature();
392
- const root = await this.#queue(wikitext), errors = root.lint();
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
- async #provideFoldingRangesOrDocumentSymbols(text, fold = true) {
470
+ /**
471
+ * Provide folding ranges
472
+ *
473
+ * 提供折叠范围
474
+ * @param text source Wikitext / 源代码
475
+ */
476
+ async provideFoldingRanges(text) {
426
477
  this.#checkSignature();
427
- const ranges = [], symbols = [],
428
- /* NOT FOR BROWSER ONLY */
429
- names = new Set(), sections = new Array(6),
430
- /* NOT FOR BROWSER ONLY END */
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
- if (fold) {
439
- for (let i = level - 1; i < 6; i++) {
440
- const startLine = levels[i];
441
- if (startLine !== undefined && startLine < top - 1) {
442
- ranges.push({
443
- startLine,
444
- endLine: top - 1,
445
- kind: 'region',
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
- else {
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 (fold && height > 2) {
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
- if (fold) {
491
- for (const startLine of levels) {
492
- if (startLine !== undefined && startLine < length - 1) {
493
- ranges.push({
494
- startLine,
495
- endLine: length - 1,
496
- kind: 'region',
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 fold ? ranges : symbols;
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(`^(?:${index_1.default.getConfig().protocol}|//)`, 'iu'), selector = 'link-target,template-name,invoke-module,magic-link,ext-link-url,free-ext-link,attr-value,'
525
- + 'image-parameter#link';
526
- return (await this.#queue(text)).querySelectorAll(selector).flatMap((token) => {
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 (type === 'magic-link') {
539
- if (target.startsWith('ISBN')) {
540
- target = getUrl(`Special:Booksources/${target.slice(4).replace(/[\p{Zs}\t-]/gu, '')}`);
541
- }
542
- else {
543
- target = target.startsWith('RFC')
544
- ? `https://tools.ietf.org/html/rfc${target.slice(3).trim()}`
545
- : `https://pubmed.ncbi.nlm.nih.gov/${target.slice(4).trim()}`;
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', 'template-name', 'invoke-module'].includes(type)
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 === 'template-name' || type === 'attr-value') {
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
- target = getUrl(target, ns);
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 rect = firstChild.getBoundingClientRect(), { top, left, height, width } = lastChild.getBoundingClientRect();
569
- return [
570
- {
571
- range: {
572
- start: { line: rect.top, character: rect.left },
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 [{ range: createNodeRange(token), target }];
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
- async #provideReferencesOrDefinition(text, position, usage) {
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 renameTypes = [
589
- 'arg-name',
590
- 'template-name',
591
- 'magic-word-name',
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
- else if (usage === 2) {
612
- return createNodeRange(node);
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
- * @param text 源代码
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
- return this.#provideReferencesOrDefinition(text, position, 1);
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
- return this.#provideReferencesOrDefinition(text, position, 2);
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 = elementFromWord(root, position), { type } = node, refName = getRefName(node), refGroup = getRefGroup(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
- ? getRefName(token) === refName || getRefGroup(token) === refGroup
678
+ ? getRefGroup(token) === refGroup
679
+ || getRefName(token) === refName
680
+ && getRefTagAttr(token.parentNode.parentNode, 'group') === refNameGroup
668
681
  : getName(token) === name;
669
682
  });
670
- if (refs.length === 0) {
671
- return undefined;
672
- }
673
- return {
674
- changes: {
675
- '': refs.map((ref) => ({
676
- range: createNodeRange(ref),
677
- newText: newName,
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.find(({ aliases }) => aliases.some(alias => alias.replace(/^#/u, '') === name));
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 token = elementFromWord(await this.#queue(text), position);
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 (token.type === 'magic-word-name') {
706
- info = this.#getParserFunction(token.parentNode.name);
707
- f = token.text().trim();
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)}}}`), { type, name, childNodes, firstChild } = lastChild;
736
- if (type !== 'magic-word') {
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 f = firstChild.text().trim(), n = childNodes.length - 1, start = lastChild.getAbsoluteIndex();
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]?.text().trim();
751
- return !p || label.startsWith(p) || label.startsWith(p.toLowerCase());
752
- })).map((params) => ({
753
- label: `{{${f}${params.length === 0 ? '' : ':'}${params.map(({ label }) => label).join('|')}}}`,
754
- parameters: params.map(({ label, const: c }) => ({
755
- label,
756
- ...c ? { documentation: 'Predefined parameter' } : undefined,
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
- ...params.length < n ? { activeParameter: Math.min(activeParameter, params.length - 1) } : undefined,
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
- /** @implements */
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
- return this.#provideFoldingRangesOrDocumentSymbols(text, false);
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;