@bhsd/codemirror-mediawiki 3.10.0 → 3.10.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.
Files changed (58) hide show
  1. package/README.md +78 -78
  2. package/dist/bidi.d.ts +5 -0
  3. package/dist/bidi.js +5 -0
  4. package/dist/codemirror.d.ts +7 -0
  5. package/dist/codemirror.js +10 -3
  6. package/dist/color.d.ts +7 -4
  7. package/dist/color.js +4 -0
  8. package/dist/css.d.ts +5 -0
  9. package/dist/css.js +5 -0
  10. package/dist/escape.d.ts +21 -1
  11. package/dist/escape.js +42 -24
  12. package/dist/fold.d.ts +55 -3
  13. package/dist/fold.js +54 -30
  14. package/dist/hover.d.ts +14 -1
  15. package/dist/hover.js +49 -31
  16. package/dist/html.js +17 -12
  17. package/dist/indent.d.ts +7 -0
  18. package/dist/indent.js +7 -1
  19. package/dist/inlay.js +1 -1
  20. package/dist/javascript.d.ts +10 -1
  21. package/dist/javascript.js +6 -1
  22. package/dist/keybindings.d.ts +1 -0
  23. package/dist/keybindings.js +1 -0
  24. package/dist/keymap.d.ts +11 -0
  25. package/dist/keymap.js +3 -4
  26. package/dist/linter.d.ts +16 -0
  27. package/dist/linter.js +8 -1
  28. package/dist/lintsource.d.ts +36 -4
  29. package/dist/lintsource.js +37 -4
  30. package/dist/lua.js +31 -20
  31. package/dist/main.min.js +29 -29
  32. package/dist/matchBrackets.d.ts +16 -0
  33. package/dist/matchBrackets.js +16 -0
  34. package/dist/matchTag.d.ts +5 -2
  35. package/dist/matchTag.js +7 -4
  36. package/dist/mediawiki.d.ts +11 -0
  37. package/dist/mediawiki.js +8 -4
  38. package/dist/mw.min.js +31 -31
  39. package/dist/mwConfig.js +1 -1
  40. package/dist/openLinks.d.ts +8 -0
  41. package/dist/openLinks.js +8 -0
  42. package/dist/ref.d.ts +15 -1
  43. package/dist/ref.js +81 -65
  44. package/dist/signature.d.ts +6 -0
  45. package/dist/signature.js +22 -19
  46. package/dist/static.d.ts +4 -0
  47. package/dist/static.js +4 -0
  48. package/dist/theme.d.ts +1 -0
  49. package/dist/theme.js +3 -1
  50. package/dist/token.d.ts +9 -1
  51. package/dist/token.js +12 -6
  52. package/dist/util.d.ts +8 -0
  53. package/dist/util.js +8 -0
  54. package/dist/wiki.min.js +30 -30
  55. package/i18n/en.json +1 -1
  56. package/i18n/zh-hans.json +1 -1
  57. package/i18n/zh-hant.json +1 -1
  58. package/package.json +22 -22
package/dist/color.d.ts CHANGED
@@ -1,6 +1,9 @@
1
- import type { Text, Extension } from '@codemirror/state';
2
- import type { Tree } from '@lezer/common';
3
- import type { WidgetOptions } from '@bhsd/codemirror-css-color-picker';
4
- export declare const discoverColors: (_: Tree, from: number, to: number, type: string, doc: Text) => WidgetOptions[] | null;
1
+ import { makeColorPicker } from '@bhsd/codemirror-css-color-picker';
2
+ import type { Extension } from '@codemirror/state';
3
+ /**
4
+ * @implements
5
+ * @test
6
+ */
7
+ export declare const discoverColors: Parameters<typeof makeColorPicker>[0]['discoverColors'];
5
8
  declare const _default: Extension[];
6
9
  export default _default;
package/dist/color.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import { splitColors, numToHex } from '@bhsd/common';
2
2
  import { parseCallExpression, parseColorLiteral, ColorType, colorPickerTheme, makeColorPicker, } from '@bhsd/codemirror-css-color-picker';
