@bhsd/codemirror-mediawiki 2.30.3 → 2.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mwConfig.js CHANGED
@@ -1,4 +1,4 @@
1
- // ../code-standard/dist/index.mjs
1
+ // ../browser/dist/index.js
2
2
  var CDN = "https://testingcf.jsdelivr.net";
3
3
  var getObject = (key) => JSON.parse(String(localStorage.getItem(key)));
4
4
  var setObject = (key, value) => {
@@ -10,7 +10,7 @@ var compareVersion = (version, baseVersion) => {
10
10
  return major > baseMajor || major === baseMajor && minor >= baseMinor;
11
11
  };
12
12
 
13
- // ../code-standard/dist/cm.mjs
13
+ // ../cm-util/dist/index.mjs
14
14
  var otherParserFunctions = /* @__PURE__ */ new Set(["msg", "raw", "subst", "safesubst"]);
15
15
  var getConfig = (magicWords, rule, flip) => {
16
16
  const words = magicWords.filter(rule);
package/dist/ref.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { hoverTooltip, EditorView } from '@codemirror/view';
2
2
  import { ensureSyntaxTree } from '@codemirror/language';
3
- import { getLSP } from '@bhsd/common';
3
+ import { getLSP } from '@bhsd/browser';
4
4
  import { getTag } from './matchTag';
5
5
  import { tokens } from './config';
6
6
  import { indexToPos, posToIndex } from './hover';
package/dist/signature.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { EditorView, showTooltip } from '@codemirror/view';
2
2
  import { StateField, StateEffect } from '@codemirror/state';
3
- import { getLSP } from '@bhsd/common';
3
+ import { getLSP } from '@bhsd/browser';
4
4
  import { indexToPos, createTooltipView } from './hover';
5
5
  const stateEffect = StateEffect.define(), field = StateField.define({
6
6
  create() {
package/dist/token.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { Tag } from '@lezer/highlight';
7
7
  import { tokens } from './config';
8
- import type { MwConfig as MwConfigBase } from '@bhsd/common/dist/cm';
8
+ import type { MwConfig as MwConfigBase } from '@bhsd/cm-util';
9
9
  import type { EditorState } from '@codemirror/state';
10
10
  import type { StreamParser, StringStream as StringStreamBase } from '@codemirror/language';
11
11
  import type { SyntaxNode } from '@lezer/common';
@@ -102,6 +102,7 @@ export declare class MediaWiki {
102
102
  readonly tags: string[];
103
103
  readonly hasVariants: boolean;
104
104
  readonly preRegex: [RegExp, RegExp];
105
+ readonly substRegex: RegExp;
105
106
  readonly autocompleteNamespaces: {
106
107
  0: string;
107
108
  6: string;
@@ -126,11 +127,11 @@ export declare class MediaWiki {
126
127
  * See makeLocalStyle() for how these tokens are used.
127
128
  */
128
129
  registerGroundTokens(): void;
129
- inChars({ length }: string, tag: TagName): Tokenizer;
130
- inStr(str: string, tag: TagName | false, errorTag?: TagName): Tokenizer;
130
+ inChars({ length }: string, tag: TagName): Tokenizer<string>;
131
+ inStr(str: string, tag: TagName | false, errorTag?: TagName): Tokenizer<string>;
131
132
  eatWikiText(style: string): Tokenizer;
132
133
  eatApostrophes(obj: Pick<State, 'bold' | 'italic'>): Tokenizer<string | false>;
133
- eatExternalLinkProtocol({ length }: string, free?: boolean): Tokenizer;
134
+ eatExternalLinkProtocol({ length }: string, free?: boolean): Tokenizer<string>;
134
135
  inExternalLink(text?: boolean): Tokenizer;
135
136
  eatFreeExternalLink(this: void, stream: StringStream, state: State): Style;
136
137
  inLink(file: boolean, section?: boolean): Tokenizer;
@@ -138,22 +139,23 @@ export declare class MediaWiki {
138
139
  toEatImageParameter(stream: StringStream, state: State): void;
139
140
  eatList(stream: StringStream, state: State): string;
140
141
  eatDoubleUnderscore(style: string, stream: StringStream, state: State): Style;
141
- get eatStartTable(): Tokenizer;
142
+ get eatStartTable(): Tokenizer<string>;
142
143
  inTableDefinition(tr?: boolean, quote?: string): Tokenizer;
143
144
  get inTable(): Tokenizer;
144
145
  inTableCell(style: string, needAttr?: boolean, firstLine?: boolean): Tokenizer;
145
146
  inSectionHeader(str: string): Tokenizer;
146
- get inComment(): Tokenizer;
147
+ get inComment(): Tokenizer<string>;
147
148
  eatExtTag(tagname: string, isCloseTag: boolean, state: State): string;
148
149
  eatHtmlTag(tagname: string, isCloseTag: boolean, state: State): string;
149
- eatTagName(name: string, isCloseTag?: boolean, isHtmlTag?: boolean): Tokenizer;
150
+ eatTagName(name: string, isCloseTag?: boolean, isHtmlTag?: boolean): Tokenizer<string>;
150
151
  inHtmlTagAttribute(name: string, quote?: string): Tokenizer;
151
- inExtTagAttribute(name: string, quote?: string, isLang?: boolean, isPage?: boolean): Tokenizer;
152
- eatExtTagArea(name: string): Tokenizer;
153
- inExtTokens(origString: string): Tokenizer;
152
+ inExtTagAttribute(name: string, quote?: string, isLang?: boolean, isPage?: boolean): Tokenizer<string>;
153
+ eatExtTagArea(name: string): Tokenizer<string>;
154
+ inExtTokens(origString: string): Tokenizer<string>;
154
155
  inVariable(pos?: number): Tokenizer;
155
156
  eatTransclusion(stream: StringStream, state: State): string | undefined;
156
- inParserFunctionName(invoke?: number, n?: number, ns?: number, subst?: boolean): Tokenizer;
157
+ inSubst(subst?: string): Tokenizer<string>;
158
+ inParserFunctionName(invoke?: number, n?: number, ns?: number): Tokenizer<string>;
157
159
  inTemplatePageName(haveEaten?: boolean, anchor?: boolean): Tokenizer;
158
160
  inParserFunctionArgument(module?: number, n?: number, ns?: number): Tokenizer;
159
161
  inTemplateArgument(expectName?: boolean, parserFunction?: boolean): Tokenizer;
package/dist/token.js CHANGED
@@ -38,8 +38,9 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
38
38
  done = true;
39
39
  };
40
40
  import { Tag } from '@lezer/highlight';
41
- import { decodeHTML, getRegex } from '@bhsd/common';
42
- import { otherParserFunctions } from '@bhsd/common/dist/cm';
41
+ import { getRegex } from '@bhsd/common';
42
+ import { decodeHTML } from '@bhsd/browser';
43
+ import { otherParserFunctions } from '@bhsd/cm-util';
43
44
  import { css } from '@codemirror/legacy-modes/mode/css';
44
45
  import { javascript, json } from '@codemirror/legacy-modes/mode/javascript';
45
46
  import { htmlTags, voidHtmlTags, selfClosingTags, tokenTable, tokens } from './config';
@@ -136,11 +137,11 @@ typeof document !== 'object' || str.startsWith('#') || [...decodeHTML(`&${str}`)
136
137
  /**
137
138
  * 更新内部 Tokenizer
138
139
  * @param state
139
- * @param tokenizer
140
+ * @param tokenizers
140
141
  */
141
- const chain = (state, tokenizer) => {
142
- state.stack.unshift(state.tokenize);
143
- state.tokenize = tokenizer;
142
+ const chain = (state, ...tokenizers) => {
143
+ state.stack.unshift(...tokenizers.slice(1), state.tokenize);
144
+ state.tokenize = tokenizers[0];
144
145
  };
145
146
  /**
146
147
  * 更新内部 Tokenizer
@@ -192,7 +193,7 @@ const needColon = (state) => {
192
193
  return Boolean(dt.n) && dt.html === 0 && !state.bold && !state.italic && cmpNesting(dt, state, true);
193
194
  };
194
195
  /**
195
- * 获取外部链接正则表达式
196
+ * 获取外部链接正则表达式,注意`&lt;`和`&gt;`大小写敏感
196
197
  * @param punctuations 标点符号
197
198
  */
198
199
  const getUrlRegex = (punctuations = '') => {
@@ -319,8 +320,6 @@ const peekSpace = (stream, sol) => {
319
320
  return Boolean(peek && !peek.trim());
320
321
  };
321
322
  const syntaxHighlight = new Set(['syntaxhighlight', 'source', 'pre']), pageFunctions = new Set([
322
- 'subst',
323
- 'safesubst',
324
323
  'raw',
325
324
  'msg',
326
325
  'filepath',
@@ -332,7 +331,7 @@ const syntaxHighlight = new Set(['syntaxhighlight', 'source', 'pre']), pageFunct
332
331
  'canonicalurle',
333
332
  'int',
334
333
  'msgnw',
335
- ]), 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("'{[<~_-")})+`, 'u'), tableDefinitionRegex = new RegExp(`^(?:[^&={<]|${lookahead('{<')})+`, 'iu'), extLinkChars = "[{'<-", tableDefinitionChars = '{<', tableCellChars = "'<~_{-", htmlAttrChars = '{/', freeRegex = [false, true].map(lpar => {
334
+ ]), 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 => {
336
335
  const punctuations = getPunctuations(lpar), source = getUrlRegex(punctuations);
337
336
  return new RegExp(`^(?:${source}|[${punctuations}]+(?=${source}))*`, 'u');
338
337
  }), indentedTableRegex = [false, true].map(isTemplate => new RegExp(String.raw `^:*\s*(?=\{${getPipe(isTemplate)})`, 'u')), tableRegex = [false, true]
@@ -369,6 +368,7 @@ let MediaWiki = (() => {
369
368
  let _eatExtTagArea_decorators;
370
369
  let _inExtTokens_decorators;
371
370
  let _inVariable_decorators;
371
+ let _inSubst_decorators;
372
372
  let _inParserFunctionName_decorators;
373
373
  let _inTemplatePageName_decorators;
374
374
  let _inParserFunctionArgument_decorators;
@@ -381,33 +381,40 @@ let MediaWiki = (() => {
381
381
  return class MediaWiki {
382
382
  static {
383
383
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
384
- _inChars_decorators = [getTokenizer];
385
- _inStr_decorators = [getTokenizer];
384
+ _inChars_decorators = [(getTokenizer)
385
+ // eslint-disable-next-line @typescript-eslint/class-methods-use-this
386
+ ];
387
+ _inStr_decorators = [(getTokenizer)
388
+ // eslint-disable-next-line @typescript-eslint/class-methods-use-this
389
+ ];
386
390
  _eatWikiText_decorators = [getTokenizer];
387
391
  _eatApostrophes_decorators = [getTokenizer];
388
- _eatExternalLinkProtocol_decorators = [getTokenizer];
392
+ _eatExternalLinkProtocol_decorators = [(getTokenizer)];
389
393
  _inExternalLink_decorators = [getTokenizer];
390
394
  _inLink_decorators = [getTokenizer];
391
395
  _inLinkText_decorators = [getTokenizer];
392
- _get_eatStartTable_decorators = [getTokenizer];
396
+ _get_eatStartTable_decorators = [(getTokenizer)];
393
397
  _inTableDefinition_decorators = [getTokenizer];
394
398
  _get_inTable_decorators = [getTokenizer];
395
399
  _inTableCell_decorators = [getTokenizer];
396
400
  _inSectionHeader_decorators = [getTokenizer];
397
- _get_inComment_decorators = [getTokenizer];
398
- _eatTagName_decorators = [getTokenizer];
401
+ _get_inComment_decorators = [(getTokenizer)];
402
+ _eatTagName_decorators = [(getTokenizer)];
399
403
  _inHtmlTagAttribute_decorators = [getTokenizer];
400
- _inExtTagAttribute_decorators = [getTokenizer];
401
- _eatExtTagArea_decorators = [getTokenizer];
402
- _inExtTokens_decorators = [getTokenizer];
404
+ _inExtTagAttribute_decorators = [(getTokenizer)];
405
+ _eatExtTagArea_decorators = [(getTokenizer)];
406
+ _inExtTokens_decorators = [(getTokenizer)
407
+ // eslint-disable-next-line @typescript-eslint/class-methods-use-this
408
+ ];
403
409
  _inVariable_decorators = [getTokenizer];
404
- _inParserFunctionName_decorators = [getTokenizer];
410
+ _inSubst_decorators = [(getTokenizer)];
411
+ _inParserFunctionName_decorators = [(getTokenizer)];
405
412
  _inTemplatePageName_decorators = [getTokenizer];
406
413
  _inParserFunctionArgument_decorators = [getTokenizer];
407
414
  _inTemplateArgument_decorators = [getTokenizer];
408
415
  _inConvert_decorators = [getTokenizer];
409
- _inPre_decorators = [getTokenizer];
410
- _inNested_decorators = [getTokenizer];
416
+ _inPre_decorators = [(getTokenizer)];
417
+ _inNested_decorators = [(getTokenizer)];
411
418
  _get_inInputbox_decorators = [(getTokenizer)];
412
419
  _inGallery_decorators = [getTokenizer];
413
420
  __esDecorate(this, null, _inChars_decorators, { kind: "method", name: "inChars", static: false, private: false, access: { has: obj => "inChars" in obj, get: obj => obj.inChars }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -430,6 +437,7 @@ let MediaWiki = (() => {
430
437
  __esDecorate(this, null, _eatExtTagArea_decorators, { kind: "method", name: "eatExtTagArea", static: false, private: false, access: { has: obj => "eatExtTagArea" in obj, get: obj => obj.eatExtTagArea }, metadata: _metadata }, null, _instanceExtraInitializers);
431
438
  __esDecorate(this, null, _inExtTokens_decorators, { kind: "method", name: "inExtTokens", static: false, private: false, access: { has: obj => "inExtTokens" in obj, get: obj => obj.inExtTokens }, metadata: _metadata }, null, _instanceExtraInitializers);
432
439
  __esDecorate(this, null, _inVariable_decorators, { kind: "method", name: "inVariable", static: false, private: false, access: { has: obj => "inVariable" in obj, get: obj => obj.inVariable }, metadata: _metadata }, null, _instanceExtraInitializers);
440
+ __esDecorate(this, null, _inSubst_decorators, { kind: "method", name: "inSubst", static: false, private: false, access: { has: obj => "inSubst" in obj, get: obj => obj.inSubst }, metadata: _metadata }, null, _instanceExtraInitializers);
433
441
  __esDecorate(this, null, _inParserFunctionName_decorators, { kind: "method", name: "inParserFunctionName", static: false, private: false, access: { has: obj => "inParserFunctionName" in obj, get: obj => obj.inParserFunctionName }, metadata: _metadata }, null, _instanceExtraInitializers);
434
442
  __esDecorate(this, null, _inTemplatePageName_decorators, { kind: "method", name: "inTemplatePageName", static: false, private: false, access: { has: obj => "inTemplatePageName" in obj, get: obj => obj.inTemplatePageName }, metadata: _metadata }, null, _instanceExtraInitializers);
435
443
  __esDecorate(this, null, _inParserFunctionArgument_decorators, { kind: "method", name: "inParserFunctionArgument", static: false, private: false, access: { has: obj => "inParserFunctionArgument" in obj, get: obj => obj.inParserFunctionArgument }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -443,7 +451,7 @@ let MediaWiki = (() => {
443
451
  }
444
452
  constructor(config) {
445
453
  __runInitializers(this, _instanceExtraInitializers);
446
- const { urlProtocols, permittedHtmlTags, implicitlyClosedHtmlTags, tags, nsid, variants, redirection = ['#REDIRECT'], img = {}, } = config;
454
+ const { urlProtocols, permittedHtmlTags, implicitlyClosedHtmlTags, tags, nsid, variants, functionSynonyms: [insensitive], redirection = ['#REDIRECT'], img = {}, } = config;
447
455
  this.config = config;
448
456
  this.tokenTable = { ...tokenTable };
449
457
  this.hiddenTable = {};
@@ -463,12 +471,15 @@ let MediaWiki = (() => {
463
471
  this.imgRegex = new RegExp(String.raw `^(?:${this.img.filter(word => word.endsWith('$1')).map(word => word.slice(0, -2))
464
472
  .join('|')}|(?:${this.img.filter(word => !word.endsWith('$1')).join('|')}|(?:\d+x?|\d*x\d+)\s*(?:px)?px)\s*(?=\||\]\]|$))`, 'u');
465
473
  this.tags = [...Object.keys(tags), 'includeonly', 'noinclude', 'onlyinclude'];
466
- this.convertRegex = new RegExp(String.raw `^(?:[^}|;&='{[<~_-]|\}(?!-)|=(?!>)|\[(?!\[|${urlProtocols})|${lookahead("'{<~_-")})+`, 'u');
467
- this.convertSemicolon = variants && new RegExp(String.raw `^;\s*(?=(?:[^;]*?=>\s*)?(?:${variants.join('|')})\s*:|(?:$|\}-))`, 'u');
474
+ this.convertRegex = new RegExp(String.raw `^(?:[^}|;&='{[<~_-]|\}(?!-)|=(?!>)|\[(?!\[|${urlProtocols})|${lookahead("'{<~_-")})+`, 'iu');
475
+ this.convertSemicolon = variants && new RegExp(String.raw `^;\s*(?=(?:[^;]*?=>\s*)?(?:${variants.join('|')})\s*:|(?:$|\}-))`, 'iu');
468
476
  this.convertLang = variants
469
- && new RegExp(String.raw `^(?:=>\s*)?(?:${variants.join('|')})\s*:`, 'u');
477
+ && new RegExp(String.raw `^(?:=>\s*)?(?:${variants.join('|')})\s*:`, 'iu');
470
478
  this.hasVariants = Boolean(variants?.length);
471
479
  this.preRegex = [false, true].map(begin => new RegExp(String.raw `^(?:[^<&-]|-${this.hasVariants ? String.raw `(?!\{)` : ''}|<(?!${begin ? '/' : ''}nowiki>))+`, 'iu'));
480
+ this.substRegex = new RegExp(String.raw `^\s*(?:(${Object.entries(insensitive).filter(([, v]) => substs.has(v))
481
+ .map(([k]) => k + (k.endsWith(':') ? '' : ':'))
482
+ .join('|')})\s*)?`, 'iu');
472
483
  this.autocompleteNamespaces = {
473
484
  0: '',
474
485
  6: 'File:',
@@ -908,7 +919,11 @@ let MediaWiki = (() => {
908
919
  const linkState = { bold: false, italic: false }, regex = linkTextRegex[file ? 1 : 0];
909
920
  return (stream, state) => {
910
921
  const tmpstyle = `${tokens[file ? 'fileText' : 'linkText']} ${linkState.bold ? tokens.strong : ''} ${linkState.italic ? tokens.em : ''} ${file && state.imgLink ? tokens.pageName : ''}`, { redirect, lbrack } = state, closing = stream.match(']]');
911
- if (closing || !file && stream.match('[[', false)) {
922
+ if (closing
923
+ || !file && stream.match('[[', false)
924
+ || !gallery
925
+ && state.stack[0]?.name === 'inTableCell'
926
+ && stream.sol() && stream.match(tableCellRegex, false)) {
912
927
  if (gallery) {
913
928
  return makeStyle(tmpstyle, state);
914
929
  }
@@ -1067,7 +1082,7 @@ let MediaWiki = (() => {
1067
1082
  inTableCell(style, needAttr = true, firstLine = true) {
1068
1083
  return (stream, state) => {
1069
1084
  if (stream.sol()) {
1070
- if (stream.match(/^\s*(?:[|!]|\{\{\s*![!)+-]?\s*\}\})/u, false)) {
1085
+ if (stream.match(tableCellRegex, false)) {
1071
1086
  state.tokenize = this.inTable;
1072
1087
  return '';
1073
1088
  }
@@ -1250,7 +1265,12 @@ let MediaWiki = (() => {
1250
1265
  return makeLocalStyle(tokens.extTagAttributeValue + (isPage ? ` ${tokens.pageName}` : ''), state);
1251
1266
  };
1252
1267
  return (stream, state) => {
1253
- if (stream.eat('>')) {
1268
+ if (stream.match('/>') || name === 'img' && stream.match('>')) {
1269
+ state.extMode = false;
1270
+ pop(state);
1271
+ return makeLocalTagStyle('extTagBracket', state);
1272
+ }
1273
+ else if (stream.eat('>')) {
1254
1274
  const { config: { tagModes } } = this;
1255
1275
  state.extName = name;
1256
1276
  state.extMode ||= name in tagModes
@@ -1261,11 +1281,6 @@ let MediaWiki = (() => {
1261
1281
  state.tokenize = this.eatExtTagArea(name);
1262
1282
  return makeLocalTagStyle('extTagBracket', state);
1263
1283
  }
1264
- else if (stream.match('/>')) {
1265
- state.extMode = false;
1266
- pop(state);
1267
- return makeLocalTagStyle('extTagBracket', state);
1268
- }
1269
1284
  else if (quote) { // 有引号的属性值
1270
1285
  if (stream.eat(quote[0])) {
1271
1286
  const [, remains] = quote;
@@ -1372,12 +1387,12 @@ let MediaWiki = (() => {
1372
1387
  };
1373
1388
  }
1374
1389
  eatTransclusion(stream, state) {
1375
- const [{ length }] = stream.match(/^\s*/u);
1390
+ const [{ length }, subst] = stream.match(this.substRegex), tokenizers = subst ? [this.inSubst(subst)] : [];
1376
1391
  // Parser function
1377
1392
  if (stream.peek() === '#') {
1378
1393
  stream.backUp(length);
1379
1394
  state.nExt++;
1380
- chain(state, this.inParserFunctionName());
1395
+ chain(state, ...tokenizers, this.inParserFunctionName());
1381
1396
  return makeLocalTagStyle('parserFunctionBracket', state);
1382
1397
  }
1383
1398
  // Check for parser function without '#'
@@ -1397,13 +1412,14 @@ let MediaWiki = (() => {
1397
1412
  && functionSynonyms[0][ffLower];
1398
1413
  if ((!delimiter || fullWidth || delimiter === ':' || delimiter === '}')
1399
1414
  && canonicalName
1415
+ && !substs.has(canonicalName)
1400
1416
  && (fullWidth || delimiter === ':' || !variableIDs || variableIDs.includes(canonicalName))
1401
1417
  && (!fullWidth && delimiter !== ':'
1402
1418
  || !functionHooks
1403
1419
  || functionHooks.includes(canonicalName) || otherParserFunctions.has(canonicalName))) {
1404
1420
  stream.backUp(length);
1405
1421
  state.nExt++;
1406
- chain(state, this.inParserFunctionName());
1422
+ chain(state, ...tokenizers, this.inParserFunctionName());
1407
1423
  return makeLocalTagStyle('parserFunctionBracket', state);
1408
1424
  }
1409
1425
  }
@@ -1413,10 +1429,24 @@ let MediaWiki = (() => {
1413
1429
  // Template
1414
1430
  stream.backUp(length);
1415
1431
  state.nTemplate++;
1416
- chain(state, this.inTemplatePageName());
1432
+ chain(state, ...tokenizers, this.inTemplatePageName());
1417
1433
  return makeLocalTagStyle('templateBracket', state);
1418
1434
  }
1419
- inParserFunctionName(invoke, n, ns, subst) {
1435
+ inSubst(subst) {
1436
+ return (stream, state) => {
1437
+ if (subst) {
1438
+ stream.eatSpace();
1439
+ stream.match(subst);
1440
+ stream.backUp(1);
1441
+ state.tokenize = this.inSubst();
1442
+ return makeLocalTagStyle('parserFunctionName', state);
1443
+ }
1444
+ stream.next();
1445
+ pop(state);
1446
+ return makeLocalTagStyle('parserFunctionDelimiter', state);
1447
+ };
1448
+ }
1449
+ inParserFunctionName(invoke, n, ns) {
1420
1450
  return (stream, state) => {
1421
1451
  const sol = stream.sol(), space = stream.eatSpace();
1422
1452
  if (stream.eol()) {
@@ -1438,9 +1468,7 @@ let MediaWiki = (() => {
1438
1468
  }
1439
1469
  const ch = stream.eat(/[::|]/u);
1440
1470
  if (ch) {
1441
- state.tokenize = subst && stream.match(/^\s*#/u, false)
1442
- ? this.inParserFunctionName()
1443
- : this.inParserFunctionArgument(invoke, n, ns);
1471
+ state.tokenize = this.inParserFunctionArgument(invoke, n, ns);
1444
1472
  return makeLocalTagStyle(space || ch === '|' ? 'error' : 'parserFunctionDelimiter', state);
1445
1473
  }
1446
1474
  const mt = stream.match(/^(?:[^::}{|<>[\]\s]|\s(?![::]))+/u);
@@ -1485,9 +1513,7 @@ let MediaWiki = (() => {
1485
1513
  namespace = 10;
1486
1514
  // no default
1487
1515
  }
1488
- state.tokenize = canonicalName === 'subst' || canonicalName === 'safesubst'
1489
- ? this.inParserFunctionName(invoke, n, ns, true)
1490
- : this.inParserFunctionName(Infinity, Infinity, namespace);
1516
+ state.tokenize = this.inParserFunctionName(Infinity, Infinity, namespace);
1491
1517
  }
1492
1518
  }
1493
1519
  return makeLocalTagStyle('parserFunctionName', state);
@@ -1637,8 +1663,8 @@ let MediaWiki = (() => {
1637
1663
  return makeLocalTagStyle('convertBracket', state);
1638
1664
  }
1639
1665
  else if (needFlag && stream.match(/^[;\sa-z-]*(?=\|)/iu)) {
1640
- chain(state, this.inConvert(style, false, true, plain));
1641
- state.tokenize = this.inStr('|', 'convertDelimiter');
1666
+ state.tokenize = this.inConvert(style, false, true, plain);
1667
+ chain(state, this.inStr('|', 'convertDelimiter'));
1642
1668
  return makeLocalTagStyle('convertFlag', state);
1643
1669
  }
1644
1670
  else if (stream.match(this.convertSemicolon)) {