@bhsd/codemirror-mediawiki 3.9.2 → 3.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +147 -87
  2. package/dist/bidi.d.ts +9 -8
  3. package/dist/bidi.js +38 -23
  4. package/dist/codemirror.d.ts +7 -0
  5. package/dist/codemirror.js +22 -9
  6. package/dist/color.d.ts +8 -5
  7. package/dist/color.js +5 -1
  8. package/dist/config.d.ts +1 -0
  9. package/dist/config.js +1 -0
  10. package/dist/constants.d.ts +3 -2
  11. package/dist/constants.js +3 -2
  12. package/dist/css.d.ts +5 -0
  13. package/dist/css.js +14 -6
  14. package/dist/escape.d.ts +22 -2
  15. package/dist/escape.js +44 -25
  16. package/dist/fold.d.ts +57 -5
  17. package/dist/fold.js +149 -58
  18. package/dist/hover.d.ts +16 -3
  19. package/dist/hover.js +84 -67
  20. package/dist/html.js +17 -12
  21. package/dist/indent.d.ts +7 -0
  22. package/dist/indent.js +7 -1
  23. package/dist/index.d.ts +54 -16
  24. package/dist/index.js +91 -38
  25. package/dist/inlay.d.ts +1 -1
  26. package/dist/inlay.js +26 -25
  27. package/dist/javascript.d.ts +10 -1
  28. package/dist/javascript.js +50 -2
  29. package/dist/keybindings.d.ts +1 -0
  30. package/dist/keybindings.js +1 -0
  31. package/dist/keymap.d.ts +11 -0
  32. package/dist/keymap.js +3 -4
  33. package/dist/linter.d.ts +31 -2
  34. package/dist/linter.js +10 -3
  35. package/dist/lintsource.d.ts +47 -3
  36. package/dist/lintsource.js +50 -11
  37. package/dist/lua.d.ts +0 -2
  38. package/dist/lua.js +27 -10
  39. package/dist/main.min.js +31 -29
  40. package/dist/matchBrackets.d.ts +16 -0
  41. package/dist/matchBrackets.js +16 -0
  42. package/dist/matchTag.d.ts +5 -2
  43. package/dist/matchTag.js +11 -7
  44. package/dist/mediawiki.d.ts +15 -2
  45. package/dist/mediawiki.js +59 -45
  46. package/dist/mw.min.js +33 -37
  47. package/dist/mwConfig.js +2 -2
  48. package/dist/openLinks.d.ts +12 -2
  49. package/dist/openLinks.js +64 -54
  50. package/dist/ref.d.ts +16 -2
  51. package/dist/ref.js +110 -95
  52. package/dist/signature.d.ts +7 -1
  53. package/dist/signature.js +53 -49
  54. package/dist/static.d.ts +4 -0
  55. package/dist/static.js +4 -0
  56. package/dist/statusBar.js +9 -8
  57. package/dist/theme.d.ts +1 -0
  58. package/dist/theme.js +8 -0
  59. package/dist/token.d.ts +29 -7
  60. package/dist/token.js +33 -18
  61. package/dist/util.d.ts +25 -2
  62. package/dist/util.js +47 -1
  63. package/dist/wiki.min.js +32 -36
  64. package/i18n/en.json +2 -2
  65. package/i18n/zh-hans.json +2 -2
  66. package/i18n/zh-hant.json +2 -2
  67. package/package.json +15 -13
package/dist/theme.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Extension } from '@codemirror/state';
2
+ export declare const getLightHighlightStyle: () => Extension;
2
3
  export declare const light: Extension,