3
+ /**
4
+ * @implements
5
+ * @test
6
+ */
3
7
  export const discoverColors = (_, from, to, type, doc) => {
4
8
  if (!/mw-(?:(?:ext|html)tag-attribute-value|table-definition)/u.test(type)
5
9
  && (!/mw-(?:template|parserfunction)(?:$|_)/u.test(type)
package/dist/css.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import { LanguageSupport } from '@codemirror/language';
2
2
  import type { Extension } from '@codemirror/state';
3
3
  import type { Dialect } from './codemirror';
4
+ /**
5
+ * CSS completion source with dialect-specific adjustments.
6
+ * @param dialect 是否是sanitized-css
7
+ * @test
8
+ */
4
9
  export declare const cssCompletion: (dialect?: Dialect) => Extension;
5
10
  declare const _default: (dialect: Dialect) => LanguageSupport;
6
11
  export default _default;
package/dist/css.js CHANGED
@@ -2,6 +2,11 @@ import { cssLanguage, cssCompletionSource } from '@codemirror/lang-css';
2
2
  import { LanguageSupport, syntaxTree } from '@codemirror/language';
3
3
  import { sliceDoc } from './util.js';
4
4
  const cssWideKeywords = /* @__PURE__ */ (() => ['revert', 'revert-layer'].map((label) => ({ label, type: 'keyword' })))();
5
+ /**
6
+ * CSS completion source with dialect-specific adjustments.
7
+ * @param dialect 是否是sanitized-css
8
+ * @test
9
+ */
5
10
  export const cssCompletion = (dialect) => {
6
11
  const source = context => {
7
12
  const { state, pos } = context, node = syntaxTree(state).resolveInner(pos, -1), result = cssCompletionSource(context);
package/dist/escape.d.ts CHANGED
@@ -1,5 +1,25 @@
1
+ import { getLSP } from '@bhsd/browser';
2
+ import type { EditorView } from '@codemirror/view';
1
3
  import type { Extension } from '@codemirror/state';
2
4
  import type { CodeMirror6 } from './codemirror';
3
- export declare const escapeHTML: (str: string) => string, escapeURI: (str: string) => string;
5
+ /**
6
+ * 转义HTML
7
+ * @param str 输入字符串
8
+ * @test
9
+ */
10
+ export declare const escapeHTML: (str: string) => string,
11
+ /**
12
+ * 转义URI
13
+ * @param str 输入字符串
14
+ * @test
15
+ */
16
+ escapeURI: (str: string) => string,
17
+ /**
18
+ * 使用魔术字转义选中文本
19
+ * @param view
20
+ * @param lsp LSP实例
21
+ * @test
22
+ */
23
+ escapeWiki: (view: EditorView, lsp: Exclude<ReturnType<typeof getLSP>, undefined>) => Promise<void>;
4
24
  declare const _default: (articlePath?: string) => (cm: CodeMirror6) => Extension;
5
25
  export default _default;
package/dist/escape.js CHANGED
@@ -19,13 +19,24 @@ const convert = (func, cmd) => (view) => {
19
19
  }
20
20
  return cmd(view);
21
21
  };
22
+ /**
23
+ * 转义HTML
24
+ * @param str 输入字符串
25
+ * @test
26
+ */
22
27
  export const escapeHTML = (str) => [...str].map(c => {
23
28
  if (c in entity) {
24
29
  return `&${entity[c]};`;
25
30
  }
26
31
  const code = c.codePointAt(0);
27
32
  return code < 256 ? `&#${code};` : `&#x${code.toString(16)};`;
28
- }).join(''), escapeURI = (str) => {
33
+ }).join(''),
34
+ /**
35
+ * 转义URI
36
+ * @param str 输入字符串
37
+ * @test
38
+ */
39
+ escapeURI = (str) => {
29
40
  if (str.includes('%')) {
30
41
  try {
31
42
  return decodeURIComponent(str);
@@ -33,28 +44,35 @@ export const escapeHTML = (str) => [...str].map(c => {
33
44
  catch { }
34
45
  }
35
46
  return encodeURIComponent(str);
47
+ },
48
+ /**
49
+ * 使用魔术字转义选中文本
50
+ * @param view
51
+ * @param lsp LSP实例
52
+ * @test
53
+ */
54
+ escapeWiki = async (view, lsp) => {
55
+ const { state } = view, { ranges } = state.selection, replacements = new WeakMap();
56
+ for (const range of ranges) {
57
+ // eslint-disable-next-line no-await-in-loop
58
+ const [action] = await lsp.provideRefactoringAction(sliceDoc(state, range));
59
+ replacements.set(range, action?.edit.changes[''][0].newText);
60
+ }
61
+ view.dispatch(state.changeByRange(range => {
62
+ const insert = replacements.get(range);
63
+ if (insert === undefined) {
64
+ return { range };
65
+ }
66
+ return {
67
+ range: EditorSelection.range(range.from, range.from + insert.length),
68
+ changes: { from: range.from, to: range.to, insert },
69
+ };
70
+ }));
36
71
  };
37
- const escapeWiki = (view, getConfig) => {
38
- const { state } = view, { ranges } = state.selection, lsp = getLSP(view, false, getConfig, base.CDN);
39
- if (lsp && 'provideRefactoringAction' in lsp && ranges.some(({ empty }) => !empty)) {
40
- (async () => {
41
- const replacements = new WeakMap();
42
- for (const range of ranges) {
43
- // eslint-disable-next-line no-await-in-loop
44
- const [action] = await lsp.provideRefactoringAction(sliceDoc(state, range));
45
- replacements.set(range, action?.edit.changes[''][0].newText);
46
- }
47
- view.dispatch(state.changeByRange(range => {
48
- const insert = replacements.get(range);
49
- if (insert === undefined) {
50
- return { range };
51
- }
52
- return {
53
- range: EditorSelection.range(range.from, range.from + insert.length),
54
- changes: { from: range.from, to: range.to, insert },
55
- };
56
- }));
57
- })();
72
+ const escapeWikiCommand = (view, getConfig) => {
73
+ const lsp = getLSP(view, false, getConfig, base.CDN);
74
+ if (lsp && 'provideRefactoringAction' in lsp && view.state.selection.ranges.some(({ empty }) => !empty)) {
75
+ void escapeWiki(view, lsp);
58
76
  return true;
59
77
  }
60
78
  return false;
@@ -85,7 +103,7 @@ menuRegistry.push({
85
103
  if (lsp && 'provideRefactoringAction' in lsp) {
86
104
  const btnWiki = elt('div', 'Escape with magic words');
87
105
  btnWiki.addEventListener('click', e => {
88
- escapeWiki(view, cm.getWikiConfig);
106
+ escapeWikiCommand(view, cm.getWikiConfig);
89
107
  handlerBase(view, e);
90
108
  });
91
109
  items.unshift(btnWiki);
@@ -100,7 +118,7 @@ export default (articlePath) => (cm) => keymap.of([
100
118
  {
101
119
  key: 'Mod-\\',
102
120
  run(view) {
103
- return escapeWiki(view, toConfigGetter(cm.getWikiConfig, articlePath));
121
+ return escapeWikiCommand(view, toConfigGetter(cm.getWikiConfig, articlePath));
104
122
  },
105
123
  },
106
124
  ]);
package/dist/fold.d.ts CHANGED
@@ -1,22 +1,74 @@
1
- import { EditorView } from '@codemirror/view';
2
- import type { Command } from '@codemirror/view';
3
- import type { EditorState, Extension } from '@codemirror/state';
1
+ import { GutterMarker, EditorView } from '@codemirror/view';
2
+ import { RangeSet } from '@codemirror/state';
3
+ import type { BlockInfo, Command } from '@codemirror/view';
4
+ import type { EditorState, StateEffect, Extension } from '@codemirror/state';
4
5
  import type { SyntaxNode, Tree } from '@lezer/common';
5
6
  export interface DocRange {
6
7
  from: number;
7
8
  to: number;
8
9
  }
10
+ declare type AnchorUpdate = (pos: number, range: DocRange) => number;
11
+ export declare const updateSelection: AnchorUpdate, updateAll: AnchorUpdate;
9
12
  /**
10
13
  * 寻找可折叠的范围
11
14
  * @param state
12
15
  * @param posOrNode 字符位置或语法树节点
13
16
  * @param tree 语法树
14
17
  * @param refOnly 是否仅检查`<ref>`标签
18
+ * @test
15
19
  */
16
20
  export declare const foldable: (state: EditorState, posOrNode: number | SyntaxNode, tree?: Tree | null, refOnly?: boolean) => DocRange | false;
21
+ /**
22
+ * 折叠所有模板
23
+ * @param state
24
+ * @param tree 语法树
25
+ * @param effects 折叠
26
+ * @param node 语法树节点
27
+ * @param end 终止位置
28
+ * @param anchor 光标位置
29
+ * @param update 更新光标位置
30
+ * @param refOnly 是否仅检查`<ref>`标签
31
+ * @test
32
+ */
33
+ export declare const traverse: (state: EditorState, tree: Tree, effects: StateEffect<DocRange>[], node: SyntaxNode | null, end: number, anchor: number, update: AnchorUpdate, refOnly?: boolean) => number;
34
+ declare class FoldMarker extends GutterMarker {
35
+ readonly open: boolean;
36
+ constructor(open: boolean);
37
+ eq(other: this): boolean;
38
+ toDOM({ state }: EditorView): HTMLElement;
39
+ }
40
+ /**
41
+ * 寻找可折叠的行范围
42
+ * @ignore
43
+ * @test
44
+ */
17
45
  export declare const foldableLine: ({ state, viewportLineBlocks }: EditorView, { from: f, to: t }: DocRange) => DocRange | false;
46
+ /**
47
+ * 生成行号旁的折叠标记
48
+ * @param view
49
+ * @test
50
+ */
51
+ export declare const buildMarkers: (view: EditorView) => RangeSet<FoldMarker>;
52
+ /**
53
+ * 生成折叠命令
54
+ * @param refOnly 是否仅检查`<ref>`标签
55
+ * @test
56
+ */
57
+ export declare const foldCommand: (refOnly?: boolean) => Command;
18
58
  export declare const foldRef: Command;
19
59
  export declare const unfoldRef: Command;
60
+ /**
61
+ * 获取所有光标所在的行
62
+ * @param view
63
+ * @test
64
+ */
65
+ export declare const selectedLines: (view: EditorView) => BlockInfo[];
66
+ /**
67
+ * Fold the template at the selection/cursor
68
+ * @param view
69
+ * @test
70
+ */
71
+ export declare const foldAt: Command;
20
72
  declare const _default: (e?: Extension | undefined) => Extension;
21
73
  export default _default;
22
74
  export declare const mediawikiFold: Extension;
package/dist/fold.js CHANGED
@@ -8,7 +8,7 @@ import { bgDark } from './constants.js';
8
8
  import { matchTag, getTag } from './matchTag.js';
9
9
  import { braceStackUpdate, sliceDoc } from './util.js';
10
10
  const getExtRegex = /* @__PURE__ */ getRegex(tag => new RegExp(`mw-tag-${tag}(?![a-z])`, 'u'));
11
- const updateSelection = (pos, { to }) => Math.max(pos, to), updateAll = (pos, { from, to }) => from <= pos && to > pos ? to : pos;
11
+ export const updateSelection = (pos, { to }) => Math.max(pos, to), updateAll = (pos, { from, to }) => from <= pos && to > pos ? to : pos;
12
12
  /**
13
13
  * Check if a SyntaxNode is among the specified components
14
14
  * @param keys The keys of the tokens to check
@@ -45,6 +45,7 @@ const refNames = new Set(['ref', 'references']);
45
45
  * @param posOrNode 字符位置或语法树节点
46
46
  * @param tree 语法树
47
47
  * @param refOnly 是否仅检查`<ref>`标签
48
+ * @test
48
49
  */
49
50
  export const foldable = (state, posOrNode, tree, refOnly = false) => {
50
51
  if (typeof posOrNode === 'number') {
@@ -140,7 +141,7 @@ const foldSelector = '.cm-tooltip-fold';
140
141
  * @param state
141
142
  */
142
143
  const create = (state) => {
143
- const { selection: { main: { head } } } = state, range = foldable(state, head);
144
+ const { head } = state.selection.main, range = foldable(state, head);
144
145
  if (range) {
145
146
  const { from, to } = range;
146
147
  let folded = false;
@@ -200,8 +201,9 @@ const getAnchor = (state) => Math.max(...state.selection.ranges.map(({ to }) =>
200
201
  * @param anchor 光标位置
201
202
  * @param update 更新光标位置
202
203
  * @param refOnly 是否仅检查`<ref>`标签
204
+ * @test
203
205
  */
204
- const traverse = (state, tree, effects, node, end, anchor, update, refOnly) => {
206
+ export const traverse = (state, tree, effects, node, end, anchor, update, refOnly) => {
205
207
  while (node && (node.from < end
206
208
  || node.from === end
207
209
  && !(isTemplateBracket(node) && sliceDoc(state, node).startsWith('}}')))) {
@@ -239,6 +241,11 @@ const findFold = ({ state }, line) => {
239
241
  });
240
242
  return found;
241
243
  };
244
+ /**
245
+ * 寻找可折叠的行范围
246
+ * @ignore
247
+ * @test
248
+ */
242
249
  export const foldableLine = ({ state, viewportLineBlocks }, { from: f, to: t }) => {
243
250
  const tree = syntaxTree(state), { doc } = state, { length } = viewportLineBlocks;
244
251
  /**
@@ -307,7 +314,12 @@ export const foldableLine = ({ state, viewportLineBlocks }, { from: f, to: t })
307
314
  }
308
315
  return false;
309
316
  };
310
- const buildMarkers = (view) => {
317
+ /**
318
+ * 生成行号旁的折叠标记
319
+ * @param view
320
+ * @test
321
+ */
322
+ export const buildMarkers = (view) => {
311
323
  const builder = new RangeSetBuilder();
312
324
  for (const line of view.viewportLineBlocks) {
313
325
  let mark;
@@ -341,8 +353,9 @@ const defaultFoldExtension = /* @__PURE__ */ (() => [foldGutter(), keymap.of(fol
341
353
  /**
342
354
  * 生成折叠命令
343
355
  * @param refOnly 是否仅检查`<ref>`标签
356
+ * @test
344
357
  */
345
- const foldCommand = (refOnly) => view => {
358
+ export const foldCommand = (refOnly) => view => {
346
359
  const { state } = view, tree = ensureSyntaxTree(state, state.doc.length, 1e3) ?? syntaxTree(state), effects = [];
347
360
  let anchor = traverse(state, tree, effects, tree.topNode.firstChild, Infinity, getAnchor(state), updateAll, refOnly);
348
361
  if (!refOnly) {
@@ -372,7 +385,12 @@ export const unfoldRef = (view) => {
372
385
  }
373
386
  return false;
374
387
  };
375
- const selectedLines = (view) => {
388
+ /**
389
+ * 获取所有光标所在的行
390
+ * @param view
391
+ * @test
392
+ */
393
+ export const selectedLines = (view) => {
376
394
  const lines = [];
377
395
  for (const { head } of view.state.selection.ranges) {
378
396
  if (lines.some(({ from, to }) => from <= head && to >= head)) {
@@ -394,6 +412,35 @@ const unfoldCode = (view, line) => {
394
412
  const folded = findFold(view, line);
395
413
  return folded && unfoldEffect.of(folded);
396
414
  };
415
+ /**
416
+ * Fold the template at the selection/cursor
417
+ * @param view
418
+ * @test
419
+ */
420
+ export const foldAt = view => {
421
+ const { state } = view, tree = syntaxTree(state), effects = [];
422
+ let anchor = getAnchor(state);
423
+ for (const { from, to, empty } of state.selection.ranges) {
424
+ let node;
425
+ if (empty) {
426
+ // No selection, try both sides of the cursor position
427
+ node = tree.resolve(from, -1);
428
+ }
429
+ if (!node || node.name === 'Document') {
430
+ node = tree.resolve(from, 1);
431
+ }
432
+ anchor = traverse(state, tree, effects, node, to, anchor, updateSelection);
433
+ }
434
+ if (effects.length > 0) {
435
+ return execute(view, effects, anchor);
436
+ }
437
+ for (const line of selectedLines(view)) {
438
+ if (foldCode(view, line)) {
439
+ return true;
440
+ }
441
+ }
442
+ return false;
443
+ };
397
444
  export default ((e = defaultFoldExtension) => [
398
445
  e,
399
446
  EditorView.theme({
@@ -436,30 +483,7 @@ export const mediawikiFold = /* @__PURE__ */ (() => [
436
483
  // Fold the template at the selection/cursor
437
484
  key: 'Ctrl-Shift-[',
438
485
  mac: 'Cmd-Alt-[',
439
- run(view) {
440
- const { state } = view, tree = syntaxTree(state), effects = [];
441
- let anchor = getAnchor(state);
442
- for (const { from, to, empty } of state.selection.ranges) {
443
- let node;
444
- if (empty) {
445
- // No selection, try both sides of the cursor position
446
- node = tree.resolve(from, -1);
447
- }
448
- if (!node || node.name === 'Document') {
449
- node = tree.resolve(from, 1);
450
- }
451
- anchor = traverse(state, tree, effects, node, to, anchor, updateSelection);
452
- }
453
- if (effects.length > 0) {
454
- return execute(view, effects, anchor);
455
- }
456
- for (const line of selectedLines(view)) {
457
- if (foldCode(view, line)) {
458
- return true;
459
- }
460
- }
461
- return false;
462
- },
486
+ run: foldAt,
463
487
  },
464
488
  {
465
489
  // Fold all templates in the document
package/dist/hover.d.ts CHANGED
@@ -1,4 +1,17 @@
1
- import type { Extension } from '@codemirror/state';
1
+ import type { Extension, EditorState } from '@codemirror/state';
2
+ import type { Hover } from 'vscode-languageserver-types';
2
3
  import type { CodeMirror6 } from './codemirror';
4
+ import type { CompletionSectionName, ApiSuggest } from './token';
5
+ /**
6
+ * @ignore
7
+ * @test
8
+ */
9
+ export declare const getDoc: (section: CompletionSectionName, info?: string) => string;
10
+ /**
11
+ * 从TemplateData API获取hover信息
12
+ * @ignore
13
+ * @test
14
+ */
15
+ export declare const getHoverFromApi: (state: EditorState, pos: number, side: 1 | -1, paramSuggest: ApiSuggest, templatedata?: boolean) => Promise<Hover | undefined>;
3
16
  declare const _default: (articlePath?: string, templatedata?: boolean) => (cm: CodeMirror6) => Extension;
4
17
  export default _default;
package/dist/hover.js CHANGED
@@ -5,8 +5,54 @@ import { tokens } from './config.js';
5
5
  import { base, hoverSelector, bgDark } from './constants.js';
6
6
  import { indexToPos, posToIndex, createTooltipView, toConfigGetter, escHTML, sliceDoc, findTemplateName, } from './util.js';
7
7
  const code = `${hoverSelector} code`;
8
- const getDoc = (section, info = '') => escHTML(info)
8
+ /**
9
+ * @ignore
10
+ * @test
11
+ */
12
+ export const getDoc = (section, info = '') => escHTML(info)
9
13
  + (section === 'Optional' ? '' : `<br><b><i>@${section.toLowerCase()}</i></b>`);
14
+ /**
15
+ * 从TemplateData API获取hover信息
16
+ * @ignore
17
+ * @test
18
+ */
19
+ export const getHoverFromApi = async (state, pos, side, paramSuggest, templatedata) => {
20
+ const node = ensureSyntaxTree(state, pos + Math.max(side, 0))?.resolve(pos, side), { doc } = state;
21
+ if (node?.name.includes(tokens.templateName)) {
22
+ const result = await paramSuggest(sliceDoc(state, node), templatedata), { description, length } = result;
23
+ if (description || length > 0) {
24
+ return {
25
+ contents: {
26
+ kind: 'plaintext',
27
+ value: (description ? `<p>${escHTML(description)}</p>` : '')
28
+ + ['Required', 'Suggested', 'Optional', 'Deprecated'].map(name => {
29
+ const sectionResult = result.filter(([, , , section]) => section === name);
30
+ return sectionResult.length === 0
31
+ ? ''
32
+ : `<h4>${name}</h4><ul>${sectionResult.map(([keys, , info]) => `<li>${keys.map(key => `<code>${escHTML(key)}</code>`).join('/')}${info && ` - ${escHTML(info)}`}</li>`).join('')}</ul>`;
33
+ }).join(''),
34
+ },
35
+ range: { start: indexToPos(doc, node.from), end: indexToPos(doc, node.to) },
36
+ };
37
+ }
38
+ }
39
+ else if (node?.name.includes(tokens.templateArgumentName)) {
40
+ const name = findTemplateName(state, node);
41
+ if (name) {
42
+ const result = await paramSuggest(name, templatedata), param = sliceDoc(state, node).trim().slice(0, -1).trim(), [, , info, section] = result.find(([keys]) => keys.includes(param)) ?? [];
43
+ if (info || section && section !== 'Optional') {
44
+ return {
45
+ contents: {
46
+ kind: 'plaintext',
47
+ value: getDoc(section, info).replace(/^<br>/u, ''),
48
+ },
49
+ range: { start: indexToPos(doc, node.from), end: indexToPos(doc, node.to) },
50
+ };
51
+ }
52
+ }
53
+ }
54
+ return undefined;
55
+ };
10
56
  export default (articlePath, templatedata) => (cm) => {
11
57
  return [
12
58
  hoverTooltip(async (view, pos, side) => {
@@ -14,36 +60,8 @@ export default (articlePath, templatedata) => (cm) => {
14
60
  const { paramSuggest, tags } = cm.langConfig;
15
61
  let hover = await getLSP(view, false, toConfigGetter(cm.getWikiConfig, articlePath), base.CDN)?.provideHover(doc.toString(), indexToPos(doc, pos));
16
62
  if (!hover && paramSuggest && 'templatedata' in tags) {
17
- const node = ensureSyntaxTree(state, pos + Math.max(side, 0))?.resolve(pos, side);
18
- if (node?.name.includes(tokens.templateName)) {
19
- const result = await paramSuggest(sliceDoc(state, node), templatedata), { description, length } = result;
20
- if (description || length > 0) {
21
- hover = {
22
- contents: {
23
- kind: 'plaintext',
24
- value: (description ? `<p>${escHTML(description)}</p>` : '') + (length === 0
25
- ? ''
26
- : `<ul>${result.map(([keys, , info, section]) => `<li>${keys.map(key => `<code>${escHTML(key)}</code>`).join('/')}${info && ' - '}${getDoc(section, info)}</li>`).join('')}</ul>`),
27
- },
28
- range: { start: indexToPos(doc, node.from), end: indexToPos(doc, node.to) },
29
- };
30
- }
31
- }
32
- else if (node?.name.includes(tokens.templateArgumentName)) {
33
- const name = findTemplateName(state, node);
34
- if (name) {
35
- const result = await paramSuggest(name, templatedata), param = sliceDoc(state, node).trim().slice(0, -1).trim(), [, , info, section] = result.find(([keys]) => keys.includes(param)) ?? [];
36
- if (info || section !== 'Optional') {
37
- hover = {
38
- contents: {
39
- kind: 'plaintext',
40
- value: getDoc(section, info).replace(/^<br>/u, ''),
41
- },
42
- range: { start: indexToPos(doc, node.from), end: indexToPos(doc, node.to) },
43
- };
44
- }
45
- }
46
- }
63
+ // eslint-disable-next-line require-atomic-updates
64
+ hover = await getHoverFromApi(state, pos, side, paramSuggest, templatedata);
47
65
  }
48
66
  if (hover) {
49
67
  const { CDN = '' } = base;
package/dist/html.js CHANGED
@@ -2,31 +2,36 @@ import { configureNesting } from '@lezer/html';
2
2
  import { htmlLanguage, htmlCompletionSourceWith } from '@codemirror/lang-html';
3
3
  import { javascript, javascriptLanguage } from '@codemirror/lang-javascript';
4
4
  import { cssLanguage } from '@codemirror/lang-css';
5
- import { LanguageSupport, syntaxHighlighting, defaultHighlightStyle, HighlightStyle } from '@codemirror/language';
5
+ import { LanguageSupport } from '@codemirror/language';
6
6
  import { cssCompletion } from './css.js';
7
7
  import { jsCompletion } from './javascript.js';
8
8
  import { mediawikiBase } from './mediawiki.js';
9
+ import { getLightHighlightStyle } from './theme.js';
9
10
  export default (config) => {
10
- const { language, support } = mediawikiBase(config), lang = new LanguageSupport(htmlLanguage.configure({
11
+ const { language, support } = mediawikiBase(config),
12
+ /** @test */
13
+ lang = htmlLanguage.configure({
11
14
  wrap: configureNesting([
12
15
  { tag: 'script', parser: javascriptLanguage.parser },
13
16
  { tag: 'style', parser: cssLanguage.parser },
14
17
  { tag: 'noinclude', parser: language.parser },
15
18
  ], [{ name: 'style', parser: cssLanguage.parser.configure({ top: 'Styles' }) }]),
16
- }), [
17
- htmlLanguage.data.of({
18
- autocomplete: htmlCompletionSourceWith({
19
- extraTags: {
20
- noinclude: { globalAttrs: false },
21
- },
22
- }),
19
+ }),
20
+ /** @test */
21
+ autocomplete = htmlLanguage.data.of({
22
+ autocomplete: htmlCompletionSourceWith({
23
+ extraTags: {
24
+ noinclude: { globalAttrs: false },
25
+ },
23
26
  }),
27
+ }), langSupport = new LanguageSupport(lang, [
28
+ autocomplete,
24
29
  javascript().support,
25
30
  jsCompletion,
26
31
  cssCompletion(),
27
32
  support,
28
- syntaxHighlighting(HighlightStyle.define(defaultHighlightStyle.specs, { themeType: 'light' })),
33
+ getLightHighlightStyle(),
29
34
  ]);
30
- Object.assign(lang, { nestedMWLanguage: language });
31
- return lang;
35
+ Object.assign(langSupport, { nestedMWLanguage: language });
36
+ return langSupport;
32
37
  };
package/dist/indent.d.ts CHANGED
@@ -3,10 +3,17 @@ export interface Text extends TextBase {
3
3
  children: readonly Text[] | null;
4
4
  text?: string[];
5
5
  }
6
+ /**
7
+ * 获取逐行文本内容
8
+ * @param text
9
+ * @test
10
+ */
11
+ export declare const getLines: (text: Text) => string[];
6
12
  /**
7
13
  * 检测文本的缩进方式
8
14
  * @param text 文本内容
9
15
  * @param defaultIndent 默认缩进方式
10
16
  * @param lang 语言
17
+ * @test
11
18
  */
12
19
  export declare const detectIndent: (text: string | Text, defaultIndent: string, lang: string) => string;
package/dist/indent.js CHANGED
@@ -1,10 +1,16 @@
1
1
  import { noDetectionLangs } from './constants.js';
2
- const getLines = (text) => text.children?.flatMap(getLines) ?? text.text;
2
+ /**
3
+ * 获取逐行文本内容
4
+ * @param text
5
+ * @test
6
+ */
7
+ export const getLines = (text) => text.children?.flatMap(getLines) ?? text.text;
3
8
  /**
4
9
  * 检测文本的缩进方式
5
10
  * @param text 文本内容
6
11
  * @param defaultIndent 默认缩进方式
7
12
  * @param lang 语言
13
+ * @test
8
14
  */
9
15
  export const detectIndent = (text, defaultIndent, lang) => {
10
16
  if (noDetectionLangs.has(lang)) {
package/dist/inlay.js CHANGED
@@ -22,7 +22,7 @@ const stateEffect = StateEffect.define(), field = StateField.define({
22
22
  const str = doc.toString();
23
23
  for (const effect of effects) {
24
24
  if (effect.is(stateEffect)) {
25
- const { value: { text, inlayHints } } = effect;
25
+ const { text, inlayHints } = effect.value;
26
26
  if (str === text) {
27
27
  return inlayHints
28
28
  ? Decoration.set(inlayHints.map(({ position, label }) => Decoration.widget({
@@ -1,4 +1,13 @@
1
- import type { Extension } from '@codemirror/state';
1
+ import type { Extension, EditorState } from '@codemirror/state';
2
+ import type { DecorationSet } from '@codemirror/view';
3
+ import type { Tree } from '@lezer/common';
4
+ import type { DocRange } from './fold';
2
5
  export declare const jsCompletion: Extension;
6
+ /**
7
+ * 高亮显示全局变量
8
+ * @ignore
9
+ * @test
10
+ */
11
+ export declare const markGlobals: (tree: Tree, visibleRanges: readonly DocRange[], state: EditorState) => DecorationSet;
3
12
  declare const _default: () => Extension;
4
13
  export default _default;
@@ -4,7 +4,12 @@ import { syntaxTree } from '@codemirror/language';
4
4
  import { builtin } from 'globals/globals.json';
5
5
  export const jsCompletion = javascriptLanguage.data.of({ autocomplete: scopeCompletionSource(globalThis) });
6
6
  const globals = Decoration.mark({ class: 'cm-globals' });
7
- const markGlobals = (tree, visibleRanges, state) => {
7
+ /**
8
+ * 高亮显示全局变量
9
+ * @ignore
10
+ * @test
11
+ */
12
+ export const markGlobals = (tree, visibleRanges, state) => {
8
13
  const decorations = [];
9
14
  for (const { from, to } of visibleRanges) {
10
15
  tree.iterate({
@@ -11,5 +11,6 @@ export declare const keybindings: KeymapConfig[];
11
11
  * @param text 跨行文本
12
12
  * @param pre 前缀
13
13
  * @param post 后缀
14
+ * @test
14
15
  */
15
16
  export declare const encapsulateLines: (text: string, pre: string, post: string) => string;
@@ -26,6 +26,7 @@ export const keybindings = [
26
26
  * @param text 跨行文本
27
27
  * @param pre 前缀
28
28
  * @param post 后缀
29
+ * @test
29
30
  */
30
31
  export const encapsulateLines = (text, pre, post) => {
31
32
  const lines = text.split('\n');