@bhsd/codemirror-mediawiki 2.29.1 → 2.30.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/token.js CHANGED
@@ -3,6 +3,40 @@
3
3
  * @license GPL-2.0-or-later
4
4
  * @see https://gerrit.wikimedia.org/g/mediawiki/extensions/CodeMirror
5
5
  */
6
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
7
+ var useValue = arguments.length > 2;
8
+ for (var i = 0; i < initializers.length; i++) {
9
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
10
+ }
11
+ return useValue ? value : void 0;
12
+ };
13
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
14
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
15
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
16
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
17
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
18
+ var _, done = false;
19
+ for (var i = decorators.length - 1; i >= 0; i--) {
20
+ var context = {};
21
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
22
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
23
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
24
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
25
+ if (kind === "accessor") {
26
+ if (result === void 0) continue;
27
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
28
+ if (_ = accept(result.get)) descriptor.get = _;
29
+ if (_ = accept(result.set)) descriptor.set = _;
30
+ if (_ = accept(result.init)) initializers.unshift(_);
31
+ }
32
+ else if (_ = accept(result)) {
33
+ if (kind === "field") initializers.unshift(_);
34
+ else descriptor[key] = _;
35
+ }
36
+ }
37
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
38
+ done = true;
39
+ };
6
40
  import { Tag } from '@lezer/highlight';
7
41
  import { decodeHTML, getRegex } from '@bhsd/common';
8
42
  import { otherParserFunctions } from '@bhsd/common/dist/cm';
@@ -313,1576 +347,1639 @@ const syntaxHighlight = new Set(['syntaxhighlight', 'source', 'pre']), pageFunct
313
347
  ? /<\/onlyinclude(?:>|$)/u
314
348
  : new RegExp(String.raw `</${name}\s*(?:>|$)`, 'iu')), getNestedRegex = getRegex(tag => new RegExp(String.raw `^(?:[^<]|<(?!${tag}(?:[\s/>]|$)))+`, 'iu'));
315
349
  /** Adapted from the original CodeMirror 5 stream parser by Pavel Astakhov */