3
4
  /**
4
5
  * @author 鬼影233
package/dist/theme.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { EditorView } from '@codemirror/view';
2
+ import { syntaxHighlighting, HighlightStyle, defaultHighlightStyle } from '@codemirror/language';
2
3
  import { nord as nordBase } from 'cm6-theme-nord';
3
4
  import { matchingCls, nonmatchingCls, actionSelector, panelsSelector, bgDark, } from './constants.js';
4
5
  const focused = '&.cm-focused', matching = `${focused} .${matchingCls}`, nonmatching = `${focused} .${nonmatchingCls}`;
6
+ export const getLightHighlightStyle = () => syntaxHighlighting(HighlightStyle.define(defaultHighlightStyle.specs, { themeType: 'light' }));
5
7
  export const light = /* @__PURE__ */ EditorView.theme({
6
8
  '&': {
7
9
  backgroundColor: '#fff',
@@ -25,6 +27,9 @@ export const light = /* @__PURE__ */ EditorView.theme({
25
27
  '--cm-var-name': '#ac6600',
26
28
  '--cm-ref': 'rgb(223,242,235,.5)',
27
29
  },
30
+ '.cm-globals, .cm-globals>*': {
31
+ color: '#164',
32
+ },
28
33
  [matching]: {
29
34
  backgroundColor: 'rgb(50,140,130,.32)',
30
35
  },
@@ -61,6 +66,9 @@ nord = /* @__PURE__ */ (() => [
61
66
  '--cm-var-name': '#d08770',
62
67
  '--cm-ref': 'rgb(60,90,80,0.5)',
63
68
  },
69
+ '.cm-globals, .cm-globals>*': {
70
+ color: '#d08770',
71
+ },
64
72
  'div.cm-activeLine': {
65
73
  backgroundColor: 'rgb(76,86,106,.27)',
66
74
  },
package/dist/token.d.ts CHANGED
@@ -13,12 +13,12 @@ declare type Style = string | [string];
13
13
  declare type Tokenizer<T = Style> = ((stream: StringStream, state: State) => T) & {
14
14
  args?: unknown[];
15
15
  };
16
- declare type NestCount = 'nTemplate' | 'nExt' | 'nVar' | 'nLink' | 'nExtLink';
16
+ export type NestCount = 'nTemplate' | 'nExt' | 'nVar' | 'nLink' | 'nExtLink';
17
17
  declare interface Nesting extends Record<NestCount, number> {
18
18
  extName: string | false;
19
19
  extState: object | false;
20
20
  }
21
- declare interface State extends Nesting {
21
+ export interface State extends Nesting {
22
22
  readonly stack: Tokenizer[];
23
23
  readonly inHtmlTag: string[];
24
24
  tokenize: Tokenizer;
@@ -37,6 +37,7 @@ declare interface State extends Nesting {
37
37
  imgLink: boolean;
38
38
  data: MediaWikiData;
39
39
  }
40
+ declare type ExtState = Omit<State, 'dt'> & Partial<Pick<State, 'dt'>>;
40
41
  declare interface Token {
41
42
  readonly char?: string | undefined;
42
43
  readonly string: string;
@@ -44,11 +45,12 @@ declare interface Token {
44
45
  pos: number;
45
46
  style: Style;
46
47
  }
47
- declare interface StringStream extends StringStreamBase {
48
+ export interface StringStream extends StringStreamBase {
48
49
  match(pattern: string, consume?: boolean, caseInsensitive?: boolean): true | null;
49
50
  match(pattern: RegExp, consume?: boolean): RegExpMatchArray | null;
50
51
  }
51
- export type ApiSuggestions = [string, string?][] & {
52
+ export type CompletionSectionName = 'Required' | 'Suggested' | 'Optional' | 'Deprecated';
53
+ export type ApiSuggestions<T = string[]> = [T, string?, string?, CompletionSectionName?][] & {
52
54
  description?: string;
53
55
  };
54
56
  /**
@@ -57,17 +59,17 @@ export type ApiSuggestions = [string, string?][] & {
57
59
  * @param subpage 是否为子页面
58
60
  * @param namespace 命名空间
59
61
  */
60
- export type ApiSuggest = (search: string, subpage?: boolean, namespace?: number) => ApiSuggestions | Promise<ApiSuggestions>;
62
+ export type ApiSuggest<T = string[]> = (search: string, subpage?: boolean, namespace?: number) => ApiSuggestions<T> | Promise<ApiSuggestions<T>>;
61
63
  export interface MwConfig extends MwConfigBase {
62
64
  nsid: Record<string, number>;
63
65
  variants?: string[];
64
66
  img?: Record<string, string>;
65
67
  permittedHtmlTags?: string[];
66
68
  implicitlyClosedHtmlTags?: string[];
67
- linkSuggest?: ApiSuggest;
69
+ articlePath?: string;
70
+ linkSuggest?: ApiSuggest<string>;
68
71
  paramSuggest?: ApiSuggest;
69
72
  titleParser?: (state: EditorState, node: SyntaxNode) => string | undefined;
70
- isbnParser?: (link: string) => string;
71
73
  }
72
74
  declare class MediaWikiData {
73
75
  /** 已解析的节点 */
@@ -83,6 +85,26 @@ declare class MediaWikiData {
83
85
  readonly urlProtocols: RegExp;
84
86
  constructor(tags: string[], urlProtocols: string);
85
87
  }
88
+ /**
89
+ * 是否为行首语法
90
+ * @param stream
91
+ * @param table 是否允许表格
92
+ * @param file 是否为文件
93
+ * @test
94
+ */
95
+ export declare const isSolSyntax: (stream: StringStream, table?: boolean, file?: boolean) => unknown;
96
+ /**
97
+ * 获取负向先行断言
98
+ * @param chars
99
+ * @param comment 是否仅排除注释
100
+ * @test
101
+ */
102
+ export declare const lookahead: (chars: string, comment?: boolean | State) => string;
103
+ /**
104
+ * @ignore
105
+ * @test
106
+ */
107
+ export declare const makeLocalStyle: (style: string, state: ExtState, endGround?: NestCount) => string;
86
108
  /** Adapted from the original CodeMirror 5 stream parser by Pavel Astakhov */
87
109
  export declare class MediaWiki {
88
110
  readonly config: MwConfig;
package/dist/token.js CHANGED
@@ -153,16 +153,18 @@ const pop = (state) => {
153
153
  * @param stream
154
154
  * @param table 是否允许表格
155
155
  * @param file 是否为文件
156
+ * @test
156
157
  */
157
- const isSolSyntax = (stream, table, file) => stream.sol() && (table && stream.match(/^\s*(?::+\s*)?\{\|/u, false)
158
+ export const isSolSyntax = (stream, table, file) => stream.sol() && (table && stream.match(/^\s*(?::+\s*)?\{\|/u, false)
158
159
  || stream.match(/^(?:-{4}|=)/u, false)
159
160
  || !file && /[*#;:]/u.test(stream.peek() || ''));
160
161
  /**
161
162
  * 获取负向先行断言
162
163
  * @param chars
163
164
  * @param comment 是否仅排除注释
165
+ * @test
164
166
  */
165
- const lookahead = (chars, comment) => {
167
+ export const lookahead = (chars, comment) => {
166
168
  const table = {
167
169
  "'": "'(?!')",
168
170
  '{': String.raw `\{(?!\{)`,
@@ -212,7 +214,11 @@ const getTokenizer = (method, context) => function (...args) {
212
214
  const makeFullStyle = (style, state) => (typeof style === 'string'
213
215
  ? style
214
216
  : `${style[0]} ${state.bold || state.dt?.n ? tokens.strong : ''} ${state.italic ? tokens.em : ''}`).trim().replace(/\s{2,}/gu, ' ') || ' ';
215
- const makeLocalStyle = (style, state, endGround) => {
217
+ /**
218
+ * @ignore
219
+ * @test
220
+ */
221
+ export const makeLocalStyle = (style, state, endGround) => {
216
222
  let ground = '';
217
223
  switch (state.nTemplate) {
218
224
  case 0:
@@ -305,6 +311,11 @@ const getQuote = (stream) => {
305
311
  * @param t Tokenizer
306
312
  */
307
313
  const getEqual = (t) => t.name === 'inTemplateArgument' && t.args[0] ? '=' : '';
314
+ /**
315
+ * 转义字符类中的特殊字符
316
+ * @param chars 字符类
317
+ */
318
+ const escapeCharClass = (chars) => chars.replace(/[\]-]/gu, String.raw `\$&`);
308
319
  /**
309
320
  * 下一个字符是否为空白字符
310
321
  * @param stream StringStream
@@ -329,21 +340,21 @@ const syntaxHighlight = new Set(['syntaxhighlight', 'source', 'pre']), pageFunct
329
340
  'canonicalurle',
330
341
  'int',
331
342
  'msgnw',
332
- ]), substs = new Set(['subst', 'safesubst']), headerRegex = new RegExp(`^(?:[^&[<{~'-]|${lookahead("<{~'-")})+`, 'iu'), templateRegex = new RegExp(`^(?:[^|{}<]|${lookahead('{}<', true)})+`, 'u'), argumentRegex = new RegExp(`^(?:[^|[&:}{<~'__-]|${lookahead("}{<~'__-")})+`, 'iu'), styleRegex = new RegExp(`^(?:[^|[&}{<~'__-]|${lookahead("}{<~'__-")})+`, 'iu'), wikiRegex = new RegExp(`^(?:[^&'{[<~__:-]|${lookahead("'{[<~__-")})+`, 'iu'), tableDefinitionRegex = new RegExp(`^(?:[^&={<]|${lookahead('{<')})+`, 'iu'), tableCellRegex = /^\s*(?:[|!]|\{\{\s*![!)+-]?\s*\}\})/u, extLinkChars = "[{'<-", tableDefinitionChars = '{<', tableCellChars = "'<~__{-", htmlAttrChars = '{/', freeRegex = [false, true].map(lpar => {
343
+ ]), substs = new Set(['subst', 'safesubst']), headerRegex = new RegExp(String.raw `^(?:[^&[<{~'\-]|${lookahead("<{~'-")})+`, 'iu'), templateRegex = new RegExp(`^(?:[^|{}<]|${lookahead('{}<', true)})+`, 'u'), argumentRegex = new RegExp(String.raw `^(?:[^|[&:}{<~'__\-]|${lookahead("}{<~'__-")})+`, 'iu'), styleRegex = new RegExp(String.raw `^(?:[^|[&}{<~'__\-]|${lookahead("}{<~'__-")})+`, 'iu'), wikiRegex = new RegExp(String.raw `^(?:[^&:'{[<~__\-]|${lookahead("'{[<~__-")})+`, 'iu'), tableDefinitionRegex = new RegExp(`^(?:[^&={<]|${lookahead('{<')})+`, 'iu'), tableCellRegex = /^\s*(?:[|!]|\{\{\s*![!)\-+]?\s*\}\})/u, extLinkChars = "[{'<-", tableDefinitionChars = '{<', tableCellChars = "'<~__{-", htmlAttrChars = '{/', freeRegex = [false, true].map(lpar => {
333
344
  const punctuations = getPunctuations(lpar), source = getUrlRegex(punctuations);
334
345
  return new RegExp(`^(?:${source}|[${punctuations}]+(?=${source}))*`, 'u');
335
346
  }), indentedTableRegex = [false, true].map(isTemplate => new RegExp(String.raw `^:*\s*(?=\{${getPipe(isTemplate)})`, 'u')), tableRegex = [false, true]
336
347
  .map(isTemplate => new RegExp(String.raw `^${getPipe(isTemplate)}\s*`, 'u')), spacedTableRegex = [false, true].map(isTemplate => new RegExp(String.raw `^\s*(:+\s*)?(?=\{${getPipe(isTemplate)})`, 'u')), linkTextRegex = [false, true].map(file => {
337
348
  const chars = `]'{<${file ? '~' : '['}-`;
338
- return new RegExp(`^(?:[^&${file ? '[|' : ''}\\${chars}]|${lookahead(chars)})+`, 'iu');
349
+ return new RegExp(`^(?:[^&${file ? '[|' : ''}${escapeCharClass(chars)}]|${lookahead(chars)})+`, 'iu');
339
350
  }), linkErrorRegex = [
340
351
  new RegExp(String.raw `^(?:[<>{}]|%(?:3[ce]|[57][bd])|${lookahead('[]')})+`, 'iu'),
341
352
  new RegExp(String.raw `^(?:\}|${lookahead('[]{')})+`, 'u'),
342
353
  new RegExp(String.raw `^(?:[>}]|%(?:3[ce]|[57][bd])|${lookahead('[]{<')})+`, 'iu'),
343
- ], tableDefinitionValueRegex = ['', '='].map(equal => new RegExp(String.raw `^(?:[^\s&${tableDefinitionChars}${equal}]|${lookahead(tableDefinitionChars)})+`, 'iu')), variableRegex = [false, true].map(isDefault => new RegExp(String.raw `^(?:[^|{}<${isDefault ? "[&~'__:-" : ''}]|\}(?!\}\})|${isDefault ? lookahead("{<~'__-") : lookahead('{<', true)})+`, 'iu')), parserFunctionRegex = ['', '[&', '[&:'].map(s => getRegex(chars => new RegExp(`^(?:[^|${s}${chars}]|${lookahead(chars)})+`, 'iu'))), doubleUnderscoreRegex = {
354
+ ], tableDefinitionValueRegex = ['', '='].map(equal => new RegExp(String.raw `^(?:[^\s&${tableDefinitionChars}${equal}]|${lookahead(tableDefinitionChars)})+`, 'iu')), variableRegex = [false, true].map(isDefault => new RegExp(String.raw `^(?:[^|{}<${isDefault ? String.raw `[&~'__:\-` : ''}]|\}(?!\}\})|${isDefault ? lookahead("{<~'__-") : lookahead('{<', true)})+`, 'iu')), parserFunctionRegex = ['', '[&', '[&:'].map(s => getRegex(chars => new RegExp(`^(?:[^|${s}${escapeCharClass(chars)}]|${lookahead(chars)})+`, 'iu'))), doubleUnderscoreRegex = {
344
355
  _: /^[\p{L}\p{N}_]+?__/u,
345
356
  '_': /^[\p{L}\p{N}__]+?_{2}/u,
346
- }, getExtLinkTextRegex = getRegex(pipe => new RegExp(String.raw `^(?:[^\]&${pipe}${extLinkChars}]|${lookahead(extLinkChars)})+`, 'iu')), getExtLinkRegex = getRegex(pipe => new RegExp(`^(?:${getUrlRegex(pipe)})+`, 'u')), getTableDefinitionRegex = getRegex(s => new RegExp(`^(?:[^&${tableDefinitionChars}${s}]|${lookahead(tableDefinitionChars)})+`, 'iu')), getTableCellRegex = getRegex(s => new RegExp(`^(?:[^[&${s}${tableCellChars}]|${lookahead(tableCellChars)})+`, 'iu')), getHtmlAttrRegex = getRegex(s => new RegExp(`^(?:[^<>&${htmlAttrChars}${s}]|${lookahead(htmlAttrChars)})+`, 'u')), getHtmlAttrKeyRegex = getRegex(pipe => new RegExp(`^(?:[^<>&={/${pipe}]|${lookahead('{/')})+`, 'u')), getExtAttrRegex = getRegex(s => new RegExp(`^(?:[^>/${s}]|${lookahead('/')})+`, 'u')), getExtTagCloseRegex = getRegex(name => name === 'onlyinclude'
357
+ }, getExtLinkTextRegex = getRegex(pipe => new RegExp(String.raw `^(?:[^\]&${pipe}${escapeCharClass(extLinkChars)}]|${lookahead(extLinkChars)})+`, 'iu')), getExtLinkRegex = getRegex(pipe => new RegExp(`^(?:${getUrlRegex(pipe)})+`, 'u')), getTableDefinitionRegex = getRegex(s => new RegExp(`^(?:[^&${tableDefinitionChars}${s}]|${lookahead(tableDefinitionChars)})+`, 'iu')), getTableCellRegex = getRegex(s => new RegExp(`^(?:[^[&${s}${escapeCharClass(tableCellChars)}]|${lookahead(tableCellChars)})+`, 'iu')), getHtmlAttrRegex = getRegex(s => new RegExp(`^(?:[^<>&${htmlAttrChars}${s}]|${lookahead(htmlAttrChars)})+`, 'u')), getHtmlAttrKeyRegex = getRegex(pipe => new RegExp(`^(?:[^<>&={/${pipe}]|${lookahead('{/')})+`, 'u')), getExtAttrRegex = getRegex(s => new RegExp(`^(?:[^>/${s}]|${lookahead('/')})+`, 'u')), getExtTagCloseRegex = getRegex(name => name === 'onlyinclude'
347
358
  ? /<\/onlyinclude(?:>|$)/u
348
359
  : new RegExp(String.raw `</${name}\s*(?:>|$)`, 'iu')), getNestedRegex = getRegex(tag => new RegExp(String.raw `^(?:[^<]|<(?!${tag}(?:[\s/>]|$)))+`, 'iu'));
349
360
  /** Adapted from the original CodeMirror 5 stream parser by Pavel Astakhov */
@@ -463,15 +474,18 @@ let MediaWiki = (() => {
463
474
  this.fileRegex = new RegExp(String.raw `^(?:${Object.entries(nsid).filter(([, id]) => id === 6).map(([ns]) => ns).join('|')})\s*:`, 'iu');
464
475
  this.redirectRegex = new RegExp(String.raw `^\s*(?:${redirection.join('|')})(\s*:)?\s*(?=\[\[|$)`, 'iu');
465
476
  this.img = Object.keys(img).filter(word => !/\$1./u.test(word));
477
+ const spImgKeys = Object.keys(img).filter(word => word.startsWith('$1'));
466
478
  this.imgRegex = new RegExp(String.raw `^(?:${this.img.filter(word => word.endsWith('$1')).map(word => word.slice(0, -2))
467
- .join('|')}|(?:${this.img.filter(word => !word.endsWith('$1')).join('|')}|(?:\d+x?|\d*x\d+)\s*(?:px)?px)\s*(?=\||\]\]|$))`, 'u');
479
+ .join('|')}|(?:${this.img.filter(word => !word.endsWith('$1')).join('|')}|(?:(?:\d+x?|\d*x\d+)\s*(?:px)?(?:${spImgKeys.filter(word => img[word] === 'img_width').map(word => word.slice(2))
480
+ .join('|')}))|\d+\s*(?:${spImgKeys.filter(word => img[word] !== 'img_width').map(word => word.slice(2))
481
+ .join('|')}))\s*(?=\||\]\]|$))`, 'u');
468
482
  this.tags = [...Object.keys(tags), 'includeonly', 'noinclude', 'onlyinclude'];
469
- this.convertRegex = new RegExp(String.raw `^(?:[^}|;&='{[<~__-]|\}(?!-)|=(?!>)|\[(?!\[|${urlProtocols})|${lookahead("'{<~__-")})+`, 'iu');
483
+ this.convertRegex = new RegExp(String.raw `^(?:[^}|;&='{[<~__\-]|\}(?!-)|=(?!>)|\[(?!\[|${urlProtocols})|${lookahead("'{<~__-")})+`, 'iu');
470
484
  this.convertSemicolon = variants && new RegExp(String.raw `^;\s*(?=(?:[^;]*?=>\s*)?(?:${variants.join('|')})\s*:|(?:$|\}-))`, 'iu');
471
485
  this.convertLang = variants
472
486
  && new RegExp(String.raw `^(?:=>\s*)?(?:${variants.join('|')})\s*:`, 'iu');
473
487
  this.hasVariants = Boolean(variants?.length);
474
- this.preRegex = [false, true].map(begin => new RegExp(String.raw `^(?:[^<&-]|-${this.hasVariants ? String.raw `(?!\{)` : ''}|<(?!${begin ? '/' : ''}nowiki>))+`, 'iu'));
488
+ this.preRegex = [false, true].map(begin => new RegExp(String.raw `^(?:[^<&\-]|-${this.hasVariants ? String.raw `(?!\{)` : ''}|<(?!${begin ? '/' : ''}nowiki>))+`, 'iu'));
475
489
  this.substRegex = new RegExp(String.raw `^\s*(?:(${Object.entries(insensitive).filter(([, v]) => substs.has(v))
476
490
  .map(([k]) => k + (k.endsWith(':') ? '' : ':'))
477
491
  .join('|')})\s*)?`, 'iu');
@@ -775,7 +789,7 @@ let MediaWiki = (() => {
775
789
  stream.backUp(1);
776
790
  }
777
791
  else {
778
- stream.eatWhile(/[^\p{L}\p{N}__&'{[<~:-]/u);
792
+ stream.eatWhile(/[^\p{L}\p{N}__&'{[<\-~:]/u);
779
793
  }
780
794
  const mt = stream.match(this.urlProtocols, false);
781
795
  if (mt) {
@@ -1046,7 +1060,7 @@ let MediaWiki = (() => {
1046
1060
  return (stream, state) => {
1047
1061
  if (stream.sol()) {
1048
1062
  stream.eatSpace();
1049
- const mt = stream.match(/^(?:\||\{\{\s*!([!)+-])?\s*\}\})/u);
1063
+ const mt = stream.match(/^(?:\||\{\{\s*!([!)\-+])?\s*\}\})/u);
1050
1064
  if (mt) {
1051
1065
  if (mt[1] === '-' || !mt[1] && stream.eat('-')) {
1052
1066
  stream.match(/^-*\s*/u);
@@ -1648,7 +1662,7 @@ let MediaWiki = (() => {
1648
1662
  if (expectName
1649
1663
  && stream.match(new RegExp(`^(?:[^=|}{[<]|${lookahead('}{[<', state)})*=`, 'iu'))) {
1650
1664
  state.tokenize = this.inTemplateArgument(false, parserFunction);
1651
- return makeLocalTagStyle('templateArgumentName', state);
1665
+ return makeLocalTagStyle(parserFunction ? 'parserFunctionArgumentName' : 'templateArgumentName', state);
1652
1666
  }
1653
1667
  else if (isSolSyntax(stream) && stream.peek() !== '=') {
1654
1668
  return this.eatWikiText(tag)(stream, state);
@@ -1684,7 +1698,7 @@ let MediaWiki = (() => {
1684
1698
  if (stream.match('-{', false)) {
1685
1699
  return this.eatWikiText(style)(stream, state);
1686
1700
  }
1687
- stream.match(/^(?:(?:[^};=-]|\}(?!-)|=(?!>)|-(?!\{))+|;|=>)/u);
1701
+ stream.match(/^(?:(?:[^}\-;=]|\}(?!-)|=(?!>)|-(?!\{))+|;|=>)/u);
1688
1702
  return makeStyle(style, state);
1689
1703
  }
1690
1704
  return !isSolSyntax(stream, true) && stream.match(this.convertRegex) || space
@@ -1782,16 +1796,17 @@ let MediaWiki = (() => {
1782
1796
  // get token style
1783
1797
  stream.start = stream.pos;
1784
1798
  const char = stream.peek(), style = state.tokenize(stream, state);
1785
- if (typeof style === 'string' && style.includes(tokens.templateArgumentName)) {
1799
+ if (typeof style === 'string' && style.includes('-argument-name')) {
1800
+ const isTemplate = style.includes(tokens.templateArgumentName), argument = tokens[isTemplate ? 'template' : 'parserFunction'], argumentName = tokens[isTemplate ? 'templateArgumentName' : 'parserFunctionArgumentName'], delimiter = tokens[isTemplate ? 'templateDelimiter' : 'parserFunctionDelimiter'];
1786
1801
  for (let i = readyTokens.length - 1; i >= 0; i--) {
1787
1802
  const token = readyTokens[i];
1788
1803
  if (cmpNesting(state, token.state, true)) {
1789
- const types = typeof token.style === 'string' && token.style.split(' '), j = types && types.indexOf(tokens.template);
1804
+ const types = typeof token.style === 'string' && token.style.split(' '), j = types && types.indexOf(argument);
1790
1805
  if (j !== false && j !== -1) {
1791
- types[j] = tokens.templateArgumentName;
1806
+ types[j] = argumentName;
1792
1807
  token.style = types.join(' ');
1793
1808
  }
1794
- else if (types && types.includes(tokens.templateDelimiter)) {
1809
+ else if (types && types.includes(delimiter)) {
1795
1810
  break;
1796
1811
  }
1797
1812
  }
package/dist/util.d.ts CHANGED
@@ -1,22 +1,27 @@
1
1
  import type { EditorView, TooltipView } from '@codemirror/view';
2
- import type { Text, EditorState } from '@codemirror/state';
2
+ import type { Text, EditorState, SelectionRange } from '@codemirror/state';
3
3
  import type { SyntaxNode } from '@lezer/common';
4
4
  import type { Position } from 'vscode-languageserver-types';
5
+ import type { ConfigGetter } from '@bhsd/browser';
6
+ import type { TagName } from './config';
5
7
  /**
6
8
  * 转义HTML字符串
7
9
  * @param text 原字符串
10
+ * @test
8
11
  */
9
12
  export declare const escHTML: (text: string) => string;
10
13
  /**
11
14
  * 将索引转换为位置
12
15
  * @param doc Text 实例
13
16
  * @param index 索引
17
+ * @test
14
18
  */
15
19
  export declare const indexToPos: (doc: Text, index: number) => Position;
16
20
  /**
17
21
  * 将位置转换为索引
18
22
  * @param doc Text 实例
19
23
  * @param pos 位置
24
+ * @test
20
25
  */
21
26
  export declare const posToIndex: (doc: Text, pos: Position) => number;
22
27
  /**
@@ -25,20 +30,38 @@ export declare const posToIndex: (doc: Text, pos: Position) => number;
25
30
  * @param innerHTML 提示内容
26
31
  */
27
32
  export declare const createTooltipView: (view: EditorView, innerHTML: string) => TooltipView;
33
+ /**
34
+ * 获取节点对应的字符串
35
+ * @param state EditorState 实例
36
+ * @param node 语法树节点
37
+ * @test
38
+ */
39
+ export declare const sliceDoc: (state: EditorState, node: SyntaxNode | SelectionRange) => string;
28
40
  /**
29
41
  * Update the stack of opening (+) or closing (-) brackets
30
42
  * @param state
31
43
  * @param node 语法树节点
44
+ * @test
32
45
  */
33
46
  export declare const braceStackUpdate: (state: EditorState, node: SyntaxNode) => [number, number];
47
+ /**
48
+ * Find the current template name
49
+ * @param state
50
+ * @param node 语法树节点
51
+ * @test
52
+ */
53
+ export declare const findTemplateName: (state: EditorState, node: SyntaxNode) => string | null;
34
54
  /**
35
55
  * 判断节点是否包含指定类型
36
56
  * @param types 节点类型
37
57
  * @param names 指定类型
58
+ * @test
38
59
  */
39
- export declare const hasTag: (types: Set<string>, names: string | string[]) => boolean;
60
+ export declare const hasTag: (types: Set<string>, names: TagName | TagName[]) => boolean;
61
+ export declare const toConfigGetter: (configGetter?: ConfigGetter, articlePath?: string) => ConfigGetter | undefined;
40
62
  /**
41
63
  * 获取字符串开头的空白字符
42
64
  * @param str 字符串
65
+ * @test
43
66
  */
44
67
  export declare const leadingSpaces: (str: string) => string;
package/dist/util.js CHANGED
@@ -5,12 +5,14 @@ const dict = { '\n': '<br>', '&': '&amp;', '<': '&lt;' };
5
5
  /**
6
6
  * 转义HTML字符串
7
7
  * @param text 原字符串
8
+ * @test
8
9
  */
9
10
  export const escHTML = (text) => text.replace(/[\n<&]/gu, ch => dict[ch]);
10
11
  /**
11
12
  * 将索引转换为位置
12
13
  * @param doc Text 实例
13
14
  * @param index 索引
15
+ * @test
14
16
  */
15
17
  export const indexToPos = (doc, index) => {
16
18
  const line = doc.lineAt(index);
@@ -20,6 +22,7 @@ export const indexToPos = (doc, index) => {
20
22
  * 将位置转换为索引
21
23
  * @param doc Text 实例
22
24
  * @param pos 位置
25
+ * @test
23
26
  */
24
27
  export const posToIndex = (doc, pos) => {
25
28
  const line = doc.line(pos.line + 1);
@@ -36,23 +39,66 @@ export const createTooltipView = (view, innerHTML) => {
36
39
  inner.innerHTML = innerHTML;
37
40
  return { dom };
38
41
  };
42
+ /**
43
+ * 获取节点对应的字符串
44
+ * @param state EditorState 实例
45
+ * @param node 语法树节点
46
+ * @test
47
+ */
48
+ export const sliceDoc = (state, node) => state.sliceDoc(node.from, node.to);
39
49
  /**
40
50
  * Update the stack of opening (+) or closing (-) brackets
41
51
  * @param state
42
52
  * @param node 语法树节点
53
+ * @test
43
54
  */
44
55
  export const braceStackUpdate = (state, node) => {
45
- const brackets = state.sliceDoc(node.from, node.to);
56
+ const brackets = sliceDoc(state, node);
46
57
  return [brackets.split('{{').length - 1, 1 - brackets.split('}}').length];
47
58
  };
59
+ /**
60
+ * Find the current template name
61
+ * @param state
62
+ * @param node 语法树节点
63
+ * @test
64
+ */
65
+ export const findTemplateName = (state, node) => {
66
+ let stack = -1, { prevSibling } = node,
67
+ /** 可包含`_`、`:`等 */ page = '';
68
+ while (prevSibling) {
69
+ const { name } = prevSibling;
70
+ if (name.includes(tokens.templateBracket)) {
71
+ const [lbrace, rbrace] = braceStackUpdate(state, prevSibling);
72
+ stack += lbrace;
73
+ if (stack >= 0) {
74
+ break;
75
+ }
76
+ stack += rbrace;
77
+ }
78
+ else if (stack === -1 && name.includes(tokens.templateName)) {
79
+ page = sliceDoc(state, prevSibling) + page;
80
+ }
81
+ else if (page && !name.includes(tokens.comment)) {
82
+ prevSibling = null;
83
+ break;
84
+ }
85
+ ({ prevSibling } = prevSibling);
86
+ }
87
+ return prevSibling && page;
88
+ };
48
89
  /**
49
90
  * 判断节点是否包含指定类型
50
91
  * @param types 节点类型
51
92
  * @param names 指定类型
93
+ * @test
52
94
  */
53
95
  export const hasTag = (types, names) => (Array.isArray(names) ? names : [names]).some(name => types.has(name in tokens ? tokens[name] : name));
96
+ export const toConfigGetter = (configGetter, articlePath) => articlePath
97
+ ? async () => Object.assign(await (configGetter ?? wikiparse.getConfig)(), { articlePath })
98
+ : configGetter;
54
99
  /**
55
100
  * 获取字符串开头的空白字符
56
101
  * @param str 字符串
102
+ * @test
57
103
  */
58
104
  export const leadingSpaces = (str) => /^\s*/u.exec(str)[0];