316
- export class MediaWiki {
317
- constructor(config) {
318
- const { urlProtocols, permittedHtmlTags, implicitlyClosedHtmlTags, tags, nsid, variants, redirection = ['#REDIRECT'], img = {}, } = config;
319
- this.config = config;
320
- this.tokenTable = { ...tokenTable };
321
- this.hiddenTable = {};
322
- this.permittedHtmlTags = new Set([
323
- ...htmlTags,
324
- ...permittedHtmlTags ?? [],
325
- ]);
326
- this.voidHtmlTags = new Set([
327
- ...voidHtmlTags,
328
- ...implicitlyClosedHtmlTags ?? [],
329
- ]);
330
- this.urlProtocols = new RegExp(String.raw `^(?:${urlProtocols})(?=[^\p{Zs}[\]<>"])`, 'iu');
331
- this.linkRegex = new RegExp(String.raw `^\[(?!${urlProtocols})\s*`, 'iu');
332
- this.fileRegex = new RegExp(String.raw `^(?:${Object.entries(nsid).filter(([, id]) => id === 6).map(([ns]) => ns).join('|')})\s*:`, 'iu');
333
- this.redirectRegex = new RegExp(String.raw `^\s*(?:${redirection.join('|')})(\s*:)?\s*(?=\[\[|$)`, 'iu');
334
- this.img = Object.keys(img).filter(word => !/\$1./u.test(word));
335
- this.imgRegex = new RegExp(String.raw `^(?:${this.img.filter(word => word.endsWith('$1')).map(word => word.slice(0, -2))
336
- .join('|')}|(?:${this.img.filter(word => !word.endsWith('$1')).join('|')}|(?:\d+x?|\d*x\d+)\s*(?:px)?px)\s*(?=\||\]\]|$))`, 'u');
337
- this.tags = [...Object.keys(tags), 'includeonly', 'noinclude', 'onlyinclude'];
338
- this.convertRegex = new RegExp(String.raw `^(?:[^}|;&='{[<~_-]|\}(?!-)|=(?!>)|\[(?!\[|${urlProtocols})|${lookahead("'{<~_-")})+`, 'u');
339
- this.convertSemicolon = variants && new RegExp(String.raw `^;\s*(?=(?:[^;]*?=>\s*)?(?:${variants.join('|')})\s*:|(?:$|\}-))`, 'u');
340
- this.convertLang = variants
341
- && new RegExp(String.raw `^(?:=>\s*)?(?:${variants.join('|')})\s*:`, 'u');
342
- this.hasVariants = Boolean(variants?.length);
343
- this.preRegex = [false, true].map(begin => new RegExp(String.raw `^(?:[^<&-]|-${this.hasVariants ? String.raw `(?!\{)` : ''}|<(?!${begin ? '/' : ''}nowiki>))+`, 'iu'));
344
- this.autocompleteNamespaces = {
345
- 0: '',
346
- 6: 'File:',
347
- 8: 'MediaWiki:',
348
- 10: 'Template:',
349
- 274: 'Widget:',
350
- 828: 'Module:',
351
- };
352
- this.registerGroundTokens();
353
- }
354
- /**
355
- * Dynamically register a token in CodeMirror.
356
- * This is solely for use by this.addTag() and CodeMirrorModeMediaWiki.makeLocalStyle().
357
- *
358
- * @param token
359
- * @param hidden Whether the token is not highlighted
360
- * @param parent
361
- */
362
- addToken(token, hidden = false, parent) {
363
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
364
- this[hidden ? 'hiddenTable' : 'tokenTable'][`mw-${token}`] ??= Tag.define(parent);
365
- }
366
- /**
367
- * Register the ground tokens. These aren't referenced directly in the StreamParser, nor do
368
- * they have a parent Tag, so we don't need them as constants like we do for other tokens.
369
- * See makeLocalStyle() for how these tokens are used.
370
- */
371
- registerGroundTokens() {
372
- const grounds = [
373
- 'ext',
374
- 'ext-link',
375
- 'ext2',
376
- 'ext2-link',
377
- 'ext3',
378
- 'ext3-link',
379
- 'link',
380
- 'template-ext',
381
- 'template-ext-link',
382
- 'template-ext2',
383
- 'template-ext2-link',
384
- 'template-ext3',
385
- 'template-ext3-link',
386
- 'template',
387
- 'template-link',
388
- 'template2-ext',
389
- 'template2-ext-link',
390
- 'template2-ext2',
391
- 'template2-ext2-link',
392
- 'template2-ext3',
393
- 'template2-ext3-link',
394
- 'template2',
395
- 'template2-link',
396
- 'template3-ext',
397
- 'template3-ext-link',
398
- 'template3-ext2',
399
- 'template3-ext2-link',
400
- 'template3-ext3',
401
- 'template3-ext3-link',
402
- 'template3',
403
- 'template3-link',
404
- ];
405
- for (const ground of grounds) {
406
- this.addToken(`${ground}-ground`);
350
+ let MediaWiki = (() => {
351
+ let _instanceExtraInitializers = [];
352
+ let _inChars_decorators;
353
+ let _inStr_decorators;
354
+ let _eatWikiText_decorators;
355
+ let _eatApostrophes_decorators;
356
+ let _eatExternalLinkProtocol_decorators;
357
+ let _inExternalLink_decorators;
358
+ let _inLink_decorators;
359
+ let _inLinkText_decorators;
360
+ let _get_eatStartTable_decorators;
361
+ let _inTableDefinition_decorators;
362
+ let _get_inTable_decorators;
363
+ let _inTableCell_decorators;
364
+ let _inSectionHeader_decorators;
365
+ let _get_inComment_decorators;
366
+ let _eatTagName_decorators;
367
+ let _inHtmlTagAttribute_decorators;
368
+ let _inExtTagAttribute_decorators;
369
+ let _eatExtTagArea_decorators;
370
+ let _inExtTokens_decorators;
371
+ let _inVariable_decorators;
372
+ let _inParserFunctionName_decorators;
373
+ let _inTemplatePageName_decorators;
374
+ let _inParserFunctionArgument_decorators;
375
+ let _inTemplateArgument_decorators;
376
+ let _inConvert_decorators;
377
+ let _inPre_decorators;
378
+ let _inNested_decorators;
379
+ let _get_inInputbox_decorators;
380
+ let _inGallery_decorators;
381
+ return class MediaWiki {
382
+ static {
383
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
384
+ _inChars_decorators = [getTokenizer];
385
+ _inStr_decorators = [getTokenizer];
386
+ _eatWikiText_decorators = [getTokenizer];
387
+ _eatApostrophes_decorators = [getTokenizer];
388
+ _eatExternalLinkProtocol_decorators = [getTokenizer];
389
+ _inExternalLink_decorators = [getTokenizer];
390
+ _inLink_decorators = [getTokenizer];
391
+ _inLinkText_decorators = [getTokenizer];
392
+ _get_eatStartTable_decorators = [getTokenizer];
393
+ _inTableDefinition_decorators = [getTokenizer];
394
+ _get_inTable_decorators = [getTokenizer];
395
+ _inTableCell_decorators = [getTokenizer];
396
+ _inSectionHeader_decorators = [getTokenizer];
397
+ _get_inComment_decorators = [getTokenizer];
398
+ _eatTagName_decorators = [getTokenizer];
399
+ _inHtmlTagAttribute_decorators = [getTokenizer];
400
+ _inExtTagAttribute_decorators = [getTokenizer];
401
+ _eatExtTagArea_decorators = [getTokenizer];
402
+ _inExtTokens_decorators = [getTokenizer];
403
+ _inVariable_decorators = [getTokenizer];
404
+ _inParserFunctionName_decorators = [getTokenizer];
405
+ _inTemplatePageName_decorators = [getTokenizer];
406
+ _inParserFunctionArgument_decorators = [getTokenizer];
407
+ _inTemplateArgument_decorators = [getTokenizer];
408
+ _inConvert_decorators = [getTokenizer];
409
+ _inPre_decorators = [getTokenizer];
410
+ _inNested_decorators = [getTokenizer];
411
+ _get_inInputbox_decorators = [(getTokenizer)];
412
+ _inGallery_decorators = [getTokenizer];
413
+ __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);
414
+ __esDecorate(this, null, _inStr_decorators, { kind: "method", name: "inStr", static: false, private: false, access: { has: obj => "inStr" in obj, get: obj => obj.inStr }, metadata: _metadata }, null, _instanceExtraInitializers);
415
+ __esDecorate(this, null, _eatWikiText_decorators, { kind: "method", name: "eatWikiText", static: false, private: false, access: { has: obj => "eatWikiText" in obj, get: obj => obj.eatWikiText }, metadata: _metadata }, null, _instanceExtraInitializers);
416
+ __esDecorate(this, null, _eatApostrophes_decorators, { kind: "method", name: "eatApostrophes", static: false, private: false, access: { has: obj => "eatApostrophes" in obj, get: obj => obj.eatApostrophes }, metadata: _metadata }, null, _instanceExtraInitializers);
417
+ __esDecorate(this, null, _eatExternalLinkProtocol_decorators, { kind: "method", name: "eatExternalLinkProtocol", static: false, private: false, access: { has: obj => "eatExternalLinkProtocol" in obj, get: obj => obj.eatExternalLinkProtocol }, metadata: _metadata }, null, _instanceExtraInitializers);
418
+ __esDecorate(this, null, _inExternalLink_decorators, { kind: "method", name: "inExternalLink", static: false, private: false, access: { has: obj => "inExternalLink" in obj, get: obj => obj.inExternalLink }, metadata: _metadata }, null, _instanceExtraInitializers);
419
+ __esDecorate(this, null, _inLink_decorators, { kind: "method", name: "inLink", static: false, private: false, access: { has: obj => "inLink" in obj, get: obj => obj.inLink }, metadata: _metadata }, null, _instanceExtraInitializers);
420
+ __esDecorate(this, null, _inLinkText_decorators, { kind: "method", name: "inLinkText", static: false, private: false, access: { has: obj => "inLinkText" in obj, get: obj => obj.inLinkText }, metadata: _metadata }, null, _instanceExtraInitializers);
421
+ __esDecorate(this, null, _get_eatStartTable_decorators, { kind: "getter", name: "eatStartTable", static: false, private: false, access: { has: obj => "eatStartTable" in obj, get: obj => obj.eatStartTable }, metadata: _metadata }, null, _instanceExtraInitializers);
422
+ __esDecorate(this, null, _inTableDefinition_decorators, { kind: "method", name: "inTableDefinition", static: false, private: false, access: { has: obj => "inTableDefinition" in obj, get: obj => obj.inTableDefinition }, metadata: _metadata }, null, _instanceExtraInitializers);
423
+ __esDecorate(this, null, _get_inTable_decorators, { kind: "getter", name: "inTable", static: false, private: false, access: { has: obj => "inTable" in obj, get: obj => obj.inTable }, metadata: _metadata }, null, _instanceExtraInitializers);
424
+ __esDecorate(this, null, _inTableCell_decorators, { kind: "method", name: "inTableCell", static: false, private: false, access: { has: obj => "inTableCell" in obj, get: obj => obj.inTableCell }, metadata: _metadata }, null, _instanceExtraInitializers);
425
+ __esDecorate(this, null, _inSectionHeader_decorators, { kind: "method", name: "inSectionHeader", static: false, private: false, access: { has: obj => "inSectionHeader" in obj, get: obj => obj.inSectionHeader }, metadata: _metadata }, null, _instanceExtraInitializers);
426
+ __esDecorate(this, null, _get_inComment_decorators, { kind: "getter", name: "inComment", static: false, private: false, access: { has: obj => "inComment" in obj, get: obj => obj.inComment }, metadata: _metadata }, null, _instanceExtraInitializers);
427
+ __esDecorate(this, null, _eatTagName_decorators, { kind: "method", name: "eatTagName", static: false, private: false, access: { has: obj => "eatTagName" in obj, get: obj => obj.eatTagName }, metadata: _metadata }, null, _instanceExtraInitializers);
428
+ __esDecorate(this, null, _inHtmlTagAttribute_decorators, { kind: "method", name: "inHtmlTagAttribute", static: false, private: false, access: { has: obj => "inHtmlTagAttribute" in obj, get: obj => obj.inHtmlTagAttribute }, metadata: _metadata }, null, _instanceExtraInitializers);
429
+ __esDecorate(this, null, _inExtTagAttribute_decorators, { kind: "method", name: "inExtTagAttribute", static: false, private: false, access: { has: obj => "inExtTagAttribute" in obj, get: obj => obj.inExtTagAttribute }, metadata: _metadata }, null, _instanceExtraInitializers);
430
+ __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
+ __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
+ __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);
433
+ __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
+ __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
+ __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);
436
+ __esDecorate(this, null, _inTemplateArgument_decorators, { kind: "method", name: "inTemplateArgument", static: false, private: false, access: { has: obj => "inTemplateArgument" in obj, get: obj => obj.inTemplateArgument }, metadata: _metadata }, null, _instanceExtraInitializers);
437
+ __esDecorate(this, null, _inConvert_decorators, { kind: "method", name: "inConvert", static: false, private: false, access: { has: obj => "inConvert" in obj, get: obj => obj.inConvert }, metadata: _metadata }, null, _instanceExtraInitializers);
438
+ __esDecorate(this, null, _inPre_decorators, { kind: "method", name: "inPre", static: false, private: false, access: { has: obj => "inPre" in obj, get: obj => obj.inPre }, metadata: _metadata }, null, _instanceExtraInitializers);
439
+ __esDecorate(this, null, _inNested_decorators, { kind: "method", name: "inNested", static: false, private: false, access: { has: obj => "inNested" in obj, get: obj => obj.inNested }, metadata: _metadata }, null, _instanceExtraInitializers);
440
+ __esDecorate(this, null, _get_inInputbox_decorators, { kind: "getter", name: "inInputbox", static: false, private: false, access: { has: obj => "inInputbox" in obj, get: obj => obj.inInputbox }, metadata: _metadata }, null, _instanceExtraInitializers);
441
+ __esDecorate(this, null, _inGallery_decorators, { kind: "method", name: "inGallery", static: false, private: false, access: { has: obj => "inGallery" in obj, get: obj => obj.inGallery }, metadata: _metadata }, null, _instanceExtraInitializers);
442
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
407
443
  }
408
- for (let i = 1; i < 7; i++) {
409
- this.addToken(`section--${i}`);
444
+ constructor(config) {
445
+ __runInitializers(this, _instanceExtraInitializers);
446
+ const { urlProtocols, permittedHtmlTags, implicitlyClosedHtmlTags, tags, nsid, variants, redirection = ['#REDIRECT'], img = {}, } = config;
447
+ this.config = config;
448
+ this.tokenTable = { ...tokenTable };
449
+ this.hiddenTable = {};
450
+ this.permittedHtmlTags = new Set([
451
+ ...htmlTags,
452
+ ...permittedHtmlTags ?? [],
453
+ ]);
454
+ this.voidHtmlTags = new Set([
455
+ ...voidHtmlTags,
456
+ ...implicitlyClosedHtmlTags ?? [],
457
+ ]);
458
+ this.urlProtocols = new RegExp(String.raw `^(?:${urlProtocols})(?=[^\p{Zs}[\]<>"])`, 'iu');
459
+ this.linkRegex = new RegExp(String.raw `^\[(?!${urlProtocols})\s*`, 'iu');
460
+ this.fileRegex = new RegExp(String.raw `^(?:${Object.entries(nsid).filter(([, id]) => id === 6).map(([ns]) => ns).join('|')})\s*:`, 'iu');
461
+ this.redirectRegex = new RegExp(String.raw `^\s*(?:${redirection.join('|')})(\s*:)?\s*(?=\[\[|$)`, 'iu');
462
+ this.img = Object.keys(img).filter(word => !/\$1./u.test(word));
463
+ this.imgRegex = new RegExp(String.raw `^(?:${this.img.filter(word => word.endsWith('$1')).map(word => word.slice(0, -2))
464
+ .join('|')}|(?:${this.img.filter(word => !word.endsWith('$1')).join('|')}|(?:\d+x?|\d*x\d+)\s*(?:px)?px)\s*(?=\||\]\]|$))`, 'u');
465
+ 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');
468
+ this.convertLang = variants
469
+ && new RegExp(String.raw `^(?:=>\s*)?(?:${variants.join('|')})\s*:`, 'u');
470
+ this.hasVariants = Boolean(variants?.length);
471
+ this.preRegex = [false, true].map(begin => new RegExp(String.raw `^(?:[^<&-]|-${this.hasVariants ? String.raw `(?!\{)` : ''}|<(?!${begin ? '/' : ''}nowiki>))+`, 'iu'));
472
+ this.autocompleteNamespaces = {
473
+ 0: '',
474
+ 6: 'File:',
475
+ 8: 'MediaWiki:',
476
+ 10: 'Template:',
477
+ 274: 'Widget:',
478
+ 828: 'Module:',
479
+ };
480
+ this.registerGroundTokens();
410
481
  }
411
- for (const tag of this.tags) {
412
- this.addToken(`tag-${tag}`, tag !== 'nowiki' && tag !== 'pre');
413
- this.addToken(`ext-${tag}`, true);
482
+ /**
483
+ * Dynamically register a token in CodeMirror.
484
+ * This is solely for use by this.addTag() and CodeMirrorModeMediaWiki.makeLocalStyle().
485
+ *
486
+ * @param token
487
+ * @param hidden Whether the token is not highlighted
488
+ * @param parent
489
+ */
490
+ addToken(token, hidden = false, parent) {
491
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
492
+ this[hidden ? 'hiddenTable' : 'tokenTable'][`mw-${token}`] ??= Tag.define(parent);
414
493
  }
415
- for (const tag of this.permittedHtmlTags) {
416
- this.addToken(`html-${tag}`, true);
417
- }
418
- for (const i in this.autocompleteNamespaces) {
419
- if (Number.isInteger(Number(i))) {
420
- this.addToken(`function-${i}`, true);
494
+ /**
495
+ * Register the ground tokens. These aren't referenced directly in the StreamParser, nor do
496
+ * they have a parent Tag, so we don't need them as constants like we do for other tokens.
497
+ * See makeLocalStyle() for how these tokens are used.
498
+ */
499
+ registerGroundTokens() {
500
+ const grounds = [
501
+ 'ext',
502
+ 'ext-link',
503
+ 'ext2',
504
+ 'ext2-link',
505
+ 'ext3',
506
+ 'ext3-link',
507
+ 'link',
508
+ 'template-ext',
509
+ 'template-ext-link',
510
+ 'template-ext2',
511
+ 'template-ext2-link',
512
+ 'template-ext3',
513
+ 'template-ext3-link',
514
+ 'template',
515
+ 'template-link',
516
+ 'template2-ext',
517
+ 'template2-ext-link',
518
+ 'template2-ext2',
519
+ 'template2-ext2-link',
520
+ 'template2-ext3',
521
+ 'template2-ext3-link',
522
+ 'template2',
523
+ 'template2-link',
524
+ 'template3-ext',
525
+ 'template3-ext-link',
526
+ 'template3-ext2',
527
+ 'template3-ext2-link',
528
+ 'template3-ext3',
529
+ 'template3-ext3-link',
530
+ 'template3',
531
+ 'template3-link',
532
+ ];
533
+ for (const ground of grounds) {
534
+ this.addToken(`${ground}-ground`);
535
+ }
536
+ for (let i = 1; i < 7; i++) {
537
+ this.addToken(`section--${i}`);
538
+ }
539
+ for (const tag of this.tags) {
540
+ this.addToken(`tag-${tag}`, tag !== 'nowiki' && tag !== 'pre');
541
+ this.addToken(`ext-${tag}`, true);
542
+ }
543
+ for (const tag of this.permittedHtmlTags) {
544
+ this.addToken(`html-${tag}`, true);
545
+ }
546
+ for (const i in this.autocompleteNamespaces) {
547
+ if (Number.isInteger(Number(i))) {
548
+ this.addToken(`function-${i}`, true);
549
+ }
421
550
  }
422
551
  }
423
- }
424
- @getTokenizer
425
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
426
- inChars({ length }, tag) {
427
- return (stream, state) => {
428
- stream.pos += length;
429
- pop(state);
430
- return makeLocalTagStyle(tag, state);
431
- };
432
- }
433
- @getTokenizer
434
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
435
- inStr(str, tag, errorTag = 'error') {
436
- return (stream, state) => {
437
- if (stream.match(str, Boolean(tag))) {
552
+ inChars({ length }, tag) {
553
+ return (stream, state) => {
554
+ stream.pos += length;
438
555
  pop(state);
439
- return tag ? makeLocalTagStyle(tag, state) : '';
440
- }
441
- else if (!stream.skipTo(str)) {
442
- stream.skipToEnd();
443
- }
444
- return makeLocalTagStyle(errorTag, state);
445
- };
446
- }
447
- @getTokenizer
448
- eatWikiText(style) {
449
- if (style in tokens) {
450
- style = tokens[style]; // eslint-disable-line no-param-reassign
556
+ return makeLocalTagStyle(tag, state);
557
+ };
451
558
  }
452
- const regex = /^(?:(?:RFC|PMID)[\p{Zs}\t]+\d+|ISBN[\p{Zs}\t]+(?:97[89][\p{Zs}\t-]?)?(?:\d[\p{Zs}\t-]?){9}[\dxX])\b/u;
453
- return (stream, state) => {
454
- let ch;
455
- if (stream.eol()) {
456
- return '';
559
+ inStr(str, tag, errorTag = 'error') {
560
+ return (stream, state) => {
561
+ if (stream.match(str, Boolean(tag))) {
562
+ pop(state);
563
+ return tag ? makeLocalTagStyle(tag, state) : '';
564
+ }
565
+ else if (!stream.skipTo(str)) {
566
+ stream.skipToEnd();
567
+ }
568
+ return makeLocalTagStyle(errorTag, state);
569
+ };
570
+ }
571
+ eatWikiText(style) {
572
+ if (style in tokens) {
573
+ style = tokens[style]; // eslint-disable-line no-param-reassign
457
574
  }
458
- else if (stream.sol()) {
459
- if (state.sof) {
460
- if (stream.match(/^\s+$/u)) {
461
- return '';
462
- }
463
- state.sof = false;
464
- const mt = stream.match(this.redirectRegex);
465
- if (mt) {
466
- state.redirect = { colon: !mt[1] };
467
- return tokens.redirect;
468
- }
575
+ const regex = /^(?:(?:RFC|PMID)[\p{Zs}\t]+\d+|ISBN[\p{Zs}\t]+(?:97[89][\p{Zs}\t-]?)?(?:\d[\p{Zs}\t-]?){9}[\dxX])\b/u;
576
+ return (stream, state) => {
577
+ let ch;
578
+ if (stream.eol()) {
579
+ return '';
469
580
  }
470
- else if (state.redirect) {
471
- if (stream.match(/^\s+(?=$|\[\[)/u)) {
472
- return '';
581
+ else if (stream.sol()) {
582
+ if (state.sof) {
583
+ if (stream.match(/^\s+$/u)) {
584
+ return '';
585
+ }
586
+ state.sof = false;
587
+ const mt = stream.match(this.redirectRegex);
588
+ if (mt) {
589
+ state.redirect = { colon: !mt[1] };
590
+ return tokens.redirect;
591
+ }
473
592
  }
474
- else if (state.redirect.colon && stream.match(/^\s*:\s*(?=$|\[\[)/u)) {
475
- state.redirect.colon = false;
476
- return tokens.redirect;
593
+ else if (state.redirect) {
594
+ if (stream.match(/^\s+(?=$|\[\[)/u)) {
595
+ return '';
596
+ }
597
+ else if (state.redirect.colon && stream.match(/^\s*:\s*(?=$|\[\[)/u)) {
598
+ state.redirect.colon = false;
599
+ return tokens.redirect;
600
+ }
601
+ state.redirect = false;
477
602
  }
478
- state.redirect = false;
603
+ ch = stream.next();
604
+ const isTemplate = ['inTemplateArgument', 'inParserFunctionArgument', 'inVariable']
605
+ .includes(state.tokenize.name);
606
+ switch (ch) {
607
+ case '#':
608
+ case ';':
609
+ case '*':
610
+ stream.backUp(1);
611
+ return this.eatList(stream, state);
612
+ case ':':
613
+ // Highlight indented tables :{|, bug T108454
614
+ if (stream.match(indentedTableRegex[isTemplate ? 1 : 0])) {
615
+ chain(state, this.eatStartTable);
616
+ return makeLocalTagStyle('list', state);
617
+ }
618
+ return this.eatList(stream, state);
619
+ case '=': {
620
+ const tmp = stream
621
+ .match(/^(={0,5})(.+?(=\1\s*)(?:<!--(?!.*-->\s*\S).*)?)$/u);
622
+ // Title
623
+ if (tmp) {
624
+ stream.backUp(tmp[2].length);
625
+ chain(state, this.inSectionHeader(tmp[3]));
626
+ return makeLocalStyle(`${tokens.sectionHeader} mw-section--${tmp[1].length + 1}`, state);
627
+ }
628
+ break;
629
+ }
630
+ case '{':
631
+ if (stream.match(tableRegex[isTemplate ? 1 : 0])) {
632
+ chain(state, this.inTableDefinition());
633
+ return makeLocalTagStyle('tableBracket', state);
634
+ }
635
+ break;
636
+ case '-':
637
+ if (stream.match(/^-{3,}/u)) {
638
+ return tokens.hr;
639
+ }
640
+ break;
641
+ default:
642
+ if (!ch.trim()) {
643
+ // Leading spaces is valid syntax for tables, bug T108454
644
+ const mt = stream.match(spacedTableRegex[isTemplate ? 1 : 0]);
645
+ if (mt) {
646
+ chain(state, this.eatStartTable);
647
+ return makeLocalStyle(mt[1] ? tokens.list : '', state);
648
+ }
649
+ else if (ch === ' '
650
+ && !/^ \s*(?=<!--)(?:\s|<!--(?:(?!-->).)*-->)+$/u.test(stream.string)) {
651
+ /** @todo indent-pre is sometimes suppressed */
652
+ return tokens.skipFormatting;
653
+ }
654
+ }
655
+ }
656
+ }
657
+ else {
658
+ ch = stream.next();
479
659
  }
480
- ch = stream.next();
481
- const isTemplate = ['inTemplateArgument', 'inParserFunctionArgument', 'inVariable']
482
- .includes(state.tokenize.name);
483
660
  switch (ch) {
484
- case '#':
485
- case ';':
486
- case '*':
487
- stream.backUp(1);
488
- return this.eatList(stream, state);
489
- case ':':
490
- // Highlight indented tables :{|, bug T108454
491
- if (stream.match(indentedTableRegex[isTemplate ? 1 : 0])) {
492
- chain(state, this.eatStartTable);
493
- return makeLocalTagStyle('list', state);
661
+ case '~':
662
+ if (stream.match(/^~{2,4}/u)) {
663
+ return tokens.signature;
664
+ }
665
+ break;
666
+ case '<': {
667
+ if (stream.match('!--')) { // comment
668
+ chain(state, this.inComment);
669
+ return makeLocalTagStyle('comment', state);
494
670
  }
495
- return this.eatList(stream, state);
496
- case '=': {
497
- const tmp = stream
498
- .match(/^(={0,5})(.+?(=\1\s*)(?:<!--(?!.*-->\s*\S).*)?)$/u);
499
- // Title
500
- if (tmp) {
501
- stream.backUp(tmp[2].length);
502
- chain(state, this.inSectionHeader(tmp[3]));
503
- return makeLocalStyle(`${tokens.sectionHeader} mw-section--${tmp[1].length + 1}`, state);
671
+ const isCloseTag = Boolean(stream.eat('/')), mt = stream.match(/^([a-z][^\s/>]*)>?/iu, false);
672
+ if (mt) {
673
+ const tagname = mt[1].toLowerCase();
674
+ if ((mt[0] === 'onlyinclude>' || tagname !== 'onlyinclude')
675
+ && state.data.tags.includes(tagname)) {
676
+ // Extension tag
677
+ return this.eatExtTag(tagname, isCloseTag, state);
678
+ }
679
+ else if (this.permittedHtmlTags.has(tagname)) {
680
+ // Html tag
681
+ return this.eatHtmlTag(tagname, isCloseTag, state);
682
+ }
504
683
  }
505
684
  break;
506
685
  }
507
- case '{':
508
- if (stream.match(tableRegex[isTemplate ? 1 : 0])) {
509
- chain(state, this.inTableDefinition());
510
- return makeLocalTagStyle('tableBracket', state);
686
+ case '{': {
687
+ // Can't be a variable when it starts with more than 3 brackets (T108450) or
688
+ // a single { followed by a template. E.g. {{{!}} starts a table (T292967).
689
+ if (stream.match(/^\{\{(?!\{|[^{}]*\}\}(?!\}))\s*/u)) {
690
+ state.nVar++;
691
+ chain(state, this.inVariable());
692
+ return makeLocalTagStyle('templateVariableBracket', state);
693
+ }
694
+ const mt = stream.match(/^\{(?!\{(?!\{))/u);
695
+ if (mt) {
696
+ return this.eatTransclusion(stream, state) ?? makeStyle(style, state);
511
697
  }
512
698
  break;
513
- case '-':
514
- if (stream.match(/^-{3,}/u)) {
515
- return tokens.hr;
699
+ }
700
+ case '_': {
701
+ const { pos } = stream;
702
+ stream.eatWhile('_');
703
+ switch (stream.pos - pos) {
704
+ case 0:
705
+ break;
706
+ case 1:
707
+ return this.eatDoubleUnderscore(style, stream, state);
708
+ default:
709
+ if (!stream.eol()) {
710
+ stream.backUp(2);
711
+ }
712
+ return makeStyle(style, state);
516
713
  }
517
714
  break;
518
- default:
519
- if (!ch.trim()) {
520
- // Leading spaces is valid syntax for tables, bug T108454
521
- const mt = stream.match(spacedTableRegex[isTemplate ? 1 : 0]);
522
- if (mt) {
523
- chain(state, this.eatStartTable);
524
- return makeLocalStyle(mt[1] ? tokens.list : '', state);
715
+ }
716
+ case '[':
717
+ // Link Example: [[ Foo | Bar ]]
718
+ if (stream.match(this.linkRegex)) {
719
+ const { redirect } = state;
720
+ if (redirect || /[^[\]|]/u.test(stream.peek() || '')) {
721
+ state.nLink++;
722
+ state.lbrack = undefined;
723
+ chain(state, this.inLink(!redirect && Boolean(stream.match(this.fileRegex, false))));
724
+ return makeLocalTagStyle('linkBracket', state);
525
725
  }
526
- else if (ch === ' '
527
- && !/^ \s*(?=<!--)(?:\s|<!--(?:(?!-->).)*-->)+$/u.test(stream.string)) {
528
- /** @todo indent-pre is sometimes suppressed */
529
- return tokens.skipFormatting;
726
+ else if (stream.match(']]')) {
727
+ return makeStyle(style, state);
530
728
  }
531
729
  }
532
- }
533
- }
534
- else {
535
- ch = stream.next();
536
- }
537
- switch (ch) {
538
- case '~':
539
- if (stream.match(/^~{2,4}/u)) {
540
- return tokens.signature;
541
- }
542
- break;
543
- case '<': {
544
- if (stream.match('!--')) { // comment
545
- chain(state, this.inComment);
546
- return makeLocalTagStyle('comment', state);
547
- }
548
- const isCloseTag = Boolean(stream.eat('/')), mt = stream.match(/^([a-z][^\s/>]*)>?/iu, false);
549
- if (mt) {
550
- const tagname = mt[1].toLowerCase();
551
- if ((mt[0] === 'onlyinclude>' || tagname !== 'onlyinclude')
552
- && state.data.tags.includes(tagname)) {
553
- // Extension tag
554
- return this.eatExtTag(tagname, isCloseTag, state);
730
+ else {
731
+ const mt = stream.match(this.urlProtocols, false);
732
+ if (mt) {
733
+ state.nExtLink++;
734
+ chain(state, this.eatExternalLinkProtocol(mt[0], false));
735
+ return makeLocalTagStyle('extLinkBracket', state);
736
+ }
555
737
  }
556
- else if (this.permittedHtmlTags.has(tagname)) {
557
- // Html tag
558
- return this.eatHtmlTag(tagname, isCloseTag, state);
738
+ break;
739
+ case "'": {
740
+ const result = this.eatApostrophes(state)(stream, state);
741
+ if (result) {
742
+ return result;
559
743
  }
744
+ break;
560
745
  }
561
- break;
562
- }
563
- case '{': {
564
- // Can't be a variable when it starts with more than 3 brackets (T108450) or
565
- // a single { followed by a template. E.g. {{{!}} starts a table (T292967).
566
- if (stream.match(/^\{\{(?!\{|[^{}]*\}\}(?!\}))\s*/u)) {
567
- state.nVar++;
568
- chain(state, this.inVariable());
569
- return makeLocalTagStyle('templateVariableBracket', state);
570
- }
571
- const mt = stream.match(/^\{(?!\{(?!\{))/u);
572
- if (mt) {
573
- return this.eatTransclusion(stream, state) ?? makeStyle(style, state);
574
- }
575
- break;
576
- }
577
- case '_': {
578
- const { pos } = stream;
579
- stream.eatWhile('_');
580
- switch (stream.pos - pos) {
581
- case 0:
582
- break;
583
- case 1:
584
- return this.eatDoubleUnderscore(style, stream, state);
585
- default:
586
- if (!stream.eol()) {
587
- stream.backUp(2);
588
- }
589
- return makeStyle(style, state);
590
- }
591
- break;
746
+ case ':':
747
+ if (needColon(state)) {
748
+ state.dt.n--;
749
+ return makeLocalTagStyle('list', state);
750
+ }
751
+ break;
752
+ case '&':
753
+ return makeStyle(this.eatEntity(stream, style), state);
754
+ case '-':
755
+ if (this.config.variants?.length && stream.match(/^\{(?!\{)\s*/u)) {
756
+ chain(state, this.inConvert(style, true));
757
+ return makeLocalTagStyle('convertBracket', state);
758
+ }
759
+ // no default
592
760
  }
593
- case '[':
594
- // Link Example: [[ Foo | Bar ]]
595
- if (stream.match(this.linkRegex)) {
596
- const { redirect } = state;
597
- if (redirect || /[^[\]|]/u.test(stream.peek() || '')) {
598
- state.nLink++;
599
- state.lbrack = undefined;
600
- chain(state, this.inLink(!redirect && Boolean(stream.match(this.fileRegex, false))));
601
- return makeLocalTagStyle('linkBracket', state);
761
+ if (state.stack.length === 0) {
762
+ if (ch !== '_') {
763
+ // highlight free external links, bug T108448
764
+ if (/[\p{L}\p{N}]/u.test(ch)) {
765
+ stream.backUp(1);
602
766
  }
603
- else if (stream.match(']]')) {
604
- return makeStyle(style, state);
767
+ else {
768
+ stream.eatWhile(/[^\p{L}\p{N}_&'{[<~:-]/u);
605
769
  }
606
- }
607
- else {
608
770
  const mt = stream.match(this.urlProtocols, false);
609
771
  if (mt) {
610
- state.nExtLink++;
611
- chain(state, this.eatExternalLinkProtocol(mt[0], false));
612
- return makeLocalTagStyle('extLinkBracket', state);
772
+ chain(state, this.eatExternalLinkProtocol(mt[0]));
773
+ return makeStyle(style, state);
774
+ }
775
+ const mtMagic = stream.match(regex, false);
776
+ if (mtMagic) {
777
+ chain(state, this.inChars(mtMagic[0], 'magicLink'));
778
+ return makeStyle(style, state);
613
779
  }
614
780
  }
615
- break;
616
- case "'": {
617
- const result = this.eatApostrophes(state)(stream, state);
618
- if (result) {
619
- return result;
620
- }
621
- break;
781
+ stream.eatWhile(/[\p{L}\p{N}]/u);
622
782
  }
623
- case ':':
624
- if (needColon(state)) {
625
- state.dt.n--;
626
- return makeLocalTagStyle('list', state);
627
- }
628
- break;
629
- case '&':
630
- return makeStyle(this.eatEntity(stream, style), state);
631
- case '-':
632
- if (this.config.variants?.length && stream.match(/^\{(?!\{)\s*/u)) {
633
- chain(state, this.inConvert(style, true));
634
- return makeLocalTagStyle('convertBracket', state);
635
- }
636
- // no default
637
- }
638
- if (state.stack.length === 0) {
639
- if (ch !== '_') {
640
- // highlight free external links, bug T108448
641
- if (/[\p{L}\p{N}]/u.test(ch)) {
642
- stream.backUp(1);
643
- }
644
- else {
645
- stream.eatWhile(/[^\p{L}\p{N}_&'{[<~:-]/u);
646
- }
647
- const mt = stream.match(this.urlProtocols, false);
648
- if (mt) {
649
- chain(state, this.eatExternalLinkProtocol(mt[0]));
650
- return makeStyle(style, state);
651
- }
652
- const mtMagic = stream.match(regex, false);
653
- if (mtMagic) {
654
- chain(state, this.inChars(mtMagic[0], 'magicLink'));
655
- return makeStyle(style, state);
783
+ return makeStyle(style, state);
784
+ };
785
+ }
786
+ eatApostrophes(obj) {
787
+ return (stream, state) => {
788
+ // skip the irrelevant apostrophes ( >5 or =4 )
789
+ if (stream.match(/^'*(?='{5})/u) || stream.match(/^'''(?!')/u, false)) {
790
+ return false;
791
+ }
792
+ else if (stream.match("''''")) { // bold italic
793
+ obj.bold = !obj.bold;
794
+ obj.italic = !obj.italic;
795
+ return makeLocalTagStyle('apostrophes', state);
796
+ }
797
+ else if (stream.match("''")) { // bold
798
+ if (obj === state && state.data.firstSingleLetterWord === null) {
799
+ prepareItalicForCorrection(stream, state);
656
800
  }
801
+ obj.bold = !obj.bold;
802
+ return makeLocalTagStyle('apostrophes', state);
657
803
  }
658
- stream.eatWhile(/[\p{L}\p{N}]/u);
659
- }
660
- return makeStyle(style, state);
661
- };
662
- }
663
- @getTokenizer
664
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
665
- eatApostrophes(obj) {
666
- return (stream, state) => {
667
- // skip the irrelevant apostrophes ( >5 or =4 )
668
- if (stream.match(/^'*(?='{5})/u) || stream.match(/^'''(?!')/u, false)) {
669
- return false;
670
- }
671
- else if (stream.match("''''")) { // bold italic
672
- obj.bold = !obj.bold;
673
- obj.italic = !obj.italic;
674
- return makeLocalTagStyle('apostrophes', state);
675
- }
676
- else if (stream.match("''")) { // bold
677
- if (obj === state && state.data.firstSingleLetterWord === null) {
678
- prepareItalicForCorrection(stream, state);
804
+ else if (stream.eat("'")) { // italic
805
+ obj.italic = !obj.italic;
806
+ return makeLocalTagStyle('apostrophes', state);
679
807
  }
680
- obj.bold = !obj.bold;
681
- return makeLocalTagStyle('apostrophes', state);
682
- }
683
- else if (stream.eat("'")) { // italic
684
- obj.italic = !obj.italic;
685
- return makeLocalTagStyle('apostrophes', state);
686
- }
687
- return false;
688
- };
689
- }
690
- @getTokenizer
691
- eatExternalLinkProtocol({ length }, free = true) {
692
- return (stream, state) => {
693
- stream.pos += length;
694
- state.tokenize = free ? this.eatFreeExternalLink : this.inExternalLink();
695
- return makeLocalTagStyle(free ? 'freeExtLinkProtocol' : 'extLinkProtocol', state);
696
- };
697
- }
698
- @getTokenizer
699
- inExternalLink(text) {
700
- return (stream, state) => {
701
- const t = state.stack[0], equal = getEqual(t), isNested = ['inTemplateArgument', 'inParserFunctionArgument', 'inVariable', 'inTableCell']
702
- .includes(t.name), pipe = (isNested ? '|' : '') + equal, peek = stream.peek();
703
- if (stream.sol()
704
- || stream.match(/^\p{Zs}*\]/u)
705
- || isNested && peek === '|'
706
- || equal && peek === '=') {
707
- pop(state);
708
- return makeLocalTagStyle('extLinkBracket', state, 'nExtLink');
709
- }
710
- else if (text) {
711
- return stream.match(getExtLinkTextRegex(pipe))
712
- ? makeTagStyle('extLinkText', state)
713
- : this.eatWikiText('extLinkText')(stream, state);
714
- }
715
- else if (stream.match(getExtLinkRegex(pipe))) {
716
- return makeLocalTagStyle('extLink', state);
717
- }
718
- state.tokenize = this.inExternalLink(true);
719
- return '';
720
- };
721
- }
722
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
723
- eatFreeExternalLink(stream, state) {
724
- const mt = stream.match(freeRegex[0]);
725
- if (!stream.eol() && mt[0].includes('(') && getPunctuations().includes(stream.peek())) {
726
- stream.match(freeRegex[1]);
808
+ return false;
809
+ };
727
810
  }
728
- pop(state);
729
- return makeTagStyle('freeExtLink', state);
730
- }
731
- @getTokenizer
732
- inLink(file, section) {
733
- const style = section ? tokens[file ? 'error' : 'linkToSection'] : `${tokens.linkPageName} ${tokens.pageName}`, re = section
734
- ? /^(?:[^|<[\]{}]|<(?!!--|\/?[a-z]))+/iu
735
- : /^(?:&#(?:\d+|x[a-f\d]+);|[^#|<>[\]{}%]|%(?!3[ce]|[57][bd]))+/iu;
736
- let lt;
737
- return (stream, state) => {
738
- if (stream.sol()
739
- || lt && stream.pos > lt
740
- || stream.match(/^\s*\]\]/u)
741
- || stream.match(/^\[\[/u, false)) {
742
- state.redirect = false;
743
- state.lbrack = false;
744
- pop(state);
745
- return makeLocalTagStyle('linkBracket', state, 'nLink');
746
- }
747
- lt = undefined;
748
- const space = stream.eatSpace(), { redirect } = state;
749
- if (!section && stream.match(/^#\s*/u)) {
750
- state.tokenize = this.inLink(file, true);
751
- return makeTagStyle(file ? 'error' : 'linkToSection', state);
752
- }
753
- else if (stream.match(/^\|\s*/u)) {
754
- state.tokenize = this.inLinkText(file);
755
- let s = redirect ? 'error' : 'linkDelimiter';
756
- if (file) {
757
- s = 'fileDelimiter';
758
- this.toEatImageParameter(stream, state);
811
+ eatExternalLinkProtocol({ length }, free = true) {
812
+ return (stream, state) => {
813
+ stream.pos += length;
814
+ state.tokenize = free ? this.eatFreeExternalLink : this.inExternalLink();
815
+ return makeLocalTagStyle(free ? 'freeExtLinkProtocol' : 'extLinkProtocol', state);
816
+ };
817
+ }
818
+ inExternalLink(text) {
819
+ return (stream, state) => {
820
+ const t = state.stack[0], equal = getEqual(t), isNested = ['inTemplateArgument', 'inParserFunctionArgument', 'inVariable', 'inTableCell']
821
+ .includes(t.name), pipe = (isNested ? '|' : '') + equal, peek = stream.peek();
822
+ if (stream.sol()
823
+ || stream.match(/^\p{Zs}*\]/u)
824
+ || isNested && peek === '|'
825
+ || equal && peek === '=') {
826
+ pop(state);
827
+ return makeLocalTagStyle('extLinkBracket', state, 'nExtLink');
759
828
  }
760
- return makeLocalTagStyle(s, state);
761
- }
762
- let regex;
763
- if (redirect) {
764
- [regex] = linkErrorRegex;
765
- }
766
- else if (section) {
767
- [, regex] = linkErrorRegex;
768
- }
769
- else {
770
- [, , regex] = linkErrorRegex;
771
- }
772
- if (stream.match(regex)) {
773
- return makeTagStyle('error', state);
774
- }
775
- else if (redirect) {
776
- stream.match(/^(?:[^|<>[\]{}%]|%(?!3[ce]|[57][bd]))+/iu);
777
- return makeStyle(style, state);
778
- }
779
- else if (stream.match(re) || space) {
780
- return makeStyle(style, state);
781
- }
782
- else if (stream.match(/^<(?!(?:includeonly|noinclude)(?:\/?>|\s|$))[/a-z]/iu, false)
783
- && !stream.match(/^<onlyinclude>/u, false)) {
784
- lt = stream.pos + 1;
785
- }
786
- return this.eatWikiText(section ? style : 'error')(stream, state);
787
- };
788
- }
789
- @getTokenizer
790
- inLinkText(file, gallery) {
791
- const linkState = { bold: false, italic: false }, regex = linkTextRegex[file ? 1 : 0];
792
- return (stream, state) => {
793
- 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(']]');
794
- if (closing || !file && stream.match('[[', false)) {
795
- if (gallery) {
796
- return makeStyle(tmpstyle, state);
797
- }
798
- else if (closing && !redirect && lbrack && stream.peek() === ']') {
799
- stream.backUp(1);
800
- state.lbrack = false;
801
- return makeStyle(tmpstyle, state);
829
+ else if (text) {
830
+ return stream.match(getExtLinkTextRegex(pipe))
831
+ ? makeTagStyle('extLinkText', state)
832
+ : this.eatWikiText('extLinkText')(stream, state);
802
833
  }
803
- state.redirect = false;
804
- state.lbrack = false;
805
- pop(state);
806
- return makeLocalTagStyle('linkBracket', state, 'nLink');
807
- }
808
- else if (redirect) {
809
- if (!stream.skipTo(']]')) {
810
- stream.skipToEnd();
834
+ else if (stream.match(getExtLinkRegex(pipe))) {
835
+ return makeLocalTagStyle('extLink', state);
811
836
  }
812
- return makeLocalTagStyle('error', state);
813
- }
814
- else if (file && stream.match(/^\|\s*/u)) {
815
- this.toEatImageParameter(stream, state);
816
- return makeLocalTagStyle('fileDelimiter', state);
817
- }
818
- else if (stream.match(/^'(?=')/u)) {
819
- return this.eatApostrophes(linkState)(stream, state) || makeStyle(tmpstyle, state);
820
- }
821
- else if (file && isSolSyntax(stream, true, true)
822
- || stream.sol() && stream.match('{|', false)) {
823
- return this.eatWikiText(tmpstyle)(stream, state);
824
- }
825
- const mt = stream.match(regex);
826
- if (lbrack === undefined && mt?.[0].includes('[')) {
827
- state.lbrack = true;
828
- }
829
- return mt ? makeStyle(tmpstyle, state) : this.eatWikiText(tmpstyle)(stream, state);
830
- };
831
- }
832
- toEatImageParameter(stream, state) {
833
- state.imgLink = false;
834
- const mt = stream.match(this.imgRegex, false);
835
- if (mt) {
836
- if (this.config.img?.[`${mt[0]}$1`] === 'img_link') {
837
- state.imgLink = true;
838
- }
839
- chain(state, this.inChars(mt[0], 'imageParameter'));
840
- }
841
- }
842
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
843
- eatList(stream, state) {
844
- const mt = stream.match(/^[*#;:]*/u), { dt } = state;
845
- if (mt[0].includes(';')) {
846
- dt.n = mt[0].split(';').length - 1;
847
- copyNesting(dt, state);
837
+ state.tokenize = this.inExternalLink(true);
838
+ return '';
839
+ };
848
840
  }
849
- return makeLocalTagStyle('list', state);
850
- }
851
- eatDoubleUnderscore(style, stream, state) {
852
- const { config: { doubleUnderscore } } = this, name = stream.match(/^[\p{L}\p{N}_]+?__/u);
853
- if (name) {
854
- if (Object.prototype.hasOwnProperty.call(doubleUnderscore[0], `__${name[0].toLowerCase()}`)
855
- || Object.prototype.hasOwnProperty.call(doubleUnderscore[1], `__${name[0]}`)) {
856
- return tokens.doubleUnderscore;
857
- }
858
- else if (!stream.eol()) {
859
- // Two underscore symbols at the end can be the beginning of another double underscored Magic Word
860
- stream.backUp(2);
841
+ // eslint-disable-next-line @typescript-eslint/class-methods-use-this
842
+ eatFreeExternalLink(stream, state) {
843
+ const mt = stream.match(freeRegex[0]);
844
+ if (!stream.eol() && mt[0].includes('(') && getPunctuations().includes(stream.peek())) {
845
+ stream.match(freeRegex[1]);
861
846
  }
847
+ pop(state);
848
+ return makeTagStyle('freeExtLink', state);
862
849
  }
863
- return makeStyle(style, state);
864
- }
865
- @getTokenizer
866
- get eatStartTable() {
867
- return (stream, state) => {
868
- stream.match(/^(?:\{\||\{\{(?:\{\s*|\s*\()!\s*\}\})\s*/u);
869
- state.tokenize = this.inTableDefinition();
870
- return makeLocalTagStyle('tableBracket', state);
871
- };
872
- }
873
- @getTokenizer
874
- inTableDefinition(tr, quote) {
875
- const style = quote === undefined
876
- ? `${tokens.tableDefinition} mw-html-${tr ? 'tr' : 'table'}`
877
- : tokens.tableDefinitionValue;
878
- return (stream, state) => {
879
- if (stream.sol()) {
880
- state.tokenize = this.inTable;
881
- return '';
882
- }
883
- const t = state.stack[0], equal = getEqual(t);
884
- if (equal && stream.peek() === '=') {
885
- pop(state);
886
- return '';
887
- }
888
- else if (stream.match(/^(?:&|\{\{|<(?:!--|\/?[a-z]))/iu, false)) {
889
- return this.eatWikiText(style)(stream, state);
890
- }
891
- else if (quote) { // 有引号的属性值
892
- if (stream.eat(quote[0])) {
893
- state.tokenize = this.inTableDefinition(tr, quote[1]);
850
+ inLink(file, section) {
851
+ const style = section ? tokens[file ? 'error' : 'linkToSection'] : `${tokens.linkPageName} ${tokens.pageName}`, re = section
852
+ ? /^(?:[^|<[\]{}]|<(?!!--|\/?[a-z]))+/iu
853
+ : /^(?:&#(?:\d+|x[a-f\d]+);|[^#|<>[\]{}%]|%(?!3[ce]|[57][bd]))+/iu;
854
+ let lt;
855
+ return (stream, state) => {
856
+ if (stream.sol()
857
+ || lt && stream.pos > lt
858
+ || stream.match(/^\s*\]\]/u)
859
+ || stream.match(/^\[\[/u, false)) {
860
+ state.redirect = false;
861
+ state.lbrack = false;
862
+ pop(state);
863
+ return makeLocalTagStyle('linkBracket', state, 'nLink');
864
+ }
865
+ lt = undefined;
866
+ const space = stream.eatSpace(), { redirect } = state;
867
+ if (!section && stream.match(/^#\s*/u)) {
868
+ state.tokenize = this.inLink(file, true);
869
+ return makeTagStyle(file ? 'error' : 'linkToSection', state);
870
+ }
871
+ else if (stream.match(/^\|\s*/u)) {
872
+ state.tokenize = this.inLinkText(file);
873
+ let s = redirect ? 'error' : 'linkDelimiter';
874
+ if (file) {
875
+ s = 'fileDelimiter';
876
+ this.toEatImageParameter(stream, state);
877
+ }
878
+ return makeLocalTagStyle(s, state);
879
+ }
880
+ let regex;
881
+ if (redirect) {
882
+ [regex] = linkErrorRegex;
883
+ }
884
+ else if (section) {
885
+ [, regex] = linkErrorRegex;
894
886
  }
895
887
  else {
896
- stream.match(getTableDefinitionRegex(equal + quote[0]));
888
+ [, , regex] = linkErrorRegex;
897
889
  }
898
- return makeLocalStyle(style, state);
899
- }
900
- else if (quote === '') { // 无引号的属性值
901
- if (peekSpace(stream)) {
902
- state.tokenize = this.inTableDefinition(tr);
903
- return '';
890
+ if (stream.match(regex)) {
891
+ return makeTagStyle('error', state);
904
892
  }
905
- stream.match(tableDefinitionValueRegex[equal ? 1 : 0]);
906
- return makeLocalStyle(style, state);
907
- }
908
- else if (stream.match(/^=\s*/u)) {
909
- state.tokenize = this.inTableDefinition(tr, getQuote(stream));
910
- return makeLocalStyle(style, state);
911
- }
912
- stream.match(tableDefinitionRegex);
913
- return makeLocalStyle(style, state);
914
- };
915
- }
916
- @getTokenizer
917
- get inTable() {
918
- return (stream, state) => {
919
- if (stream.sol()) {
920
- stream.eatSpace();
921
- const mt = stream.match(/^(?:\||\{\{\s*!([!)+-])?\s*\}\})/u);
922
- if (mt) {
923
- if (mt[1] === '-' || !mt[1] && stream.eat('-')) {
924
- stream.match(/^-*\s*/u);
925
- state.tokenize = this.inTableDefinition(true);
926
- return makeLocalTagStyle('tableDelimiter', state);
893
+ else if (redirect) {
894
+ stream.match(/^(?:[^|<>[\]{}%]|%(?!3[ce]|[57][bd]))+/iu);
895
+ return makeStyle(style, state);
896
+ }
897
+ else if (stream.match(re) || space) {
898
+ return makeStyle(style, state);
899
+ }
900
+ else if (stream.match(/^<(?!(?:includeonly|noinclude)(?:\/?>|\s|$))[/a-z]/iu, false)
901
+ && !stream.match(/^<onlyinclude>/u, false)) {
902
+ lt = stream.pos + 1;
903
+ }
904
+ return this.eatWikiText(section ? style : 'error')(stream, state);
905
+ };
906
+ }
907
+ inLinkText(file, gallery) {
908
+ const linkState = { bold: false, italic: false }, regex = linkTextRegex[file ? 1 : 0];
909
+ return (stream, state) => {
910
+ 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)) {
912
+ if (gallery) {
913
+ return makeStyle(tmpstyle, state);
927
914
  }
928
- else if (mt[1] === '+' || !mt[1] && stream.match(/^\+\s*/u)) {
929
- state.tokenize = this.inTableCell(tokens.tableCaption);
930
- return makeLocalTagStyle('tableDelimiter', state);
915
+ else if (closing && !redirect && lbrack && stream.peek() === ']') {
916
+ stream.backUp(1);
917
+ state.lbrack = false;
918
+ return makeStyle(tmpstyle, state);
931
919
  }
932
- else if (mt[1] === ')' || !mt[1] && stream.eat('}')) {
933
- pop(state);
934
- return makeLocalTagStyle('tableBracket', state);
920
+ state.redirect = false;
921
+ state.lbrack = false;
922
+ pop(state);
923
+ return makeLocalTagStyle('linkBracket', state, 'nLink');
924
+ }
925
+ else if (redirect) {
926
+ if (!stream.skipTo(']]')) {
927
+ stream.skipToEnd();
935
928
  }
936
- stream.eatSpace();
937
- state.tokenize = this.inTableCell(tokens.tableTd, mt[1] !== '!');
938
- return makeLocalTagStyle('tableDelimiter', state);
929
+ return makeLocalTagStyle('error', state);
930
+ }
931
+ else if (file && stream.match(/^\|\s*/u)) {
932
+ this.toEatImageParameter(stream, state);
933
+ return makeLocalTagStyle('fileDelimiter', state);
934
+ }
935
+ else if (stream.match(/^'(?=')/u)) {
936
+ return this.eatApostrophes(linkState)(stream, state) || makeStyle(tmpstyle, state);
939
937
  }
940
- else if (stream.match(/^!\s*/u)) {
941
- state.tokenize = this.inTableCell(tokens.tableTh);
942
- return makeLocalTagStyle('tableDelimiter', state);
938
+ else if (file && isSolSyntax(stream, true, true)
939
+ || stream.sol() && stream.match('{|', false)) {
940
+ return this.eatWikiText(tmpstyle)(stream, state);
943
941
  }
944
- else if (isSolSyntax(stream, true)) {
945
- return this.eatWikiText('error')(stream, state);
942
+ const mt = stream.match(regex);
943
+ if (lbrack === undefined && mt?.[0].includes('[')) {
944
+ state.lbrack = true;
945
+ }
946
+ return mt ? makeStyle(tmpstyle, state) : this.eatWikiText(tmpstyle)(stream, state);
947
+ };
948
+ }
949
+ toEatImageParameter(stream, state) {
950
+ state.imgLink = false;
951
+ const mt = stream.match(this.imgRegex, false);
952
+ if (mt) {
953
+ if (this.config.img?.[`${mt[0]}$1`] === 'img_link') {
954
+ state.imgLink = true;
946
955
  }
956
+ chain(state, this.inChars(mt[0], 'imageParameter'));
947
957
  }
948
- return stream.match(wikiRegex)
949
- ? makeTagStyle('error', state)
950
- : this.eatWikiText('error')(stream, state);
951
- };
952
- }
953
- @getTokenizer
954
- inTableCell(style, needAttr = true, firstLine = true) {
955
- return (stream, state) => {
956
- if (stream.sol()) {
957
- if (stream.match(/^\s*(?:[|!]|\{\{\s*![!)+-]?\s*\}\})/u, false)) {
958
+ }
959
+ // eslint-disable-next-line @typescript-eslint/class-methods-use-this
960
+ eatList(stream, state) {
961
+ const mt = stream.match(/^[*#;:]*/u), { dt } = state;
962
+ if (mt[0].includes(';')) {
963
+ dt.n = mt[0].split(';').length - 1;
964
+ copyNesting(dt, state);
965
+ }
966
+ return makeLocalTagStyle('list', state);
967
+ }
968
+ eatDoubleUnderscore(style, stream, state) {
969
+ const { config: { doubleUnderscore } } = this, name = stream.match(/^[\p{L}\p{N}_]+?__/u);
970
+ if (name) {
971
+ if (Object.prototype.hasOwnProperty.call(doubleUnderscore[0], `__${name[0].toLowerCase()}`)
972
+ || Object.prototype.hasOwnProperty.call(doubleUnderscore[1], `__${name[0]}`)) {
973
+ return tokens.doubleUnderscore;
974
+ }
975
+ else if (!stream.eol()) {
976
+ // Two underscore symbols at the end can be the beginning of another double underscored Magic Word
977
+ stream.backUp(2);
978
+ }
979
+ }
980
+ return makeStyle(style, state);
981
+ }
982
+ get eatStartTable() {
983
+ return (stream, state) => {
984
+ stream.match(/^(?:\{\||\{\{(?:\{\s*|\s*\()!\s*\}\})\s*/u);
985
+ state.tokenize = this.inTableDefinition();
986
+ return makeLocalTagStyle('tableBracket', state);
987
+ };
988
+ }
989
+ inTableDefinition(tr, quote) {
990
+ const style = quote === undefined
991
+ ? `${tokens.tableDefinition} mw-html-${tr ? 'tr' : 'table'}`
992
+ : tokens.tableDefinitionValue;
993
+ return (stream, state) => {
994
+ if (stream.sol()) {
958
995
  state.tokenize = this.inTable;
959
996
  return '';
960
997
  }
961
- else if (firstLine) {
962
- state.tokenize = this.inTableCell(style, false, false);
998
+ const t = state.stack[0], equal = getEqual(t);
999
+ if (equal && stream.peek() === '=') {
1000
+ pop(state);
963
1001
  return '';
964
1002
  }
965
- else if (isSolSyntax(stream, true)) {
1003
+ else if (stream.match(/^(?:&|\{\{|<(?:!--|\/?[a-z]))/iu, false)) {
966
1004
  return this.eatWikiText(style)(stream, state);
967
1005
  }
968
- }
969
- if (firstLine) {
970
- if (stream.match(/^(?:(?:\||\{\{\s*!\s*\}\}){2}|\{\{\s*!!\s*\}\})\s*/u)
971
- || style === tokens.tableTh && stream.match(/^!!\s*/u)) {
972
- state.bold = false;
973
- state.italic = false;
974
- if (!needAttr) {
975
- state.tokenize = this.inTableCell(style);
1006
+ else if (quote) { // 有引号的属性值
1007
+ if (stream.eat(quote[0])) {
1008
+ state.tokenize = this.inTableDefinition(tr, quote[1]);
976
1009
  }
977
- return makeLocalTagStyle('tableDelimiter', state);
978
- }
979
- else if (needAttr && stream.match(/^(?:\||\{\{\s*!\s*\}\})\s*/u)) {
980
- state.bold = false;
981
- state.italic = false;
982
- state.tokenize = this.inTableCell(style, false);
983
- return makeLocalTagStyle('tableDelimiter2', state);
984
- }
985
- else if (needAttr && stream.match('[[', false)) {
986
- state.tokenize = this.inTableCell(style, false);
1010
+ else {
1011
+ stream.match(getTableDefinitionRegex(equal + quote[0]));
1012
+ }
1013
+ return makeLocalStyle(style, state);
987
1014
  }
988
- }
989
- const t = state.stack[0], equal = getEqual(t);
990
- if (equal && stream.peek() === '=') {
991
- pop(state);
992
- return '';
993
- }
994
- return stream.match(getTableCellRegex((firstLine ? '|!' : ':') + equal))
995
- ? makeStyle(style, state)
996
- : this.eatWikiText(style)(stream, state);
997
- };
998
- }
999
- @getTokenizer
1000
- inSectionHeader(str) {
1001
- return (stream, state) => {
1002
- if (stream.sol()) {
1003
- pop(state);
1004
- return '';
1005
- }
1006
- else if (stream.match(headerRegex)) {
1007
- if (stream.eol()) {
1008
- stream.backUp(str.length);
1009
- state.tokenize = this.inStr(str, 'sectionHeader');
1015
+ else if (quote === '') { // 无引号的属性值
1016
+ if (peekSpace(stream)) {
1017
+ state.tokenize = this.inTableDefinition(tr);
1018
+ return '';
1019
+ }
1020
+ stream.match(tableDefinitionValueRegex[equal ? 1 : 0]);
1021
+ return makeLocalStyle(style, state);
1010
1022
  }
1011
- else if (stream.match(/^<!--(?!.*?-->.*?=)/u, false)) {
1012
- // T171074: handle trailing comments
1013
- stream.backUp(str.length);
1014
- state.tokenize = this.inStr('<!--', false, 'sectionHeader');
1023
+ else if (stream.match(/^=\s*/u)) {
1024
+ state.tokenize = this.inTableDefinition(tr, getQuote(stream));
1025
+ return makeLocalStyle(style, state);
1015
1026
  }
1016
- return makeLocalTagStyle('section', state);
1017
- }
1018
- return this.eatWikiText('section')(stream, state);
1019
- };
1020
- }
1021
- @getTokenizer
1022
- get inComment() {
1023
- return this.inStr('-->', 'comment', 'comment');
1024
- }
1025
- eatExtTag(tagname, isCloseTag, state) {
1026
- if (isCloseTag) {
1027
- chain(state, this.inStr('>', 'error'));
1028
- return makeLocalTagStyle('error', state);
1027
+ stream.match(tableDefinitionRegex);
1028
+ return makeLocalStyle(style, state);
1029
+ };
1029
1030
  }
1030
- chain(state, this.eatTagName(tagname));
1031
- return makeLocalTagStyle('extTagBracket', state);
1032
- }
1033
- eatHtmlTag(tagname, isCloseTag, state) {
1034
- if (isCloseTag) {
1035
- const { dt, inHtmlTag } = state;
1036
- if (dt.n && dt.html) {
1037
- dt.html--;
1038
- }
1039
- if (tagname === inHtmlTag[0]) {
1040
- inHtmlTag.shift();
1041
- }
1042
- else {
1043
- chain(state, this.inStr('>', 'error'));
1044
- const i = inHtmlTag.lastIndexOf(tagname);
1045
- if (i !== -1) {
1046
- inHtmlTag.splice(i, 1);
1031
+ get inTable() {
1032
+ return (stream, state) => {
1033
+ if (stream.sol()) {
1034
+ stream.eatSpace();
1035
+ const mt = stream.match(/^(?:\||\{\{\s*!([!)+-])?\s*\}\})/u);
1036
+ if (mt) {
1037
+ if (mt[1] === '-' || !mt[1] && stream.eat('-')) {
1038
+ stream.match(/^-*\s*/u);
1039
+ state.tokenize = this.inTableDefinition(true);
1040
+ return makeLocalTagStyle('tableDelimiter', state);
1041
+ }
1042
+ else if (mt[1] === '+' || !mt[1] && stream.match(/^\+\s*/u)) {
1043
+ state.tokenize = this.inTableCell(tokens.tableCaption);
1044
+ return makeLocalTagStyle('tableDelimiter', state);
1045
+ }
1046
+ else if (mt[1] === ')' || !mt[1] && stream.eat('}')) {
1047
+ pop(state);
1048
+ return makeLocalTagStyle('tableBracket', state);
1049
+ }
1050
+ stream.eatSpace();
1051
+ state.tokenize = this.inTableCell(tokens.tableTd, mt[1] !== '!');
1052
+ return makeLocalTagStyle('tableDelimiter', state);
1053
+ }
1054
+ else if (stream.match(/^!\s*/u)) {
1055
+ state.tokenize = this.inTableCell(tokens.tableTh);
1056
+ return makeLocalTagStyle('tableDelimiter', state);
1057
+ }
1058
+ else if (isSolSyntax(stream, true)) {
1059
+ return this.eatWikiText('error')(stream, state);
1060
+ }
1061
+ }
1062
+ return stream.match(wikiRegex)
1063
+ ? makeTagStyle('error', state)
1064
+ : this.eatWikiText('error')(stream, state);
1065
+ };
1066
+ }
1067
+ inTableCell(style, needAttr = true, firstLine = true) {
1068
+ return (stream, state) => {
1069
+ if (stream.sol()) {
1070
+ if (stream.match(/^\s*(?:[|!]|\{\{\s*![!)+-]?\s*\}\})/u, false)) {
1071
+ state.tokenize = this.inTable;
1072
+ return '';
1073
+ }
1074
+ else if (firstLine) {
1075
+ state.tokenize = this.inTableCell(style, false, false);
1076
+ return '';
1077
+ }
1078
+ else if (isSolSyntax(stream, true)) {
1079
+ return this.eatWikiText(style)(stream, state);
1080
+ }
1081
+ }
1082
+ if (firstLine) {
1083
+ if (stream.match(/^(?:(?:\||\{\{\s*!\s*\}\}){2}|\{\{\s*!!\s*\}\})\s*/u)
1084
+ || style === tokens.tableTh && stream.match(/^!!\s*/u)) {
1085
+ state.bold = false;
1086
+ state.italic = false;
1087
+ if (!needAttr) {
1088
+ state.tokenize = this.inTableCell(style);
1089
+ }
1090
+ return makeLocalTagStyle('tableDelimiter', state);
1091
+ }
1092
+ else if (needAttr && stream.match(/^(?:\||\{\{\s*!\s*\}\})\s*/u)) {
1093
+ state.bold = false;
1094
+ state.italic = false;
1095
+ state.tokenize = this.inTableCell(style, false);
1096
+ return makeLocalTagStyle('tableDelimiter2', state);
1097
+ }
1098
+ else if (needAttr && stream.match('[[', false)) {
1099
+ state.tokenize = this.inTableCell(style, false);
1100
+ }
1101
+ }
1102
+ const t = state.stack[0], equal = getEqual(t);
1103
+ if (equal && stream.peek() === '=') {
1104
+ pop(state);
1105
+ return '';
1047
1106
  }
1107
+ return stream.match(getTableCellRegex((firstLine ? '|!' : ':') + equal))
1108
+ ? makeStyle(style, state)
1109
+ : this.eatWikiText(style)(stream, state);
1110
+ };
1111
+ }
1112
+ inSectionHeader(str) {
1113
+ return (stream, state) => {
1114
+ if (stream.sol()) {
1115
+ pop(state);
1116
+ return '';
1117
+ }
1118
+ else if (stream.match(headerRegex)) {
1119
+ if (stream.eol()) {
1120
+ stream.backUp(str.length);
1121
+ state.tokenize = this.inStr(str, 'sectionHeader');
1122
+ }
1123
+ else if (stream.match(/^<!--(?!.*?-->.*?=)/u, false)) {
1124
+ // T171074: handle trailing comments
1125
+ stream.backUp(str.length);
1126
+ state.tokenize = this.inStr('<!--', false, 'sectionHeader');
1127
+ }
1128
+ return makeLocalTagStyle('section', state);
1129
+ }
1130
+ return this.eatWikiText('section')(stream, state);
1131
+ };
1132
+ }
1133
+ get inComment() {
1134
+ return this.inStr('-->', 'comment', 'comment');
1135
+ }
1136
+ eatExtTag(tagname, isCloseTag, state) {
1137
+ if (isCloseTag) {
1138
+ chain(state, this.inStr('>', 'error'));
1048
1139
  return makeLocalTagStyle('error', state);
1049
1140
  }
1141
+ chain(state, this.eatTagName(tagname));
1142
+ return makeLocalTagStyle('extTagBracket', state);
1050
1143
  }
1051
- chain(state, this.eatTagName(tagname, isCloseTag, true));
1052
- return makeLocalTagStyle('htmlTagBracket', state);
1053
- }
1054
- @getTokenizer
1055
- eatTagName(name, isCloseTag, isHtmlTag) {
1056
- return (stream, state) => {
1057
- stream.match(name, true, true);
1058
- stream.eatSpace();
1059
- if (isHtmlTag) {
1060
- state.tokenize = isCloseTag
1061
- ? this.inStr('>', 'htmlTagBracket')
1062
- : this.inHtmlTagAttribute(name);
1063
- return makeLocalTagStyle('htmlTagName', state);
1064
- }
1065
- // it is the extension tag
1066
- state.tokenize = isCloseTag ? this.inStr('>', 'extTagBracket') : this.inExtTagAttribute(name);
1067
- return makeLocalTagStyle('extTagName', state);
1068
- };
1069
- }
1070
- @getTokenizer
1071
- inHtmlTagAttribute(name, quote) {
1072
- const style = quote === undefined
1073
- ? `${tokens.htmlTagAttribute} mw-html-${name}`
1074
- : tokens.htmlTagAttributeValue;
1075
- return (stream, state) => {
1076
- if (stream.match(new RegExp(`^${lookahead('<', state)}`, 'iu'), false)) {
1077
- pop(state);
1078
- return '';
1079
- }
1080
- const mt = stream.match(/^\/?>/u);
1081
- if (mt) {
1082
- if (!this.voidHtmlTags.has(name) && (mt[0] === '>' || !selfClosingTags.includes(name))) {
1083
- state.inHtmlTag.unshift(name);
1084
- state.dt.html++;
1144
+ eatHtmlTag(tagname, isCloseTag, state) {
1145
+ if (isCloseTag) {
1146
+ const { dt, inHtmlTag } = state;
1147
+ if (dt.n && dt.html) {
1148
+ dt.html--;
1085
1149
  }
1086
- pop(state);
1087
- return makeLocalTagStyle('htmlTagBracket', state);
1088
- }
1089
- const t = state.stack[0], pipe = (['inTemplateArgument', 'inParserFunctionArgument', 'inVariable'].includes(t.name) ? '|' : '')
1090
- + getEqual(t);
1091
- if (pipe.includes(stream.peek() ?? '')) {
1092
- pop(state);
1093
- return makeLocalTagStyle('htmlTagBracket', state);
1094
- }
1095
- else if (stream.match(/^(?:[&<]|\{\{)/u, false)) {
1096
- return this.eatWikiText(style)(stream, state);
1097
- }
1098
- else if (quote) { // 有引号的属性值
1099
- if (stream.eat(quote[0])) {
1100
- state.tokenize = this.inHtmlTagAttribute(name, quote[1]);
1150
+ if (tagname === inHtmlTag[0]) {
1151
+ inHtmlTag.shift();
1101
1152
  }
1102
1153
  else {
1103
- stream.match(getHtmlAttrRegex(pipe + quote[0]));
1154
+ chain(state, this.inStr('>', 'error'));
1155
+ const i = inHtmlTag.lastIndexOf(tagname);
1156
+ if (i !== -1) {
1157
+ inHtmlTag.splice(i, 1);
1158
+ }
1159
+ return makeLocalTagStyle('error', state);
1104
1160
  }
1105
- return makeLocalStyle(style, state);
1106
1161
  }
1107
- else if (quote === '') { // 无引号的属性值
1108
- if (peekSpace(stream, true)) {
1109
- state.tokenize = this.inHtmlTagAttribute(name);
1162
+ chain(state, this.eatTagName(tagname, isCloseTag, true));
1163
+ return makeLocalTagStyle('htmlTagBracket', state);
1164
+ }
1165
+ eatTagName(name, isCloseTag, isHtmlTag) {
1166
+ return (stream, state) => {
1167
+ stream.match(name, true, true);
1168
+ stream.eatSpace();
1169
+ if (isHtmlTag) {
1170
+ state.tokenize = isCloseTag
1171
+ ? this.inStr('>', 'htmlTagBracket')
1172
+ : this.inHtmlTagAttribute(name);
1173
+ return makeLocalTagStyle('htmlTagName', state);
1174
+ }
1175
+ // it is the extension tag
1176
+ state.tokenize = isCloseTag ? this.inStr('>', 'extTagBracket') : this.inExtTagAttribute(name);
1177
+ return makeLocalTagStyle('extTagName', state);
1178
+ };
1179
+ }
1180
+ inHtmlTagAttribute(name, quote) {
1181
+ const style = quote === undefined
1182
+ ? `${tokens.htmlTagAttribute} mw-html-${name}`
1183
+ : tokens.htmlTagAttributeValue;
1184
+ return (stream, state) => {
1185
+ if (stream.match(new RegExp(`^${lookahead('<', state)}`, 'iu'), false)) {
1186
+ pop(state);
1110
1187
  return '';
1111
1188
  }
1112
- stream.match(getHtmlAttrRegex(String.raw `\s${pipe}`));
1113
- return makeLocalStyle(style, state);
1114
- }
1115
- else if (stream.match(/^=\s*/u)) {
1116
- state.tokenize = this.inHtmlTagAttribute(name, getQuote(stream));
1117
- return makeLocalStyle(style, state);
1118
- }
1119
- stream.match(getHtmlAttrKeyRegex(pipe));
1120
- return makeLocalStyle(style, state);
1121
- };
1122
- }
1123
- @getTokenizer
1124
- inExtTagAttribute(name, quote, isLang, isPage) {
1125
- const style = `${tokens.extTagAttribute} mw-ext-${name}`;
1126
- const advance = (stream, state, re) => {
1127
- const mt = stream.match(re);
1128
- if (isLang) {
1129
- switch (mt[0].trim().toLowerCase()) {
1130
- case 'js':
1131
- case 'javascript':
1132
- state.extMode = javascript;
1133
- break;
1134
- case 'css':
1135
- state.extMode = css;
1136
- break;
1137
- case 'json':
1138
- state.extMode = json;
1139
- // no default
1189
+ const mt = stream.match(/^\/?>/u);
1190
+ if (mt) {
1191
+ if (!this.voidHtmlTags.has(name) && (mt[0] === '>' || !selfClosingTags.includes(name))) {
1192
+ state.inHtmlTag.unshift(name);
1193
+ state.dt.html++;
1194
+ }
1195
+ pop(state);
1196
+ return makeLocalTagStyle('htmlTagBracket', state);
1140
1197
  }
1141
- }
1142
- return makeLocalStyle(tokens.extTagAttributeValue + (isPage ? ` ${tokens.pageName}` : ''), state);
1143
- };
1144
- return (stream, state) => {
1145
- if (stream.eat('>')) {
1146
- const { config: { tagModes } } = this;
1147
- state.extName = name;
1148
- state.extMode ||= name in tagModes
1149
- && this[tagModes[name]](state.data.tags.filter(tag => tag !== name));
1150
- if (state.extMode) {
1151
- state.extState = state.extMode.startState(0);
1152
- }
1153
- state.tokenize = this.eatExtTagArea(name);
1154
- return makeLocalTagStyle('extTagBracket', state);
1155
- }
1156
- else if (stream.match('/>')) {
1157
- state.extMode = false;
1158
- pop(state);
1159
- return makeLocalTagStyle('extTagBracket', state);
1160
- }
1161
- else if (quote) { // 有引号的属性值
1162
- if (stream.eat(quote[0])) {
1163
- const [, remains] = quote;
1164
- state.tokenize = this.inExtTagAttribute(name, remains, isLang && Boolean(remains), isPage && Boolean(remains));
1165
- return makeLocalTagStyle('extTagAttributeValue', state);
1198
+ const t = state.stack[0], pipe = (['inTemplateArgument', 'inParserFunctionArgument', 'inVariable'].includes(t.name) ? '|' : '')
1199
+ + getEqual(t);
1200
+ if (pipe.includes(stream.peek() ?? '')) {
1201
+ pop(state);
1202
+ return makeLocalTagStyle('htmlTagBracket', state);
1166
1203
  }
1167
- return advance(stream, state, getExtAttrRegex(quote[0]));
1168
- }
1169
- else if (quote === '') { // 无引号的属性值
1170
- if (peekSpace(stream, true)) {
1171
- state.tokenize = this.inExtTagAttribute(name);
1172
- return '';
1204
+ else if (stream.match(/^(?:[&<]|\{\{)/u, false)) {
1205
+ return this.eatWikiText(style)(stream, state);
1173
1206
  }
1174
- return advance(stream, state, /^(?:[^>/\s]|\/(?!>))+/u);
1175
- }
1176
- else if (stream.match(/^=\s*/u)) {
1177
- state.tokenize = this.inExtTagAttribute(name, getQuote(stream), isLang, isPage);
1178
- return makeLocalStyle(style, state);
1179
- }
1180
- const mt = stream.match(/^(?:[^>/=]|\/(?!>))+/u);
1181
- if (stream.peek() === '=') {
1182
- state.tokenize = this.inExtTagAttribute(name, undefined, syntaxHighlight.has(name) && /(?:^|\s)lang\s*$/iu.test(mt[0]), name === 'templatestyles' && /(?:^|\s)src\s*$/iu.test(mt[0]));
1183
- }
1184
- return makeLocalStyle(style, state);
1185
- };
1186
- }
1187
- @getTokenizer
1188
- eatExtTagArea(name) {
1189
- return (stream, state) => {
1190
- const { pos } = stream, i = stream.string.slice(pos).search(getExtTagCloseRegex(name));
1191
- if (i === 0) {
1192
- stream.match('</');
1193
- state.tokenize = this.eatTagName(name, true);
1194
- state.extName = false;
1195
- state.extMode = false;
1196
- state.extState = false;
1197
- return makeLocalTagStyle('extTagBracket', state);
1198
- }
1199
- let origString = '';
1200
- if (i !== -1) {
1201
- origString = stream.string;
1202
- stream.string = origString.slice(0, pos + i);
1203
- }
1204
- chain(state, this.inExtTokens(origString));
1205
- return '';
1206
- };
1207
- }
1208
- @getTokenizer
1209
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
1210
- inExtTokens(origString) {
1211
- return (stream, state) => {
1212
- let ret;
1213
- if (state.extMode === false) {
1214
- ret = `mw-tag-${state.extName} ${tokens.extTag}`;
1215
- stream.skipToEnd();
1216
- }
1217
- else {
1218
- ret = `mw-tag-${state.extName} ${state.extMode.token(stream, state.extState) ?? ''}`;
1219
- }
1220
- if (stream.eol()) {
1221
- if (origString) {
1222
- stream.string = origString;
1207
+ else if (quote) { // 有引号的属性值
1208
+ if (stream.eat(quote[0])) {
1209
+ state.tokenize = this.inHtmlTagAttribute(name, quote[1]);
1210
+ }
1211
+ else {
1212
+ stream.match(getHtmlAttrRegex(pipe + quote[0]));
1213
+ }
1214
+ return makeLocalStyle(style, state);
1223
1215
  }
1224
- pop(state);
1225
- }
1226
- return ret;
1227
- };
1228
- }
1229
- @getTokenizer
1230
- inVariable(pos = 0) {
1231
- let tag = 'comment';
1232
- if (pos === 0) {
1233
- tag = 'templateVariableName';
1216
+ else if (quote === '') { // 无引号的属性值
1217
+ if (peekSpace(stream, true)) {
1218
+ state.tokenize = this.inHtmlTagAttribute(name);
1219
+ return '';
1220
+ }
1221
+ stream.match(getHtmlAttrRegex(String.raw `\s${pipe}`));
1222
+ return makeLocalStyle(style, state);
1223
+ }
1224
+ else if (stream.match(/^=\s*/u)) {
1225
+ state.tokenize = this.inHtmlTagAttribute(name, getQuote(stream));
1226
+ return makeLocalStyle(style, state);
1227
+ }
1228
+ stream.match(getHtmlAttrKeyRegex(pipe));
1229
+ return makeLocalStyle(style, state);
1230
+ };
1234
1231
  }
1235
- else if (pos === 1) {
1236
- tag = 'templateVariable';
1232
+ inExtTagAttribute(name, quote, isLang, isPage) {
1233
+ const style = `${tokens.extTagAttribute} mw-ext-${name}`;
1234
+ const advance = (stream, state, re) => {
1235
+ const mt = stream.match(re);
1236
+ if (isLang) {
1237
+ switch (mt[0].trim().toLowerCase()) {
1238
+ case 'js':
1239
+ case 'javascript':
1240
+ state.extMode = javascript;
1241
+ break;
1242
+ case 'css':
1243
+ state.extMode = css;
1244
+ break;
1245
+ case 'json':
1246
+ state.extMode = json;
1247
+ // no default
1248
+ }
1249
+ }
1250
+ return makeLocalStyle(tokens.extTagAttributeValue + (isPage ? ` ${tokens.pageName}` : ''), state);
1251
+ };
1252
+ return (stream, state) => {
1253
+ if (stream.eat('>')) {
1254
+ const { config: { tagModes } } = this;
1255
+ state.extName = name;
1256
+ state.extMode ||= name in tagModes
1257
+ && this[tagModes[name]](state.data.tags.filter(tag => tag !== name));
1258
+ if (state.extMode) {
1259
+ state.extState = state.extMode.startState(0);
1260
+ }
1261
+ state.tokenize = this.eatExtTagArea(name);
1262
+ return makeLocalTagStyle('extTagBracket', state);
1263
+ }
1264
+ else if (stream.match('/>')) {
1265
+ state.extMode = false;
1266
+ pop(state);
1267
+ return makeLocalTagStyle('extTagBracket', state);
1268
+ }
1269
+ else if (quote) { // 有引号的属性值
1270
+ if (stream.eat(quote[0])) {
1271
+ const [, remains] = quote;
1272
+ state.tokenize = this.inExtTagAttribute(name, remains, isLang && Boolean(remains), isPage && Boolean(remains));
1273
+ return makeLocalTagStyle('extTagAttributeValue', state);
1274
+ }
1275
+ return advance(stream, state, getExtAttrRegex(quote[0]));
1276
+ }
1277
+ else if (quote === '') { // 无引号的属性值
1278
+ if (peekSpace(stream, true)) {
1279
+ state.tokenize = this.inExtTagAttribute(name);
1280
+ return '';
1281
+ }
1282
+ return advance(stream, state, /^(?:[^>/\s]|\/(?!>))+/u);
1283
+ }
1284
+ else if (stream.match(/^=\s*/u)) {
1285
+ state.tokenize = this.inExtTagAttribute(name, getQuote(stream), isLang, isPage);
1286
+ return makeLocalStyle(style, state);
1287
+ }
1288
+ const mt = stream.match(/^(?:[^>/=]|\/(?!>))+/u);
1289
+ if (stream.peek() === '=') {
1290
+ state.tokenize = this.inExtTagAttribute(name, undefined, syntaxHighlight.has(name) && /(?:^|\s)lang\s*$/iu.test(mt[0]), name === 'templatestyles' && /(?:^|\s)src\s*$/iu.test(mt[0]));
1291
+ }
1292
+ return makeLocalStyle(style, state);
1293
+ };
1237
1294
  }
1238
- const re = variableRegex[pos === 1 ? 1 : 0];
1239
- return (stream, state) => {
1240
- const sol = stream.sol();
1241
- stream.eatSpace();
1242
- if (stream.eol()) {
1243
- return makeLocalStyle('', state);
1244
- }
1245
- else if (stream.eat('|')) {
1246
- if (pos < 2) {
1247
- state.tokenize = this.inVariable(pos + 1);
1295
+ eatExtTagArea(name) {
1296
+ return (stream, state) => {
1297
+ const { pos } = stream, i = stream.string.slice(pos).search(getExtTagCloseRegex(name));
1298
+ if (i === 0) {
1299
+ stream.match('</');
1300
+ state.tokenize = this.eatTagName(name, true);
1301
+ state.extName = false;
1302
+ state.extMode = false;
1303
+ state.extState = false;
1304
+ return makeLocalTagStyle('extTagBracket', state);
1248
1305
  }
1249
- return makeLocalTagStyle('templateVariableDelimiter', state);
1250
- }
1251
- else if (stream.match(/^\}{2,3}/u)) {
1252
- pop(state);
1253
- return makeLocalTagStyle('templateVariableBracket', state, 'nVar');
1254
- }
1255
- else if (stream.match('<!--')) {
1256
- chain(state, this.inComment);
1257
- return makeLocalTagStyle('comment', state);
1258
- }
1259
- else if (pos === 0 && sol) {
1260
- state.nVar--;
1261
- pop(state);
1262
- stream.pos = 0;
1306
+ let origString = '';
1307
+ if (i !== -1) {
1308
+ origString = stream.string;
1309
+ stream.string = origString.slice(0, pos + i);
1310
+ }
1311
+ chain(state, this.inExtTokens(origString));
1263
1312
  return '';
1264
- }
1265
- return pos === 1 && isSolSyntax(stream) || !stream.match(re)
1266
- ? this.eatWikiText(tag)(stream, state)
1267
- : (pos === 1 ? makeTagStyle : makeLocalTagStyle)(tag, state);
1268
- };
1269
- }
1270
- eatTransclusion(stream, state) {
1271
- const [{ length }] = stream.match(/^\s*/u);
1272
- // Parser function
1273
- if (stream.peek() === '#') {
1274
- stream.backUp(length);
1275
- state.nExt++;
1276
- chain(state, this.inParserFunctionName());
1277
- return makeLocalTagStyle('parserFunctionBracket', state);
1313
+ };
1278
1314
  }
1279
- // Check for parser function without '#'
1280
- const name = stream.match(/^([^}<{|::]+)(.?)/u, false);
1281
- if (name) {
1282
- const [, f, delimiter] = name, fullWidth = delimiter === ':';
1283
- let ff = f;
1284
- if (fullWidth) {
1285
- ff += ':';
1315
+ inExtTokens(origString) {
1316
+ return (stream, state) => {
1317
+ let ret;
1318
+ if (state.extMode === false) {
1319
+ ret = `mw-tag-${state.extName} ${tokens.extTag}`;
1320
+ stream.skipToEnd();
1321
+ }
1322
+ else {
1323
+ ret = `mw-tag-${state.extName} ${state.extMode.token(stream, state.extState) ?? ''}`;
1324
+ }
1325
+ if (stream.eol()) {
1326
+ if (origString) {
1327
+ stream.string = origString;
1328
+ }
1329
+ pop(state);
1330
+ }
1331
+ return ret;
1332
+ };
1333
+ }
1334
+ inVariable(pos = 0) {
1335
+ let tag = 'comment';
1336
+ if (pos === 0) {
1337
+ tag = 'templateVariableName';
1286
1338
  }
1287
- else if (delimiter !== ':') {
1288
- ff = f.trim();
1339
+ else if (pos === 1) {
1340
+ tag = 'templateVariable';
1289
1341
  }
1290
- const ffLower = ff.toLowerCase(), { config: { functionSynonyms, variableIDs, functionHooks } } = this, canonicalName = Object.prototype.hasOwnProperty.call(functionSynonyms[1], ff)
1291
- && functionSynonyms[1][ff]
1292
- || Object.prototype.hasOwnProperty.call(functionSynonyms[0], ffLower)
1293
- && functionSynonyms[0][ffLower];
1294
- if ((!delimiter || fullWidth || delimiter === ':' || delimiter === '}')
1295
- && canonicalName
1296
- && (fullWidth || delimiter === ':' || !variableIDs || variableIDs.includes(canonicalName))
1297
- && (!fullWidth && delimiter !== ':'
1298
- || !functionHooks
1299
- || functionHooks.includes(canonicalName) || otherParserFunctions.has(canonicalName))) {
1342
+ const re = variableRegex[pos === 1 ? 1 : 0];
1343
+ return (stream, state) => {
1344
+ const sol = stream.sol();
1345
+ stream.eatSpace();
1346
+ if (stream.eol()) {
1347
+ return makeLocalStyle('', state);
1348
+ }
1349
+ else if (stream.eat('|')) {
1350
+ if (pos < 2) {
1351
+ state.tokenize = this.inVariable(pos + 1);
1352
+ }
1353
+ return makeLocalTagStyle('templateVariableDelimiter', state);
1354
+ }
1355
+ else if (stream.match(/^\}{2,3}/u)) {
1356
+ pop(state);
1357
+ return makeLocalTagStyle('templateVariableBracket', state, 'nVar');
1358
+ }
1359
+ else if (stream.match('<!--')) {
1360
+ chain(state, this.inComment);
1361
+ return makeLocalTagStyle('comment', state);
1362
+ }
1363
+ else if (pos === 0 && sol) {
1364
+ state.nVar--;
1365
+ pop(state);
1366
+ stream.pos = 0;
1367
+ return '';
1368
+ }
1369
+ return pos === 1 && isSolSyntax(stream) || !stream.match(re)
1370
+ ? this.eatWikiText(tag)(stream, state)
1371
+ : (pos === 1 ? makeTagStyle : makeLocalTagStyle)(tag, state);
1372
+ };
1373
+ }
1374
+ eatTransclusion(stream, state) {
1375
+ const [{ length }] = stream.match(/^\s*/u);
1376
+ // Parser function
1377
+ if (stream.peek() === '#') {
1300
1378
  stream.backUp(length);
1301
1379
  state.nExt++;
1302
1380
  chain(state, this.inParserFunctionName());
1303
1381
  return makeLocalTagStyle('parserFunctionBracket', state);
1304
1382
  }
1305
- }
1306
- if (stream.match('}}')) {
1307
- return undefined;
1308
- }
1309
- // Template
1310
- stream.backUp(length);
1311
- state.nTemplate++;
1312
- chain(state, this.inTemplatePageName());
1313
- return makeLocalTagStyle('templateBracket', state);
1314
- }
1315
- @getTokenizer
1316
- inParserFunctionName(invoke, n, ns, subst) {
1317
- return (stream, state) => {
1318
- const sol = stream.sol(), space = stream.eatSpace();
1319
- if (stream.eol()) {
1320
- return makeLocalStyle('', state);
1321
- }
1322
- else if (stream.eat('}')) {
1323
- pop(state);
1324
- return makeLocalTagStyle(stream.eat('}') ? 'parserFunctionBracket' : 'error', state, 'nExt');
1325
- }
1326
- else if (stream.match('<!--')) {
1327
- chain(state, this.inComment);
1328
- return makeLocalTagStyle('comment', state);
1329
- }
1330
- else if (sol) {
1331
- state.nExt--;
1332
- pop(state);
1333
- stream.pos = 0;
1334
- return '';
1383
+ // Check for parser function without '#'
1384
+ const name = stream.match(/^([^}<{|::]+)(.?)/u, false);
1385
+ if (name) {
1386
+ const [, f, delimiter] = name, fullWidth = delimiter === ':';
1387
+ let ff = f;
1388
+ if (fullWidth) {
1389
+ ff += ':';
1390
+ }
1391
+ else if (delimiter !== ':') {
1392
+ ff = f.trim();
1393
+ }
1394
+ const ffLower = ff.toLowerCase(), { config: { functionSynonyms, variableIDs, functionHooks } } = this, canonicalName = Object.prototype.hasOwnProperty.call(functionSynonyms[1], ff)
1395
+ && functionSynonyms[1][ff]
1396
+ || Object.prototype.hasOwnProperty.call(functionSynonyms[0], ffLower)
1397
+ && functionSynonyms[0][ffLower];
1398
+ if ((!delimiter || fullWidth || delimiter === ':' || delimiter === '}')
1399
+ && canonicalName
1400
+ && (fullWidth || delimiter === ':' || !variableIDs || variableIDs.includes(canonicalName))
1401
+ && (!fullWidth && delimiter !== ':'
1402
+ || !functionHooks
1403
+ || functionHooks.includes(canonicalName) || otherParserFunctions.has(canonicalName))) {
1404
+ stream.backUp(length);
1405
+ state.nExt++;
1406
+ chain(state, this.inParserFunctionName());
1407
+ return makeLocalTagStyle('parserFunctionBracket', state);
1408
+ }
1335
1409
  }
1336
- const ch = stream.eat(/[::|]/u);
1337
- if (ch) {
1338
- state.tokenize = subst && stream.match(/^\s*#/u, false)
1339
- ? this.inParserFunctionName()
1340
- : this.inParserFunctionArgument(invoke, n, ns);
1341
- return makeLocalTagStyle(space || ch === '|' ? 'error' : 'parserFunctionDelimiter', state);
1410
+ if (stream.match('}}')) {
1411
+ return undefined;
1342
1412
  }
1343
- const mt = stream.match(/^(?:[^::}{|<>[\]\s]|\s(?![::]))+/u);
1344
- if (mt) {
1345
- const name = mt[0].trim().toLowerCase() + (stream.peek() === ':' ? ':' : ''), { config: { functionSynonyms: [insensitive] } } = this;
1346
- if (name.startsWith('#')) {
1347
- switch (insensitive[name] ?? insensitive[name.slice(1)]) {
1348
- case 'invoke':
1349
- state.tokenize = this.inParserFunctionName(2);
1350
- break;
1351
- case 'widget':
1352
- state.tokenize = this.inParserFunctionName(1);
1353
- break;
1354
- case 'switch':
1355
- state.tokenize = this.inParserFunctionName(undefined, 1);
1356
- break;
1357
- case 'tag':
1358
- state.tokenize = this.inParserFunctionName(undefined, 2);
1359
- break;
1360
- case 'ifexist':
1361
- case 'lst':
1362
- case 'lstx':
1363
- case 'lsth':
1364
- state.tokenize = this.inParserFunctionName(Infinity);
1365
- // no default
1366
- }
1413
+ // Template
1414
+ stream.backUp(length);
1415
+ state.nTemplate++;
1416
+ chain(state, this.inTemplatePageName());
1417
+ return makeLocalTagStyle('templateBracket', state);
1418
+ }
1419
+ inParserFunctionName(invoke, n, ns, subst) {
1420
+ return (stream, state) => {
1421
+ const sol = stream.sol(), space = stream.eatSpace();
1422
+ if (stream.eol()) {
1423
+ return makeLocalStyle('', state);
1367
1424
  }
1368
- else {
1369
- const canonicalName = insensitive[name];
1370
- if (pageFunctions.has(canonicalName)) {
1371
- let namespace = 0;
1372
- switch (canonicalName) {
1373
- case 'filepath':
1374
- namespace = 6;
1425
+ else if (stream.eat('}')) {
1426
+ pop(state);
1427
+ return makeLocalTagStyle(stream.eat('}') ? 'parserFunctionBracket' : 'error', state, 'nExt');
1428
+ }
1429
+ else if (stream.match('<!--')) {
1430
+ chain(state, this.inComment);
1431
+ return makeLocalTagStyle('comment', state);
1432
+ }
1433
+ else if (sol) {
1434
+ state.nExt--;
1435
+ pop(state);
1436
+ stream.pos = 0;
1437
+ return '';
1438
+ }
1439
+ const ch = stream.eat(/[::|]/u);
1440
+ if (ch) {
1441
+ state.tokenize = subst && stream.match(/^\s*#/u, false)
1442
+ ? this.inParserFunctionName()
1443
+ : this.inParserFunctionArgument(invoke, n, ns);
1444
+ return makeLocalTagStyle(space || ch === '|' ? 'error' : 'parserFunctionDelimiter', state);
1445
+ }
1446
+ const mt = stream.match(/^(?:[^::}{|<>[\]\s]|\s(?![::]))+/u);
1447
+ if (mt) {
1448
+ const name = mt[0].trim().toLowerCase() + (stream.peek() === ':' ? ':' : ''), { config: { functionSynonyms: [insensitive] } } = this;
1449
+ if (name.startsWith('#')) {
1450
+ switch (insensitive[name] ?? insensitive[name.slice(1)]) {
1451
+ case 'invoke':
1452
+ state.tokenize = this.inParserFunctionName(2);
1375
1453
  break;
1376
- case 'int':
1377
- namespace = 8;
1454
+ case 'widget':
1455
+ state.tokenize = this.inParserFunctionName(1);
1378
1456
  break;
1379
- case 'raw':
1380
- case 'msg':
1381
- case 'msgnw':
1382
- namespace = 10;
1457
+ case 'switch':
1458
+ state.tokenize = this.inParserFunctionName(undefined, 1);
1459
+ break;
1460
+ case 'tag':
1461
+ state.tokenize = this.inParserFunctionName(undefined, 2);
1462
+ break;
1463
+ case 'ifexist':
1464
+ case 'lst':
1465
+ case 'lstx':
1466
+ case 'lsth':
1467
+ state.tokenize = this.inParserFunctionName(Infinity);
1383
1468
  // no default
1384
1469
  }
1385
- state.tokenize = canonicalName === 'subst' || canonicalName === 'safesubst'
1386
- ? this.inParserFunctionName(invoke, n, ns, true)
1387
- : this.inParserFunctionName(Infinity, Infinity, namespace);
1388
1470
  }
1471
+ else {
1472
+ const canonicalName = insensitive[name];
1473
+ if (pageFunctions.has(canonicalName)) {
1474
+ let namespace = 0;
1475
+ switch (canonicalName) {
1476
+ case 'filepath':
1477
+ namespace = 6;
1478
+ break;
1479
+ case 'int':
1480
+ namespace = 8;
1481
+ break;
1482
+ case 'raw':
1483
+ case 'msg':
1484
+ case 'msgnw':
1485
+ namespace = 10;
1486
+ // no default
1487
+ }
1488
+ state.tokenize = canonicalName === 'subst' || canonicalName === 'safesubst'
1489
+ ? this.inParserFunctionName(invoke, n, ns, true)
1490
+ : this.inParserFunctionName(Infinity, Infinity, namespace);
1491
+ }
1492
+ }
1493
+ return makeLocalTagStyle('parserFunctionName', state);
1389
1494
  }
1390
- return makeLocalTagStyle('parserFunctionName', state);
1391
- }
1392
- pop(state);
1393
- return makeLocalStyle('', state, 'nExt');
1394
- };
1395
- }
1396
- @getTokenizer
1397
- inTemplatePageName(haveEaten, anchor) {
1398
- const style = anchor ? tokens.error : `${tokens.templateName} ${tokens.pageName}`, chars = '{}<', re = anchor ? templateRegex : /^(?:&#(?:\d+|x[a-f\d]+);|[^|{}<>[\]#%]|%(?![\da-f]{2}))+/iu;
1399
- return (stream, state) => {
1400
- const sol = stream.sol(), space = stream.eatSpace();
1401
- if (stream.eol()) {
1402
- return makeLocalStyle('', state);
1403
- }
1404
- else if (stream.match('}}')) {
1405
- pop(state);
1406
- return makeLocalTagStyle('templateBracket', state, 'nTemplate');
1407
- }
1408
- else if (stream.match('<!--')) {
1409
- chain(state, this.inComment);
1410
- return makeLocalTagStyle('comment', state);
1411
- }
1412
- else if (stream.match(/^<\/?onlyinclude>/u)
1413
- || stream.match(/^<(?:(?:includeonly|noinclude)(?:\s[^>]*)?\/?>|\/(?:includeonly|noinclude)\s*>)/iu)) {
1414
- return makeLocalTagStyle('comment', state);
1415
- }
1416
- else if (stream.eat('|')) {
1417
- state.tokenize = this.inTemplateArgument(true);
1418
- return makeLocalTagStyle('templateDelimiter', state);
1419
- }
1420
- else if (haveEaten && sol) {
1421
- state.nTemplate--;
1422
1495
  pop(state);
1423
- stream.pos = 0;
1424
- return '';
1425
- }
1426
- else if (!anchor && stream.eat('#')) {
1427
- state.tokenize = this.inTemplatePageName(true, true);
1428
- return makeLocalTagStyle('error', state);
1429
- }
1430
- else if (!anchor
1431
- && stream.match(new RegExp(String.raw `^(?:[>[\]]|%[\da-f]{2}|${lookahead(chars, state)})+`, 'iu'))) {
1432
- return makeLocalTagStyle('error', state);
1433
- }
1434
- else if (!anchor && stream.peek() === '<') {
1435
- pop(state);
1436
- return makeLocalStyle('', state, 'nTemplate');
1437
- }
1438
- else if (space && !haveEaten) {
1439
- return makeLocalStyle('', state);
1440
- }
1441
- else if (stream.match(re)) {
1442
- if (!haveEaten) {
1443
- state.tokenize = this.inTemplatePageName(true, anchor);
1444
- }
1445
- return makeLocalStyle(style, state);
1446
- }
1447
- return space
1448
- ? makeLocalStyle(style, state)
1449
- : this.eatWikiText(style)(stream, state);
1450
- };
1451
- }
1452
- @getTokenizer
1453
- inParserFunctionArgument(module, n = module ?? Infinity, ns = 0) {
1454
- if (n === 0) {
1455
- return this.inTemplateArgument(true, true);
1456
- }
1457
- const chars = n === 2 ? '}{<' : "}{<~'_-"; // `#invoke`/`#tag`
1458
- let style = `${tokens.parserFunction} ${module ? tokens.pageName : ''}`;
1459
- switch (module) {
1460
- case 1:
1461
- style += ' mw-function-274';
1462
- break;
1463
- case 2:
1464
- style += ' mw-function-828';
1465
- break;
1466
- case Infinity:
1467
- style += ` mw-function-${ns}`;
1468
- // no default
1496
+ return makeLocalStyle('', state, 'nExt');
1497
+ };
1469
1498
  }
1470
- return (stream, state) => {
1471
- if (stream.eat('|')) {
1472
- if (module) {
1473
- state.tokenize = this.inParserFunctionArgument(undefined, module - 1);
1499
+ inTemplatePageName(haveEaten, anchor) {
1500
+ const style = anchor ? tokens.error : `${tokens.templateName} ${tokens.pageName}`, chars = '{}<', re = anchor ? templateRegex : /^(?:&#(?:\d+|x[a-f\d]+);|[^|{}<>[\]#%]|%(?![\da-f]{2}))+/iu;
1501
+ return (stream, state) => {
1502
+ const sol = stream.sol(), space = stream.eatSpace();
1503
+ if (stream.eol()) {
1504
+ return makeLocalStyle('', state);
1474
1505
  }
1475
- else if (n !== Infinity) {
1476
- state.tokenize = this.inParserFunctionArgument(undefined, n - 1);
1506
+ else if (stream.match('}}')) {
1507
+ pop(state);
1508
+ return makeLocalTagStyle('templateBracket', state, 'nTemplate');
1477
1509
  }
1478
- return makeLocalTagStyle('parserFunctionDelimiter', state);
1479
- }
1480
- else if (stream.match('}}')) {
1481
- pop(state);
1482
- return makeLocalTagStyle('parserFunctionBracket', state, 'nExt');
1483
- }
1484
- return !isSolSyntax(stream)
1485
- && stream.match(parserFunctionRegex[module ? 0 : Number(needColon(state)) + 1](chars))
1486
- ? makeLocalStyle(style, state)
1487
- : this.eatWikiText('parserFunction')(stream, state);
1488
- };
1489
- }
1490
- @getTokenizer
1491
- inTemplateArgument(expectName, parserFunction) {
1492
- const tag = parserFunction ? 'parserFunction' : 'template';
1493
- return (stream, state) => {
1494
- const space = stream.eatSpace();
1495
- if (stream.eol()) {
1496
- return makeLocalTagStyle(tag, state);
1497
- }
1498
- else if (stream.eat('|')) {
1499
- if (!expectName) {
1500
- state.tokenize = this.inTemplateArgument(true, parserFunction);
1510
+ else if (stream.match('<!--')) {
1511
+ chain(state, this.inComment);
1512
+ return makeLocalTagStyle('comment', state);
1501
1513
  }
1502
- return makeLocalTagStyle(parserFunction ? 'parserFunctionDelimiter' : 'templateDelimiter', state);
1503
- }
1504
- else if (stream.match('}}', false)) {
1505
- if (space) {
1506
- return makeLocalTagStyle(tag, state);
1514
+ else if (stream.match(/^<\/?onlyinclude>/u)
1515
+ || stream.match(/^<(?:(?:includeonly|noinclude)(?:\s[^>]*)?\/?>|\/(?:includeonly|noinclude)\s*>)/iu)) {
1516
+ return makeLocalTagStyle('comment', state);
1507
1517
  }
1508
- stream.pos += 2;
1509
- pop(state);
1510
- return makeLocalTagStyle(parserFunction ? 'parserFunctionBracket' : 'templateBracket', state, parserFunction ? 'nExt' : 'nTemplate');
1511
- }
1512
- else if (stream.sol() && stream.peek() === '=') {
1513
- const style = this.eatWikiText(tag)(stream, state);
1514
- if (style.includes(tokens.sectionHeader)) {
1515
- return style;
1518
+ else if (stream.eat('|')) {
1519
+ state.tokenize = this.inTemplateArgument(true);
1520
+ return makeLocalTagStyle('templateDelimiter', state);
1516
1521
  }
1517
- stream.pos = 0;
1518
- }
1519
- if (expectName
1520
- && stream.match(new RegExp(`^(?:[^=|}{[<]|${lookahead('}{[<', state)})*=`, 'iu'))) {
1521
- state.tokenize = this.inTemplateArgument(false, parserFunction);
1522
- return makeLocalTagStyle('templateArgumentName', state);
1523
- }
1524
- else if (isSolSyntax(stream) && stream.peek() !== '=') {
1525
- return this.eatWikiText(tag)(stream, state);
1526
- }
1527
- return stream.match(needColon(state) ? argumentRegex : styleRegex) || space
1528
- ? makeLocalTagStyle(tag, state)
1529
- : this.eatWikiText(tag)(stream, state);
1530
- };
1531
- }
1532
- @getTokenizer
1533
- inConvert(style, needFlag, needLang = true, plain) {
1534
- return (stream, state) => {
1535
- const space = stream.eatSpace();
1536
- if (stream.match('}-')) {
1537
- pop(state);
1538
- return makeLocalTagStyle('convertBracket', state);
1539
- }
1540
- else if (needFlag && stream.match(/^[;\sa-z-]*(?=\|)/iu)) {
1541
- chain(state, this.inConvert(style, false, true, plain));
1542
- state.tokenize = this.inStr('|', 'convertDelimiter');
1543
- return makeLocalTagStyle('convertFlag', state);
1544
- }
1545
- else if (stream.match(this.convertSemicolon)) {
1546
- if (needFlag || !needLang) {
1547
- state.tokenize = this.inConvert(style, false, true, plain);
1522
+ else if (haveEaten && sol) {
1523
+ state.nTemplate--;
1524
+ pop(state);
1525
+ stream.pos = 0;
1526
+ return '';
1548
1527
  }
1549
- return makeLocalTagStyle('convertDelimiter', state);
1550
- }
1551
- else if (needLang && stream.match(this.convertLang)) {
1552
- state.tokenize = this.inConvert(style, false, false, plain);
1553
- return makeLocalTagStyle('convertLang', state);
1554
- }
1555
- else if (plain) {
1556
- if (stream.match('-{', false)) {
1557
- return this.eatWikiText(style)(stream, state);
1528
+ else if (!anchor && stream.eat('#')) {
1529
+ state.tokenize = this.inTemplatePageName(true, true);
1530
+ return makeLocalTagStyle('error', state);
1558
1531
  }
1559
- stream.match(/^(?:(?:[^};=-]|\}(?!-)|=(?!>)|-(?!\{))+|;|=>)/u);
1560
- return makeStyle(style, state);
1532
+ else if (!anchor
1533
+ && stream.match(new RegExp(String.raw `^(?:[>[\]]|%[\da-f]{2}|${lookahead(chars, state)})+`, 'iu'))) {
1534
+ return makeLocalTagStyle('error', state);
1535
+ }
1536
+ else if (!anchor && stream.peek() === '<') {
1537
+ pop(state);
1538
+ return makeLocalStyle('', state, 'nTemplate');
1539
+ }
1540
+ else if (space && !haveEaten) {
1541
+ return makeLocalStyle('', state);
1542
+ }
1543
+ else if (stream.match(re)) {
1544
+ if (!haveEaten) {
1545
+ state.tokenize = this.inTemplatePageName(true, anchor);
1546
+ }
1547
+ return makeLocalStyle(style, state);
1548
+ }
1549
+ return space
1550
+ ? makeLocalStyle(style, state)
1551
+ : this.eatWikiText(style)(stream, state);
1552
+ };
1553
+ }
1554
+ inParserFunctionArgument(module, n = module ?? Infinity, ns = 0) {
1555
+ if (n === 0) {
1556
+ return this.inTemplateArgument(true, true);
1557
+ }
1558
+ const chars = n === 2 ? '}{<' : "}{<~'_-"; // `#invoke`/`#tag`
1559
+ let style = `${tokens.parserFunction} ${module ? tokens.pageName : ''}`;
1560
+ switch (module) {
1561
+ case 1:
1562
+ style += ' mw-function-274';
1563
+ break;
1564
+ case 2:
1565
+ style += ' mw-function-828';
1566
+ break;
1567
+ case Infinity:
1568
+ style += ` mw-function-${ns}`;
1569
+ // no default
1561
1570
  }
1562
- return !isSolSyntax(stream, true) && stream.match(this.convertRegex) || space
1563
- ? makeStyle(style, state)
1564
- : this.eatWikiText(style)(stream, state);
1565
- };
1566
- }
1567
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
1568
- eatEntity(stream, style) {
1569
- const entity = stream.match(/^(?:#x[a-f\d]+|#\d+|[a-z\d]+);/iu);
1570
- return entity && isHtmlEntity(entity[0]) ? tokens.htmlEntity : style;
1571
- }
1572
- /**
1573
- * main entry
1574
- *
1575
- * @see https://codemirror.net/docs/ref/#language.StreamParser
1576
- *
1577
- * @param tags
1578
- */
1579
- mediawiki(tags) {
1580
- return {
1581
- startState: () => startState(this.eatWikiText(''), tags ?? this.tags, tags === undefined),
1582
- copyState,
1583
- token(stream, state) {
1584
- const { data } = state, { readyTokens } = data;
1585
- let { oldToken } = data;
1586
- while (oldToken
1587
- && (
1588
- // 如果 PartialParse 的起点位于当前位置之后
1589
- stream.pos > oldToken.pos
1590
- || stream.pos === oldToken.pos && state.tokenize !== oldToken.state.tokenize)) {
1591
- oldToken = readyTokens.shift();
1592
- }
1593
- if (
1594
- // 检查起点
1595
- stream.pos === oldToken?.pos
1596
- && stream.string === oldToken.string
1597
- && cmpNesting(state, oldToken.state)) {
1598
- const { pos, string, state: { bold, italic, ...other }, style } = readyTokens[0];
1599
- Object.assign(state, other);
1600
- if (!(state.extName && state.extMode)
1601
- && state.nLink === 0
1602
- && typeof style === 'string'
1603
- && style.includes(tokens.apostrophes)) {
1604
- if (data.mark === pos) {
1605
- // rollback
1606
- data.mark = null;
1607
- // add one apostrophe, next token will be italic (two apostrophes)
1608
- stream.string = string.slice(0, pos - 2);
1609
- const s = state.tokenize(stream, state);
1610
- stream.string = string;
1611
- oldToken.pos++;
1612
- data.oldToken = oldToken;
1613
- return makeFullStyle(s, state);
1614
- }
1615
- const length = pos - stream.pos;
1616
- if (length !== 3) {
1617
- state.italic = !state.italic;
1618
- }
1619
- if (length !== 2) {
1620
- state.bold = !state.bold;
1621
- }
1571
+ return (stream, state) => {
1572
+ if (stream.eat('|')) {
1573
+ if (module) {
1574
+ state.tokenize = this.inParserFunctionArgument(undefined, module - 1);
1622
1575
  }
1623
- else if (typeof style === 'string' && style.includes(tokens.tableDelimiter)) {
1624
- state.bold = false;
1625
- state.italic = false;
1576
+ else if (n !== Infinity) {
1577
+ state.tokenize = this.inParserFunctionArgument(undefined, n - 1);
1626
1578
  }
1627
- // return first saved token
1628
- data.oldToken = readyTokens.shift();
1629
- stream.pos = pos;
1630
- stream.string = string;
1631
- return makeFullStyle(style, state);
1579
+ return makeLocalTagStyle('parserFunctionDelimiter', state);
1632
1580
  }
1633
- else if (stream.sol()) {
1634
- // reset bold and italic status in every new line
1635
- state.bold = false;
1636
- state.italic = false;
1637
- state.dt.n = 0;
1638
- state.dt.html = 0;
1639
- data.firstSingleLetterWord = null;
1640
- data.firstMultiLetterWord = null;
1641
- data.firstSpace = null;
1642
- if (state.tokenize.name === 'inExtTokens') {
1643
- pop(state); // dispose inExtTokens
1644
- pop(state); // dispose eatExtTagArea
1645
- state.extName = false;
1646
- state.extMode = false;
1647
- state.extState = false;
1581
+ else if (stream.match('}}')) {
1582
+ pop(state);
1583
+ return makeLocalTagStyle('parserFunctionBracket', state, 'nExt');
1584
+ }
1585
+ return !isSolSyntax(stream)
1586
+ && stream.match(parserFunctionRegex[module ? 0 : Number(needColon(state)) + 1](chars))
1587
+ ? makeLocalStyle(style, state)
1588
+ : this.eatWikiText('parserFunction')(stream, state);
1589
+ };
1590
+ }
1591
+ inTemplateArgument(expectName, parserFunction) {
1592
+ const tag = parserFunction ? 'parserFunction' : 'template';
1593
+ return (stream, state) => {
1594
+ const space = stream.eatSpace();
1595
+ if (stream.eol()) {
1596
+ return makeLocalTagStyle(tag, state);
1597
+ }
1598
+ else if (stream.eat('|')) {
1599
+ if (!expectName) {
1600
+ state.tokenize = this.inTemplateArgument(true, parserFunction);
1648
1601
  }
1602
+ return makeLocalTagStyle(parserFunction ? 'parserFunctionDelimiter' : 'templateDelimiter', state);
1649
1603
  }
1650
- readyTokens.length = 0;
1651
- data.mark = null;
1652
- data.oldToken = { pos: stream.pos, string: stream.string, state: copyState(state), style: '' };
1653
- const { start } = stream;
1654
- do {
1655
- // get token style
1656
- stream.start = stream.pos;
1657
- const char = stream.peek(), style = state.tokenize(stream, state);
1658
- if (typeof style === 'string' && style.includes(tokens.templateArgumentName)) {
1659
- for (let i = readyTokens.length - 1; i >= 0; i--) {
1660
- const token = readyTokens[i];
1661
- if (cmpNesting(state, token.state, true)) {
1662
- const types = typeof token.style === 'string' && token.style.split(' '), j = types && types.indexOf(tokens.template);
1663
- if (j !== false && j !== -1) {
1664
- types[j] = tokens.templateArgumentName;
1665
- token.style = types.join(' ');
1666
- }
1667
- else if (types && types.includes(tokens.templateDelimiter)) {
1668
- break;
1669
- }
1604
+ else if (stream.match('}}', false)) {
1605
+ if (space) {
1606
+ return makeLocalTagStyle(tag, state);
1607
+ }
1608
+ stream.pos += 2;
1609
+ pop(state);
1610
+ return makeLocalTagStyle(parserFunction ? 'parserFunctionBracket' : 'templateBracket', state, parserFunction ? 'nExt' : 'nTemplate');
1611
+ }
1612
+ else if (stream.sol() && stream.peek() === '=') {
1613
+ const style = this.eatWikiText(tag)(stream, state);
1614
+ if (style.includes(tokens.sectionHeader)) {
1615
+ return style;
1616
+ }
1617
+ stream.pos = 0;
1618
+ }
1619
+ if (expectName
1620
+ && stream.match(new RegExp(`^(?:[^=|}{[<]|${lookahead('}{[<', state)})*=`, 'iu'))) {
1621
+ state.tokenize = this.inTemplateArgument(false, parserFunction);
1622
+ return makeLocalTagStyle('templateArgumentName', state);
1623
+ }
1624
+ else if (isSolSyntax(stream) && stream.peek() !== '=') {
1625
+ return this.eatWikiText(tag)(stream, state);
1626
+ }
1627
+ return stream.match(needColon(state) ? argumentRegex : styleRegex) || space
1628
+ ? makeLocalTagStyle(tag, state)
1629
+ : this.eatWikiText(tag)(stream, state);
1630
+ };
1631
+ }
1632
+ inConvert(style, needFlag, needLang = true, plain) {
1633
+ return (stream, state) => {
1634
+ const space = stream.eatSpace();
1635
+ if (stream.match('}-')) {
1636
+ pop(state);
1637
+ return makeLocalTagStyle('convertBracket', state);
1638
+ }
1639
+ else if (needFlag && stream.match(/^[;\sa-z-]*(?=\|)/iu)) {
1640
+ chain(state, this.inConvert(style, false, true, plain));
1641
+ state.tokenize = this.inStr('|', 'convertDelimiter');
1642
+ return makeLocalTagStyle('convertFlag', state);
1643
+ }
1644
+ else if (stream.match(this.convertSemicolon)) {
1645
+ if (needFlag || !needLang) {
1646
+ state.tokenize = this.inConvert(style, false, true, plain);
1647
+ }
1648
+ return makeLocalTagStyle('convertDelimiter', state);
1649
+ }
1650
+ else if (needLang && stream.match(this.convertLang)) {
1651
+ state.tokenize = this.inConvert(style, false, false, plain);
1652
+ return makeLocalTagStyle('convertLang', state);
1653
+ }
1654
+ else if (plain) {
1655
+ if (stream.match('-{', false)) {
1656
+ return this.eatWikiText(style)(stream, state);
1657
+ }
1658
+ stream.match(/^(?:(?:[^};=-]|\}(?!-)|=(?!>)|-(?!\{))+|;|=>)/u);
1659
+ return makeStyle(style, state);
1660
+ }
1661
+ return !isSolSyntax(stream, true) && stream.match(this.convertRegex) || space
1662
+ ? makeStyle(style, state)
1663
+ : this.eatWikiText(style)(stream, state);
1664
+ };
1665
+ }
1666
+ // eslint-disable-next-line @typescript-eslint/class-methods-use-this
1667
+ eatEntity(stream, style) {
1668
+ const entity = stream.match(/^(?:#x[a-f\d]+|#\d+|[a-z\d]+);/iu);
1669
+ return entity && isHtmlEntity(entity[0]) ? tokens.htmlEntity : style;
1670
+ }
1671
+ /**
1672
+ * main entry
1673
+ *
1674
+ * @see https://codemirror.net/docs/ref/#language.StreamParser
1675
+ *
1676
+ * @param tags
1677
+ */
1678
+ mediawiki(tags) {
1679
+ return {
1680
+ startState: () => startState(this.eatWikiText(''), tags ?? this.tags, tags === undefined),
1681
+ copyState,
1682
+ token(stream, state) {
1683
+ const { data } = state, { readyTokens } = data;
1684
+ let { oldToken } = data;
1685
+ while (oldToken
1686
+ && (
1687
+ // 如果 PartialParse 的起点位于当前位置之后
1688
+ stream.pos > oldToken.pos
1689
+ || stream.pos === oldToken.pos && state.tokenize !== oldToken.state.tokenize)) {
1690
+ oldToken = readyTokens.shift();
1691
+ }
1692
+ if (
1693
+ // 检查起点
1694
+ stream.pos === oldToken?.pos
1695
+ && stream.string === oldToken.string
1696
+ && cmpNesting(state, oldToken.state)) {
1697
+ const { pos, string, state: { bold, italic, ...other }, style } = readyTokens[0];
1698
+ Object.assign(state, other);
1699
+ if (!(state.extName && state.extMode)
1700
+ && state.nLink === 0
1701
+ && typeof style === 'string'
1702
+ && style.includes(tokens.apostrophes)) {
1703
+ if (data.mark === pos) {
1704
+ // rollback
1705
+ data.mark = null;
1706
+ // add one apostrophe, next token will be italic (two apostrophes)
1707
+ stream.string = string.slice(0, pos - 2);
1708
+ const s = state.tokenize(stream, state);
1709
+ stream.string = string;
1710
+ oldToken.pos++;
1711
+ data.oldToken = oldToken;
1712
+ return makeFullStyle(s, state);
1713
+ }
1714
+ const length = pos - stream.pos;
1715
+ if (length !== 3) {
1716
+ state.italic = !state.italic;
1670
1717
  }
1718
+ if (length !== 2) {
1719
+ state.bold = !state.bold;
1720
+ }
1721
+ }
1722
+ else if (typeof style === 'string' && style.includes(tokens.tableDelimiter)) {
1723
+ state.bold = false;
1724
+ state.italic = false;
1671
1725
  }
1726
+ // return first saved token
1727
+ data.oldToken = readyTokens.shift();
1728
+ stream.pos = pos;
1729
+ stream.string = string;
1730
+ return makeFullStyle(style, state);
1672
1731
  }
1673
- else if (typeof style === 'string' && style.includes(tokens.tableDelimiter2)) {
1674
- for (let i = readyTokens.length - 1; i >= 0; i--) {
1675
- const token = readyTokens[i];
1676
- if (cmpNesting(state, token.state, true)) {
1677
- const { style: s } = token, local = typeof s === 'string', type = !local
1678
- && s[0].split(' ')
1679
- .find(t => t && !t.endsWith('-ground'));
1680
- if (type && type.startsWith('mw-table-')) {
1681
- token.style = `${s[0].replace('mw-table-', 'mw-html-')} ${tokens.tableDefinition}`;
1682
- }
1683
- else if (local && s.includes(tokens.tableDelimiter)) {
1684
- break;
1732
+ else if (stream.sol()) {
1733
+ // reset bold and italic status in every new line
1734
+ state.bold = false;
1735
+ state.italic = false;
1736
+ state.dt.n = 0;
1737
+ state.dt.html = 0;
1738
+ data.firstSingleLetterWord = null;
1739
+ data.firstMultiLetterWord = null;
1740
+ data.firstSpace = null;
1741
+ if (state.tokenize.name === 'inExtTokens') {
1742
+ pop(state); // dispose inExtTokens
1743
+ pop(state); // dispose eatExtTagArea
1744
+ state.extName = false;
1745
+ state.extMode = false;
1746
+ state.extState = false;
1747
+ }
1748
+ }
1749
+ readyTokens.length = 0;
1750
+ data.mark = null;
1751
+ data.oldToken = { pos: stream.pos, string: stream.string, state: copyState(state), style: '' };
1752
+ const { start } = stream;
1753
+ do {
1754
+ // get token style
1755
+ stream.start = stream.pos;
1756
+ const char = stream.peek(), style = state.tokenize(stream, state);
1757
+ if (typeof style === 'string' && style.includes(tokens.templateArgumentName)) {
1758
+ for (let i = readyTokens.length - 1; i >= 0; i--) {
1759
+ const token = readyTokens[i];
1760
+ if (cmpNesting(state, token.state, true)) {
1761
+ const types = typeof token.style === 'string' && token.style.split(' '), j = types && types.indexOf(tokens.template);
1762
+ if (j !== false && j !== -1) {
1763
+ types[j] = tokens.templateArgumentName;
1764
+ token.style = types.join(' ');
1765
+ }
1766
+ else if (types && types.includes(tokens.templateDelimiter)) {
1767
+ break;
1768
+ }
1685
1769
  }
1686
1770
  }
1687
1771
  }
1688
- }
1689
- else if (char === '|' && typeof style === 'string' && style.includes(tokens.convertDelimiter)) {
1690
- let count = 0;
1691
- for (let i = readyTokens.length - 1; i >= 0; i--) {
1692
- const token = readyTokens[i];
1693
- if (cmpNesting(state, token.state, true)) {
1694
- const { style: s } = token;
1695
- if (typeof s === 'string' && s.includes(tokens.convertBracket)) {
1696
- count += token.char === '-' ? 1 : -1;
1697
- if (count === 1) {
1772
+ else if (typeof style === 'string' && style.includes(tokens.tableDelimiter2)) {
1773
+ for (let i = readyTokens.length - 1; i >= 0; i--) {
1774
+ const token = readyTokens[i];
1775
+ if (cmpNesting(state, token.state, true)) {
1776
+ const { style: s } = token, local = typeof s === 'string', type = !local
1777
+ && s[0].split(' ')
1778
+ .find(t => t && !t.endsWith('-ground'));
1779
+ if (type && type.startsWith('mw-table-')) {
1780
+ token.style = `${s[0].replace('mw-table-', 'mw-html-')} ${tokens.tableDefinition}`;
1781
+ }
1782
+ else if (local && s.includes(tokens.tableDelimiter)) {
1698
1783
  break;
1699
1784
  }
1700
1785
  }
1701
- else if (typeof s === 'object') {
1702
- token.style = s[0]
1703
- + (s[0].includes(tokens.convertFlag) ? '' : ` ${tokens.convertFlag}`);
1786
+ }
1787
+ }
1788
+ else if (char === '|' && typeof style === 'string' && style.includes(tokens.convertDelimiter)) {
1789
+ let count = 0;
1790
+ for (let i = readyTokens.length - 1; i >= 0; i--) {
1791
+ const token = readyTokens[i];
1792
+ if (cmpNesting(state, token.state, true)) {
1793
+ const { style: s } = token;
1794
+ if (typeof s === 'string' && s.includes(tokens.convertBracket)) {
1795
+ count += token.char === '-' ? 1 : -1;
1796
+ if (count === 1) {
1797
+ break;
1798
+ }
1799
+ }
1800
+ else if (typeof s === 'object') {
1801
+ token.style = s[0]
1802
+ + (s[0].includes(tokens.convertFlag) ? '' : ` ${tokens.convertFlag}`);
1803
+ }
1704
1804
  }
1705
1805
  }
1706
1806
  }
1807
+ // save token
1808
+ readyTokens.push({ pos: stream.pos, char, string: stream.string, state: copyState(state), style });
1809
+ } while ( /** @todo should end at table delimiter as well */!stream.eol());
1810
+ if (!state.bold || !state.italic) {
1811
+ // no need to rollback
1812
+ data.mark = null;
1707
1813
  }
1708
- // save token
1709
- readyTokens.push({ pos: stream.pos, char, string: stream.string, state: copyState(state), style });
1710
- } while ( /** @todo should end at table delimiter as well */!stream.eol());
1711
- if (!state.bold || !state.italic) {
1712
- // no need to rollback
1713
- data.mark = null;
1714
- }
1715
- stream.start = start;
1716
- stream.pos = data.oldToken.pos;
1717
- stream.string = data.oldToken.string;
1718
- Object.assign(state, data.oldToken.state);
1719
- return '';
1720
- },
1721
- blankLine(state) {
1722
- if (state.extName && state.extMode && state.extMode.blankLine) {
1723
- state.extMode.blankLine(state.extState, 0);
1724
- }
1725
- },
1726
- indent(state, textAfter, context) {
1727
- return state.extName && state.extMode && state.extMode.indent
1728
- ? state.extMode.indent(state.extState, textAfter, context)
1729
- : null;
1730
- },
1731
- ...tags
1732
- ? undefined
1733
- : {
1734
- tokenTable: {
1735
- ...this.tokenTable,
1736
- ...this.hiddenTable,
1737
- '': Tag.define(),
1814
+ stream.start = start;
1815
+ stream.pos = data.oldToken.pos;
1816
+ stream.string = data.oldToken.string;
1817
+ Object.assign(state, data.oldToken.state);
1818
+ return '';
1819
+ },
1820
+ blankLine(state) {
1821
+ if (state.extName && state.extMode && state.extMode.blankLine) {
1822
+ state.extMode.blankLine(state.extState, 0);
1823
+ }
1824
+ },
1825
+ indent(state, textAfter, context) {
1826
+ return state.extName && state.extMode && state.extMode.indent
1827
+ ? state.extMode.indent(state.extState, textAfter, context)
1828
+ : null;
1829
+ },
1830
+ ...tags
1831
+ ? undefined
1832
+ : {
1833
+ tokenTable: {
1834
+ ...this.tokenTable,
1835
+ ...this.hiddenTable,
1836
+ '': Tag.define(),
1837
+ },
1738
1838
  },
1839
+ };
1840
+ }
1841
+ 'text/mediawiki'(tags) {
1842
+ return this.mediawiki(tags);
1843
+ }
1844
+ 'text/nowiki'() {
1845
+ return {
1846
+ startState() {
1847
+ return {};
1739
1848
  },
1740
- };
1741
- }
1742
- 'text/mediawiki'(tags) {
1743
- return this.mediawiki(tags);
1744
- }
1745
- 'text/nowiki'() {
1746
- return {
1747
- startState() {
1748
- return {};
1749
- },
1750
- token: (stream) => {
1751
- if (stream.eatWhile(/[^&]/u)) {
1752
- return '';
1849
+ token: (stream) => {
1850
+ if (stream.eatWhile(/[^&]/u)) {
1851
+ return '';
1852
+ }
1853
+ // eat &
1854
+ stream.next();
1855
+ return this.eatEntity(stream, '');
1856
+ },
1857
+ };
1858
+ }
1859
+ inPre(begin) {
1860
+ return (stream, state) => {
1861
+ if (stream.match(begin ? /^<\/nowiki>/iu : /^<nowiki>/iu)) {
1862
+ state.tokenize = this.inPre(!begin);
1863
+ return tokens.ignored;
1753
1864
  }
1754
- // eat &
1755
- stream.next();
1756
- return this.eatEntity(stream, '');
1757
- },
1758
- };
1759
- }
1760
- @getTokenizer
1761
- inPre(begin) {
1762
- return (stream, state) => {
1763
- if (stream.match(begin ? /^<\/nowiki>/iu : /^<nowiki>/iu)) {
1764
- state.tokenize = this.inPre(!begin);
1765
- return tokens.comment;
1766
- }
1767
- else if (this.hasVariants && stream.match('-{')) {
1768
- chain(state, this.inConvert('', true, true, true));
1769
- return tokens.convertBracket;
1770
- }
1771
- else if (stream.eat('&')) {
1772
- return this.eatEntity(stream, '');
1773
- }
1774
- stream.match(this.preRegex[begin ? 1 : 0]);
1775
- return '';
1776
- };
1777
- }
1778
- 'text/pre'() {
1779
- return {
1780
- startState: () => startState(this.inPre(), []),
1781
- token: simpleToken,
1782
- };
1783
- }
1784
- @getTokenizer
1785
- inNested(tag) {
1786
- const re = tag === 'ref' ? /^(?:\{|(?:[^<{]|\{(?!\{)|<(?!!--|ref(?:[\s/>]|$)))+)/iu : getNestedRegex(tag);
1787
- return (stream, state) => {
1788
- if (tag === 'ref') {
1865
+ else if (this.hasVariants && stream.match('-{')) {
1866
+ chain(state, this.inConvert('', true, true, true));
1867
+ return tokens.convertBracket;
1868
+ }
1869
+ else if (stream.eat('&')) {
1870
+ return this.eatEntity(stream, '');
1871
+ }
1872
+ stream.match(this.preRegex[begin ? 1 : 0]);
1873
+ return '';
1874
+ };
1875
+ }
1876
+ 'text/pre'() {
1877
+ return {
1878
+ startState: () => startState(this.inPre(), []),
1879
+ token: simpleToken,
1880
+ };
1881
+ }
1882
+ inNested(tag) {
1883
+ const re = tag === 'ref' ? /^(?:\{|(?:[^<{]|\{(?!\{)|<(?!!--|ref(?:[\s/>]|$)))+)/iu : getNestedRegex(tag);
1884
+ return (stream, state) => {
1885
+ if (tag === 'ref') {
1886
+ if (stream.match('<!--')) {
1887
+ chain(state, this.inComment);
1888
+ return makeLocalTagStyle('comment', state);
1889
+ }
1890
+ else if (stream.match(/^\{{3}(?!\{|[^{}]*\}\}(?!\}))\s*/u)) {
1891
+ chain(state, this.inVariable());
1892
+ return tokens.templateVariableBracket;
1893
+ }
1894
+ const mt = stream.match(/^\{\{(?!\{(?!\{))/u);
1895
+ if (mt) {
1896
+ return this.eatTransclusion(stream, state) ?? tokens.ignored;
1897
+ }
1898
+ }
1899
+ if (stream.match(re)) {
1900
+ return tokens.ignored;
1901
+ }
1902
+ stream.eat('<');
1903
+ chain(state, this.eatTagName(tag));
1904
+ return tokens.extTagBracket;
1905
+ };
1906
+ }
1907
+ 'text/references'(tags) {
1908
+ return {
1909
+ startState: () => startState(this.inNested('ref'), tags),
1910
+ token: simpleToken,
1911
+ };
1912
+ }
1913
+ 'text/choose'(tags) {
1914
+ return {
1915
+ startState: () => startState(this.inNested('option'), tags),
1916
+ token: simpleToken,
1917
+ };
1918
+ }
1919
+ 'text/combobox'(tags) {
1920
+ return {
1921
+ startState: () => startState(this.inNested('combooption'), tags),
1922
+ token: simpleToken,
1923
+ };
1924
+ }
1925
+ get inInputbox() {
1926
+ return (stream, state) => {
1789
1927
  if (stream.match('<!--')) {
1790
1928
  chain(state, this.inComment);
1791
- return makeLocalTagStyle('comment', state);
1929
+ return tokens.comment;
1792
1930
  }
1793
- else if (stream.match(/^\{{3}(?!\{|[^{}]*\}\}(?!\}))\s*/u)) {
1794
- chain(state, this.inVariable());
1795
- return tokens.templateVariableBracket;
1931
+ /** @todo braces should also be parsed */
1932
+ stream.match(/^(?:[^<]|<(?!!--))+/u);
1933
+ return '';
1934
+ };
1935
+ }
1936
+ 'text/inputbox'() {
1937
+ return {
1938
+ startState: () => startState(this.inInputbox, []),
1939
+ token: simpleToken,
1940
+ };
1941
+ }
1942
+ inGallery(section) {
1943
+ const style = section ? tokens.error : `${tokens.linkPageName} ${tokens.pageName}`, regex = section ? /^(?:[[}\]]|\{(?!\{))+/u : /^(?:[>[}\]]|\{(?!\{)|<(?!!--))+/u, re = section ? /^(?:[^|<[\]{}]|<(?!!--))+/u : /^(?:&#(?:\d+|x[a-f\d]+);|[^#|<>[\]{}])+/u;
1944
+ return (stream, state) => {
1945
+ const space = stream.eatSpace();
1946
+ if (!section && stream.match(/^#\s*/u)) {
1947
+ state.tokenize = this.inGallery(true);
1948
+ return makeTagStyle('error', state);
1796
1949
  }
1797
- const mt = stream.match(/^\{\{(?!\{(?!\{))/u);
1798
- if (mt) {
1799
- return this.eatTransclusion(stream, state) ?? tokens.comment;
1950
+ else if (stream.match(/^\|\s*/u)) {
1951
+ state.tokenize = this.inLinkText(true, true);
1952
+ this.toEatImageParameter(stream, state);
1953
+ return makeLocalTagStyle('fileDelimiter', state);
1800
1954
  }
1801
- }
1802
- if (stream.match(re)) {
1803
- return tokens.comment;
1804
- }
1805
- stream.eat('<');
1806
- chain(state, this.eatTagName(tag));
1807
- return tokens.extTagBracket;
1808
- };
1809
- }
1810
- 'text/references'(tags) {
1811
- return {
1812
- startState: () => startState(this.inNested('ref'), tags),
1813
- token: simpleToken,
1814
- };
1815
- }
1816
- 'text/choose'(tags) {
1817
- return {
1818
- startState: () => startState(this.inNested('option'), tags),
1819
- token: simpleToken,
1820
- };
1821
- }
1822
- 'text/combobox'(tags) {
1823
- return {
1824
- startState: () => startState(this.inNested('combooption'), tags),
1825
- token: simpleToken,
1826
- };
1827
- }
1828
- @getTokenizer
1829
- get inInputbox() {
1830
- return (stream, state) => {
1831
- if (stream.match('<!--')) {
1832
- chain(state, this.inComment);
1833
- return tokens.comment;
1834
- }
1835
- /** @todo braces should also be parsed */
1836
- stream.match(/^(?:[^<]|<(?!!--))+/u);
1837
- return '';
1838
- };
1839
- }
1840
- 'text/inputbox'() {
1841
- return {
1842
- startState: () => startState(this.inInputbox, []),
1843
- token: simpleToken,
1844
- };
1845
- }
1846
- @getTokenizer
1847
- inGallery(section) {
1848
- const style = section ? tokens.error : `${tokens.linkPageName} ${tokens.pageName}`, regex = section ? /^(?:[[}\]]|\{(?!\{))+/u : /^(?:[>[}\]]|\{(?!\{)|<(?!!--))+/u, re = section ? /^(?:[^|<[\]{}]|<(?!!--))+/u : /^(?:&#(?:\d+|x[a-f\d]+);|[^#|<>[\]{}])+/u;
1849
- return (stream, state) => {
1850
- const space = stream.eatSpace();
1851
- if (!section && stream.match(/^#\s*/u)) {
1852
- state.tokenize = this.inGallery(true);
1853
- return makeTagStyle('error', state);
1854
- }
1855
- else if (stream.match(/^\|\s*/u)) {
1856
- state.tokenize = this.inLinkText(true, true);
1857
- this.toEatImageParameter(stream, state);
1858
- return makeLocalTagStyle('fileDelimiter', state);
1859
- }
1860
- else if (stream.match(regex)) {
1861
- return makeTagStyle('error', state);
1862
- }
1863
- return stream.match(re) || space
1864
- ? makeStyle(style, state)
1865
- : this.eatWikiText(section ? style : 'error')(stream, state);
1866
- };
1867
- }
1868
- 'text/gallery'(tags) {
1869
- return {
1870
- startState: () => startState(this.inGallery(), tags),
1871
- token: (stream, state) => {
1872
- if (stream.sol()) {
1873
- Object.assign(state, startState(this.inGallery(), state.data.tags));
1955
+ else if (stream.match(regex)) {
1956
+ return makeTagStyle('error', state);
1874
1957
  }
1875
- return simpleToken(stream, state);
1876
- },
1877
- };
1878
- }
1879
- javascript() {
1880
- return javascript;
1881
- }
1882
- css() {
1883
- return css;
1884
- }
1885
- json() {
1886
- return json;
1887
- }
1888
- }
1958
+ return stream.match(re) || space
1959
+ ? makeStyle(style, state)
1960
+ : this.eatWikiText(section ? style : 'error')(stream, state);
1961
+ };
1962
+ }
1963
+ 'text/gallery'(tags) {
1964
+ return {
1965
+ startState: () => startState(this.inGallery(), tags),
1966
+ token: (stream, state) => {
1967
+ if (stream.sol()) {
1968
+ Object.assign(state, startState(this.inGallery(), state.data.tags));
1969
+ }
1970
+ return simpleToken(stream, state);
1971
+ },
1972
+ };
1973
+ }
1974
+ javascript() {
1975
+ return javascript;
1976
+ }
1977
+ css() {
1978
+ return css;
1979
+ }
1980
+ json() {
1981
+ return json;
1982
+ }
1983
+ };
1984
+ })();
1985
+ export { MediaWiki };