Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F1432625
codemirror.mediawiki.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
39 KB
Referenced Files
None
Subscribers
None
codemirror.mediawiki.js
View Options
const
{
HighlightStyle
,
LanguageSupport
,
StreamLanguage
,
StreamParser
,
StringStream
,
Tag
,
syntaxHighlighting
}
=
require
(
'ext.CodeMirror.v6.lib'
);
const
mwModeConfig
=
require
(
'./codemirror.mediawiki.config.js'
);
const
bidiIsolationExtension
=
require
(
'./codemirror.mediawiki.bidiIsolation.js'
);
const
templateFoldingExtension
=
require
(
'./codemirror.mediawiki.templateFolding.js'
);
/**
* MediaWiki language support for CodeMirror 6.
* Adapted from the original CodeMirror 5 stream parser by Pavel Astakhov.
*
* @module CodeMirrorModeMediaWiki
*
* @example
* mw.loader.using( [
* 'ext.CodeMirror.v6',
* 'ext.CodeMirror.v6.mode.mediawiki'
* ] ).then( ( require ) => {
* const CodeMirror = require( 'ext.CodeMirror.v6' );
* const mediawikiLang = require( 'ext.CodeMirror.v6.mode.mediawiki' );
* const cm = new CodeMirror( myTextarea );
* cm.initialize( [ cm.defaultExtensions, mediawikiLang() ] );
* } );
*/
class
CodeMirrorModeMediaWiki
{
/**
* @param {Object} config MediaWiki configuration as generated by DataScript.php
* @internal
*/
constructor
(
config
)
{
this
.
config
=
config
;
this
.
urlProtocols
=
new
RegExp
(
`^(?:
${
this
.
config
.
urlProtocols
}
)(?=[^\\s\u00a0{[\\]<>~).,'])`
,
'i'
);
this
.
isBold
=
false
;
this
.
wasBold
=
false
;
this
.
isItalic
=
false
;
this
.
wasItalic
=
false
;
this
.
firstSingleLetterWord
=
null
;
this
.
firstMultiLetterWord
=
null
;
this
.
firstSpace
=
null
;
this
.
oldStyle
=
null
;
this
.
tokens
=
[];
this
.
oldTokens
=
[];
this
.
tokenTable
=
mwModeConfig
.
tokenTable
;
this
.
registerGroundTokens
();
// Dynamically register any tags that aren't already in CodeMirrorModeMediaWikiConfig
Object
.
keys
(
this
.
config
.
tags
).
forEach
(
(
tag
)
=>
mwModeConfig
.
addTag
(
tag
)
);
}
/**
* Register the ground tokens. These aren't referenced directly in the StreamParser, nor do
* they have a parent Tag, so we don't need them as constants like we do for other tokens.
* See this.makeLocalStyle() for how these tokens are used.
*
* @private
*/
registerGroundTokens
()
{
[
'mw-ext-ground'
,
'mw-ext-link-ground'
,
'mw-ext2-ground'
,
'mw-ext2-link-ground'
,
'mw-ext3-ground'
,
'mw-ext3-link-ground'
,
'mw-link-ground'
,
'mw-template-ext-ground'
,
'mw-template-ext-link-ground'
,
'mw-template-ext2-ground'
,
'mw-template-ext2-link-ground'
,
'mw-template-ext3-ground'
,
'mw-template-ext3-link-ground'
,
'mw-template-ground'
,
'mw-template-link-ground'
,
'mw-template2-ext-ground'
,
'mw-template2-ext-link-ground'
,
'mw-template2-ext2-ground'
,
'mw-template2-ext2-link-ground'
,
'mw-template2-ext3-ground'
,
'mw-template2-ext3-link-ground'
,
'mw-template2-ground'
,
'mw-template2-link-ground'
,
'mw-template3-ext-ground'
,
'mw-template3-ext-link-ground'
,
'mw-template3-ext2-ground'
,
'mw-template3-ext2-link-ground'
,
'mw-template3-ext3-ground'
,
'mw-template3-ext3-link-ground'
,
'mw-template3-ground'
,
'mw-template3-link-ground'
].
forEach
(
(
ground
)
=>
mwModeConfig
.
addToken
(
ground
)
);
}
eatHtmlEntity
(
stream
,
style
)
{
let
ok
;
if
(
stream
.
eat
(
'#'
)
)
{
if
(
stream
.
eat
(
'x'
)
)
{
ok
=
stream
.
eatWhile
(
/[a-fA-F\d]/
)
&&
stream
.
eat
(
';'
);
}
else
{
ok
=
stream
.
eatWhile
(
/[\d]/
)
&&
stream
.
eat
(
';'
);
}
}
else
{
ok
=
stream
.
eatWhile
(
/[\w.\-:]/
)
&&
stream
.
eat
(
';'
);
}
if
(
ok
)
{
return
mwModeConfig
.
tags
.
htmlEntity
;
}
return
style
;
}
isNested
(
state
)
{
return
state
.
nExt
>
0
||
state
.
nTemplate
>
0
||
state
.
nLink
>
0
;
}
makeStyle
(
style
,
state
,
endGround
)
{
if
(
this
.
isBold
||
state
.
nDt
>
0
)
{
style
+=
' '
+
mwModeConfig
.
tags
.
strong
;
}
if
(
this
.
isItalic
)
{
style
+=
' '
+
mwModeConfig
.
tags
.
em
;
}
return
this
.
makeLocalStyle
(
style
,
state
,
endGround
);
}
makeLocalStyle
(
style
,
state
,
endGround
)
{
let
ground
=
''
;
switch
(
state
.
nTemplate
)
{
case
0
:
break
;
case
1
:
ground
+=
'-template'
;
break
;
case
2
:
ground
+=
'-template2'
;
break
;
default
:
ground
+=
'-template3'
;
break
;
}
switch
(
state
.
nExt
)
{
case
0
:
break
;
case
1
:
ground
+=
'-ext'
;
break
;
case
2
:
ground
+=
'-ext2'
;
break
;
default
:
ground
+=
'-ext3'
;
break
;
}
if
(
state
.
nLink
>
0
)
{
ground
+=
'-link'
;
}
if
(
ground
!==
''
)
{
style
=
`mw
${
ground
}
-ground
${
style
}
`
;
}
if
(
endGround
)
{
state
[
endGround
]
--
;
}
return
style
.
trim
();
}
eatBlock
(
style
,
terminator
,
consumeLast
)
{
return
(
stream
,
state
)
=>
{
if
(
stream
.
skipTo
(
terminator
)
)
{
if
(
consumeLast
!==
false
)
{
stream
.
match
(
terminator
);
}
state
.
tokenize
=
state
.
stack
.
pop
();
}
else
{
stream
.
skipToEnd
();
}
return
this
.
makeLocalStyle
(
style
,
state
);
};
}
eatEnd
(
style
)
{
return
(
stream
,
state
)
=>
{
stream
.
skipToEnd
();
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
style
,
state
);
};
}
eatChar
(
char
,
style
)
{
return
(
stream
,
state
)
=>
{
state
.
tokenize
=
state
.
stack
.
pop
();
if
(
stream
.
eat
(
char
)
)
{
return
this
.
makeLocalStyle
(
style
,
state
);
}
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
error
,
state
);
};
}
eatSectionHeader
(
count
)
{
return
(
stream
,
state
)
=>
{
if
(
stream
.
match
(
/^[^&<[{~]+/
)
)
{
if
(
stream
.
eol
()
)
{
stream
.
backUp
(
count
);
state
.
tokenize
=
this
.
eatEnd
(
mwModeConfig
.
tags
.
sectionHeader
);
}
else
if
(
stream
.
match
(
/^<!--(?!.*?-->.*?=)/
,
false
)
)
{
// T171074: handle trailing comments
stream
.
backUp
(
count
);
state
.
tokenize
=
this
.
eatBlock
(
mwModeConfig
.
tags
.
sectionHeader
,
'<!--'
,
false
);
}
return
mwModeConfig
.
tags
.
section
;
// style is null
}
return
this
.
eatWikiText
(
mwModeConfig
.
tags
.
section
)(
stream
,
state
);
};
}
inVariable
(
stream
,
state
)
{
if
(
stream
.
match
(
/^[^{}|]+/
)
)
{
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateVariableName
,
state
);
}
if
(
stream
.
eat
(
'|'
)
)
{
state
.
tokenize
=
this
.
inVariableDefault
.
bind
(
this
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateVariableDelimiter
,
state
);
}
if
(
stream
.
match
(
'}}}'
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateVariableBracket
,
state
);
}
if
(
stream
.
match
(
'{{{'
)
)
{
state
.
stack
.
push
(
state
.
tokenize
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateVariableBracket
,
state
);
}
stream
.
next
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateVariableName
,
state
);
}
inVariableDefault
(
stream
,
state
)
{
if
(
stream
.
match
(
/^[^{}[<&~]+/
)
)
{
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateVariable
,
state
);
}
if
(
stream
.
match
(
'}}}'
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateVariableBracket
,
state
);
}
return
this
.
eatWikiText
(
mwModeConfig
.
tags
.
templateVariable
)(
stream
,
state
);
}
inParserFunctionName
(
stream
,
state
)
{
// FIXME: {{#name}} and {{uc}} are wrong, must have ':'
if
(
stream
.
match
(
/^#?[^:}{~]+/
)
)
{
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
parserFunctionName
,
state
);
}
if
(
stream
.
eat
(
':'
)
)
{
state
.
tokenize
=
this
.
inParserFunctionArguments
.
bind
(
this
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
parserFunctionDelimiter
,
state
);
}
if
(
stream
.
match
(
'}}'
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
parserFunctionBracket
,
state
,
'nExt'
);
}
return
this
.
eatWikiText
(
mwModeConfig
.
tags
.
parserFunction
)(
stream
,
state
);
}
inParserFunctionArguments
(
stream
,
state
)
{
if
(
stream
.
match
(
/^[^|}{[<&~]+/
)
)
{
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
parserFunction
,
state
);
}
else
if
(
stream
.
eat
(
'|'
)
)
{
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
parserFunctionDelimiter
,
state
);
}
else
if
(
stream
.
match
(
'}}'
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
parserFunctionBracket
,
state
,
'nExt'
);
}
return
this
.
eatWikiText
(
mwModeConfig
.
tags
.
parserFunction
)(
stream
,
state
);
}
eatTemplatePageName
(
haveAte
)
{
return
(
stream
,
state
)
=>
{
if
(
stream
.
match
(
/^[\s\u00a0]*\|[\s\u00a0]*/
)
)
{
state
.
tokenize
=
this
.
eatTemplateArgument
(
true
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateDelimiter
,
state
);
}
if
(
stream
.
match
(
/^[\s\u00a0]*\}\}/
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateBracket
,
state
,
'nTemplate'
);
}
if
(
stream
.
match
(
/^[\s\u00a0]*<!--.*?-->/
)
)
{
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
comment
,
state
);
}
if
(
haveAte
&&
stream
.
sol
()
)
{
// @todo error message
state
.
nTemplate
--
;
state
.
tokenize
=
state
.
stack
.
pop
();
return
;
}
if
(
stream
.
match
(
/^[\s\u00a0]*[^\s\u00a0|}<{&~]+/
)
)
{
state
.
tokenize
=
this
.
eatTemplatePageName
(
true
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateName
,
state
);
}
else
if
(
stream
.
eatSpace
()
)
{
if
(
stream
.
eol
()
===
true
)
{
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateName
,
state
);
}
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateName
,
state
);
}
return
this
.
eatWikiText
(
mwModeConfig
.
tags
.
templateName
)(
stream
,
state
);
};
}
eatTemplateArgument
(
expectArgName
)
{
return
(
stream
,
state
)
=>
{
if
(
expectArgName
&&
stream
.
eatWhile
(
/[^=|}{[<&~]/
)
)
{
if
(
stream
.
eat
(
'='
)
)
{
state
.
tokenize
=
this
.
eatTemplateArgument
(
false
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateArgumentName
,
state
);
}
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
template
,
state
);
}
else
if
(
stream
.
eatWhile
(
/[^|}{[<&~]/
)
)
{
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
template
,
state
);
}
else
if
(
stream
.
eat
(
'|'
)
)
{
state
.
tokenize
=
this
.
eatTemplateArgument
(
true
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateDelimiter
,
state
);
}
else
if
(
stream
.
match
(
'}}'
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateBracket
,
state
,
'nTemplate'
);
}
return
this
.
eatWikiText
(
mwModeConfig
.
tags
.
template
)(
stream
,
state
);
};
}
eatExternalLinkProtocol
(
chars
)
{
return
(
stream
,
state
)
=>
{
while
(
chars
>
0
)
{
chars
--
;
stream
.
next
();
}
if
(
stream
.
eol
()
)
{
state
.
nLink
--
;
// @todo error message
state
.
tokenize
=
state
.
stack
.
pop
();
}
else
{
state
.
tokenize
=
this
.
inExternalLink
.
bind
(
this
);
}
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
extLinkProtocol
,
state
);
};
}
inExternalLink
(
stream
,
state
)
{
if
(
stream
.
sol
()
)
{
state
.
nLink
--
;
// @todo error message
state
.
tokenize
=
state
.
stack
.
pop
();
return
;
}
if
(
stream
.
match
(
/^[\s\u00a0]*\]/
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
extLinkBracket
,
state
,
'nLink'
);
}
if
(
stream
.
eatSpace
()
)
{
state
.
tokenize
=
this
.
inExternalLinkText
.
bind
(
this
);
return
this
.
makeStyle
(
''
,
state
);
}
if
(
stream
.
match
(
/^[^\s\u00a0\]{&~']+/
)
||
stream
.
eatSpace
()
)
{
if
(
stream
.
peek
()
===
'\''
)
{
if
(
stream
.
match
(
'\'\''
,
false
)
)
{
state
.
tokenize
=
this
.
inExternalLinkText
.
bind
(
this
);
}
else
{
stream
.
next
();
}
}
return
this
.
makeStyle
(
mwModeConfig
.
tags
.
extLink
,
state
);
}
return
this
.
eatWikiText
(
mwModeConfig
.
tags
.
extLink
)(
stream
,
state
);
}
inExternalLinkText
(
stream
,
state
)
{
if
(
stream
.
sol
()
)
{
state
.
nLink
--
;
// @todo error message
state
.
tokenize
=
state
.
stack
.
pop
();
return
;
}
if
(
stream
.
eat
(
']'
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
extLinkBracket
,
state
,
'nLink'
);
}
if
(
stream
.
match
(
/^[^'\]{&~<]+/
)
)
{
return
this
.
makeStyle
(
mwModeConfig
.
tags
.
extLinkText
,
state
);
}
return
this
.
eatWikiText
(
mwModeConfig
.
tags
.
extLinkText
)(
stream
,
state
);
}
inLink
(
stream
,
state
)
{
if
(
stream
.
sol
()
)
{
state
.
nLink
--
;
// @todo error message
state
.
tokenize
=
state
.
stack
.
pop
();
return
;
}
if
(
stream
.
match
(
/^[\s\u00a0]*#[\s\u00a0]*/
)
)
{
state
.
tokenize
=
this
.
inLinkToSection
.
bind
(
this
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
link
,
state
);
}
if
(
stream
.
match
(
/^[\s\u00a0]*\|[\s\u00a0]*/
)
)
{
state
.
tokenize
=
this
.
eatLinkText
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
linkDelimiter
,
state
);
}
if
(
stream
.
match
(
/^[\s\u00a0]*\]\]/
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
linkBracket
,
state
,
'nLink'
);
}
if
(
stream
.
match
(
/^[\s\u00a0]*[^\s\u00a0#|\]&~{]+/
)
||
stream
.
eatSpace
()
)
{
return
this
.
makeStyle
(
`
${
mwModeConfig
.
tags
.
linkPageName
}
${
mwModeConfig
.
tags
.
pageName
}
`
,
state
);
}
return
this
.
eatWikiText
(
`
${
mwModeConfig
.
tags
.
linkPageName
}
${
mwModeConfig
.
tags
.
pageName
}
`
)(
stream
,
state
);
}
inLinkToSection
(
stream
,
state
)
{
if
(
stream
.
sol
()
)
{
// @todo error message
state
.
nLink
--
;
state
.
tokenize
=
state
.
stack
.
pop
();
return
;
}
// FIXME '{{' breaks links, example: [[z{{page]]
if
(
stream
.
match
(
/^[^|\]&~{}]+/
)
)
{
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
linkToSection
,
state
);
}
if
(
stream
.
eat
(
'|'
)
)
{
state
.
tokenize
=
this
.
eatLinkText
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
linkDelimiter
,
state
);
}
if
(
stream
.
match
(
']]'
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
linkBracket
,
state
,
'nLink'
);
}
return
this
.
eatWikiText
(
mwModeConfig
.
tags
.
linkToSection
)(
stream
,
state
);
}
eatLinkText
()
{
let
linkIsBold
,
linkIsItalic
;
return
(
stream
,
state
)
=>
{
let
tmpstyle
;
if
(
stream
.
match
(
']]'
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
linkBracket
,
state
,
'nLink'
);
}
if
(
stream
.
match
(
'\'\'\''
)
)
{
linkIsBold
=
!
linkIsBold
;
return
this
.
makeLocalStyle
(
`
${
mwModeConfig
.
tags
.
linkText
}
${
mwModeConfig
.
tags
.
apostrophes
}
`
,
state
);
}
if
(
stream
.
match
(
'\'\''
)
)
{
linkIsItalic
=
!
linkIsItalic
;
return
this
.
makeLocalStyle
(
`
${
mwModeConfig
.
tags
.
linkText
}
${
mwModeConfig
.
tags
.
apostrophes
}
`
,
state
);
}
tmpstyle
=
mwModeConfig
.
tags
.
linkText
;
if
(
linkIsBold
)
{
tmpstyle
+=
' '
+
mwModeConfig
.
tags
.
strong
;
}
if
(
linkIsItalic
)
{
tmpstyle
+=
' '
+
mwModeConfig
.
tags
.
em
;
}
if
(
stream
.
match
(
/^[^'\]{&~<]+/
)
)
{
return
this
.
makeStyle
(
tmpstyle
,
state
);
}
return
this
.
eatWikiText
(
tmpstyle
)(
stream
,
state
);
};
}
eatTagName
(
chars
,
isCloseTag
,
isHtmlTag
)
{
return
(
stream
,
state
)
=>
{
let
name
=
''
;
while
(
chars
>
0
)
{
chars
--
;
name
=
name
+
stream
.
next
();
}
stream
.
eatSpace
();
name
=
name
.
toLowerCase
();
if
(
isHtmlTag
)
{
if
(
isCloseTag
&&
!
mwModeConfig
.
implicitlyClosedHtmlTags
[
name
]
)
{
state
.
tokenize
=
this
.
eatChar
(
'>'
,
mwModeConfig
.
tags
.
htmlTagBracket
);
}
else
{
state
.
tokenize
=
this
.
eatHtmlTagAttribute
(
name
);
}
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
htmlTagName
,
state
);
}
// it is the extension tag
if
(
isCloseTag
)
{
state
.
tokenize
=
this
.
eatChar
(
'>'
,
`
${
mwModeConfig
.
tags
.
extTagBracket
}
mw-ext-
${
name
}
`
);
}
else
{
state
.
tokenize
=
this
.
eatExtTagAttribute
(
name
);
}
return
this
.
makeLocalStyle
(
`
${
mwModeConfig
.
tags
.
extTagName
}
mw-ext-
${
name
}
`
,
state
);
};
}
eatHtmlTagAttribute
(
name
)
{
return
(
stream
,
state
)
=>
{
if
(
stream
.
match
(
/^(?:"[^<">]*"|'[^<'>]*'|[^>/<{&~])+/
)
)
{
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
htmlTagAttribute
,
state
);
}
if
(
stream
.
eat
(
'>'
)
)
{
if
(
!
(
name
in
mwModeConfig
.
implicitlyClosedHtmlTags
)
)
{
state
.
inHtmlTag
.
push
(
name
);
}
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
htmlTagBracket
,
state
);
}
if
(
stream
.
match
(
'/>'
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
htmlTagBracket
,
state
);
}
return
this
.
eatWikiText
(
mwModeConfig
.
tags
.
htmlTagAttribute
)(
stream
,
state
);
};
}
eatNowiki
()
{
return
(
stream
)
=>
{
if
(
stream
.
match
(
/^[^&]+/
)
)
{
return
''
;
}
// eat &
stream
.
next
();
return
this
.
eatHtmlEntity
(
stream
,
''
);
};
}
eatExtTagAttribute
(
name
)
{
return
(
stream
,
state
)
=>
{
if
(
stream
.
match
(
/^(?:"[^">]*"|'[^'>]*'|[^>/<{&~])+/
)
)
{
return
this
.
makeLocalStyle
(
`
${
mwModeConfig
.
tags
.
extTagAttribute
}
mw-ext-
${
name
}
`
,
state
);
}
if
(
stream
.
eat
(
'>'
)
)
{
state
.
extName
=
name
;
// FIXME: remove nowiki and pre from TagModes in extension.json after CM6 upgrade
// leverage the tagModes system for <nowiki> and <pre>
if
(
name
===
'nowiki'
||
name
===
'pre'
)
{
// There's no actual processing within these tags (apart from HTML entities),
// so startState and copyState can be no-ops.
state
.
extMode
=
{
startState
:
()
=>
{},
copyState
:
()
=>
{},
token
:
this
.
eatNowiki
()
};
}
else
if
(
name
in
this
.
config
.
tagModes
)
{
const
mode
=
this
.
config
.
tagModes
[
name
];
if
(
mode
===
'mediawiki'
||
mode
===
'text/mediawiki'
)
{
state
.
extMode
=
this
.
mediawiki
;
state
.
extState
=
state
.
extMode
.
startState
();
}
}
state
.
tokenize
=
this
.
eatExtTagArea
(
name
);
return
this
.
makeLocalStyle
(
`
${
mwModeConfig
.
tags
.
extTagBracket
}
mw-ext-
${
name
}
`
,
state
);
}
if
(
stream
.
match
(
'/>'
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
`
${
mwModeConfig
.
tags
.
extTagBracket
}
mw-ext-
${
name
}
`
,
state
);
}
return
this
.
eatWikiText
(
`
${
mwModeConfig
.
tags
.
extTagAttribute
}
mw-ext-
${
name
}
`
)(
stream
,
state
);
};
}
eatExtTagArea
(
name
)
{
return
(
stream
,
state
)
=>
{
const
from
=
stream
.
pos
,
pattern
=
new
RegExp
(
`</
${
name
}
\\s*>`
,
'i'
),
m
=
pattern
.
exec
(
from
?
stream
.
string
.
slice
(
from
)
:
stream
.
string
);
let
origString
=
false
,
to
;
if
(
m
)
{
if
(
m
.
index
===
0
)
{
state
.
tokenize
=
this
.
eatExtCloseTag
(
name
);
state
.
extName
=
false
;
if
(
state
.
extMode
!==
false
)
{
state
.
extMode
=
false
;
state
.
extState
=
false
;
}
return
state
.
tokenize
(
stream
,
state
);
}
to
=
m
.
index
+
from
;
origString
=
stream
.
string
;
stream
.
string
=
origString
.
slice
(
0
,
to
);
}
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
eatExtTokens
(
origString
);
return
state
.
tokenize
(
stream
,
state
);
};
}
eatExtCloseTag
(
name
)
{
return
(
stream
,
state
)
=>
{
stream
.
next
();
// eat <
stream
.
next
();
// eat /
state
.
tokenize
=
this
.
eatTagName
(
name
.
length
,
true
,
false
);
return
this
.
makeLocalStyle
(
`
${
mwModeConfig
.
tags
.
extTagBracket
}
mw-ext-
${
name
}
`
,
state
);
};
}
eatExtTokens
(
origString
)
{
return
(
stream
,
state
)
=>
{
let
ret
;
if
(
state
.
extMode
===
false
)
{
ret
=
mwModeConfig
.
tags
.
extTag
;
stream
.
skipToEnd
();
}
else
{
ret
=
`mw-tag-
${
state
.
extName
}
`
+
state
.
extMode
.
token
(
stream
,
state
.
extState
,
origString
===
false
);
}
if
(
stream
.
eol
()
)
{
if
(
origString
!==
false
)
{
stream
.
string
=
origString
;
}
state
.
tokenize
=
state
.
stack
.
pop
();
}
return
this
.
makeLocalStyle
(
ret
,
state
);
};
}
eatStartTable
(
stream
,
state
)
{
stream
.
match
(
'{|'
);
stream
.
eatSpace
();
state
.
tokenize
=
this
.
inTableDefinition
.
bind
(
this
);
return
mwModeConfig
.
tags
.
tableBracket
;
}
inTableDefinition
(
stream
,
state
)
{
if
(
stream
.
sol
()
)
{
state
.
tokenize
=
this
.
inTable
.
bind
(
this
);
return
this
.
inTable
(
stream
,
state
);
}
return
this
.
eatWikiText
(
mwModeConfig
.
tags
.
tableDefinition
)(
stream
,
state
);
}
inTable
(
stream
,
state
)
{
if
(
stream
.
sol
()
)
{
stream
.
eatSpace
();
if
(
stream
.
eat
(
'|'
)
)
{
if
(
stream
.
eat
(
'-'
)
)
{
stream
.
eatSpace
();
state
.
tokenize
=
this
.
inTableDefinition
.
bind
(
this
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
tableDelimiter
,
state
);
}
if
(
stream
.
eat
(
'+'
)
)
{
stream
.
eatSpace
();
state
.
tokenize
=
this
.
eatTableRow
(
true
,
false
,
true
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
tableDelimiter
,
state
);
}
if
(
stream
.
eat
(
'}'
)
)
{
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
tableBracket
,
state
);
}
stream
.
eatSpace
();
state
.
tokenize
=
this
.
eatTableRow
(
true
,
false
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
tableDelimiter
,
state
);
}
if
(
stream
.
eat
(
'!'
)
)
{
stream
.
eatSpace
();
state
.
tokenize
=
this
.
eatTableRow
(
true
,
true
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
tableDelimiter
,
state
);
}
}
return
this
.
eatWikiText
(
''
)(
stream
,
state
);
}
// isStart actually means whether there may be attributes */
eatTableRow
(
isStart
,
isHead
,
isCaption
)
{
let
tag
=
''
;
if
(
isCaption
)
{
tag
=
mwModeConfig
.
tags
.
tableCaption
;
}
else
if
(
isHead
)
{
tag
=
mwModeConfig
.
tags
.
strong
;
}
return
(
stream
,
state
)
=>
{
if
(
stream
.
sol
()
)
{
if
(
stream
.
match
(
/^[\s\u00a0]*[|!]/
,
false
)
)
{
state
.
tokenize
=
this
.
inTable
.
bind
(
this
);
return
this
.
inTable
(
stream
,
state
);
}
}
else
{
if
(
stream
.
match
(
/^[^'|{[<&~!]+/
)
)
{
return
this
.
makeStyle
(
tag
,
state
);
}
if
(
stream
.
match
(
'||'
)
||
(
isHead
&&
stream
.
match
(
'!!'
)
)
)
{
this
.
isBold
=
false
;
this
.
isItalic
=
false
;
state
.
tokenize
=
this
.
eatTableRow
(
true
,
isHead
,
isCaption
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
tableDelimiter
,
state
);
}
if
(
isStart
&&
stream
.
eat
(
'|'
)
)
{
state
.
tokenize
=
this
.
eatTableRow
(
false
,
isHead
,
isCaption
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
tableDelimiter
,
state
);
}
}
return
this
.
eatWikiText
(
tag
)(
stream
,
state
);
};
}
eatFreeExternalLinkProtocol
(
stream
,
state
)
{
stream
.
match
(
this
.
urlProtocols
);
state
.
tokenize
=
this
.
eatFreeExternalLink
.
bind
(
this
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
freeExtLinkProtocol
,
state
);
}
eatFreeExternalLink
(
stream
,
state
)
{
if
(
stream
.
sol
()
)
{
// @todo error message
}
else
if
(
stream
.
match
(
/^[^\s\u00a0{[\]<>~).,']*/
)
)
{
if
(
stream
.
peek
()
===
'~'
)
{
if
(
!
stream
.
match
(
/^~~~+/
,
false
)
)
{
stream
.
match
(
/^~*/
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
freeExtLink
,
state
);
}
}
else
if
(
stream
.
peek
()
===
'{'
)
{
if
(
!
stream
.
match
(
'{{'
,
false
)
)
{
stream
.
next
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
freeExtLink
,
state
);
}
}
else
if
(
stream
.
peek
()
===
'\''
)
{
if
(
!
stream
.
match
(
'\'\''
,
false
)
)
{
stream
.
next
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
freeExtLink
,
state
);
}
}
else
if
(
stream
.
match
(
/^[).,]+(?=[^\s\u00a0{[\]<>~).,])/
)
)
{
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
freeExtLink
,
state
);
}
}
state
.
tokenize
=
state
.
stack
.
pop
();
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
freeExtLink
,
state
);
}
eatList
(
stream
,
state
)
{
// Just consume all nested list and indention syntax when there is more
const
mt
=
stream
.
match
(
/^[*#;:]*/u
);
if
(
mt
&&
!
this
.
isNested
(
state
)
&&
mt
[
0
].
includes
(
';'
)
)
{
state
.
nDt
+=
mt
[
0
].
split
(
';'
).
length
-
1
;
}
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
list
,
state
);
}
/**
* @param {string} style
* @return {string|Function}
* @private
*/
eatWikiText
(
style
)
{
return
(
stream
,
state
)
=>
{
let
ch
,
tmp
,
mt
,
name
,
isCloseTag
,
tagname
;
const
sol
=
stream
.
sol
();
function
chain
(
parser
)
{
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
parser
;
return
parser
(
stream
,
state
);
}
if
(
sol
)
{
// highlight free external links, see T108448
if
(
!
stream
.
match
(
'//'
,
false
)
&&
stream
.
match
(
this
.
urlProtocols
)
)
{
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
eatFreeExternalLink
.
bind
(
this
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
freeExtLinkProtocol
,
state
);
}
ch
=
stream
.
next
();
switch
(
ch
)
{
case
'-'
:
if
(
stream
.
match
(
/^---+/
)
)
{
return
mwModeConfig
.
tags
.
hr
;
}
break
;
case
'='
:
tmp
=
stream
.
match
(
/^(={0,5})(.+?(=\1\s*)(<!--(?!.*-->.*\S).*?)?)$/
);
// Title
if
(
tmp
)
{
stream
.
backUp
(
tmp
[
2
].
length
);
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
eatSectionHeader
(
tmp
[
3
].
length
);
return
mwModeConfig
.
tags
.
sectionHeader
+
' '
+
/**
* Tokens used here include:
* - cm-mw-section-1
* - cm-mw-section-2
* - cm-mw-section-3
* - cm-mw-section-4
* - cm-mw-section-5
* - cm-mw-section-6
*/
mwModeConfig
.
tags
[
`sectionHeader
${
tmp
[
1
].
length
+
1
}
`
];
}
break
;
case
';'
:
stream
.
backUp
(
1
);
// fall through
case
'*'
:
case
'#'
:
return
this
.
eatList
(
stream
,
state
);
case
':'
:
// Highlight indented tables :{|, bug T108454
if
(
stream
.
match
(
/^:*[\s\u00a0]*(?={\|)/
)
)
{
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
eatStartTable
.
bind
(
this
);
return
mwModeConfig
.
tags
.
indenting
;
}
return
this
.
eatList
(
stream
,
state
);
case
' '
:
// Leading spaces is valid syntax for tables, bug T108454
if
(
stream
.
match
(
/^[\s\u00a0]*(?::+[\s\u00a0]*)?{\|/
,
false
)
)
{
stream
.
eatSpace
();
if
(
stream
.
match
(
/^:+/
)
)
{
// ::{|
stream
.
eatSpace
();
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
eatStartTable
.
bind
(
this
);
return
mwModeConfig
.
tags
.
indenting
;
}
stream
.
eat
(
'{'
);
}
else
{
return
mwModeConfig
.
tags
.
skipFormatting
;
}
// break is not necessary here
// falls through
case
'{'
:
if
(
stream
.
eat
(
'|'
)
)
{
stream
.
eatSpace
();
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
inTableDefinition
.
bind
(
this
);
return
mwModeConfig
.
tags
.
tableBracket
;
}
}
}
else
{
ch
=
stream
.
next
();
}
switch
(
ch
)
{
case
'&'
:
return
this
.
makeStyle
(
this
.
eatHtmlEntity
(
stream
,
style
),
state
);
case
'\''
:
// skip the irrelevant apostrophes ( >5 or =4 )
if
(
stream
.
match
(
/^'*(?=''''')/
)
||
stream
.
match
(
/^'''(?!')/
,
false
)
)
{
break
;
}
if
(
stream
.
match
(
'\'\''
)
)
{
// bold
if
(
!
(
this
.
firstSingleLetterWord
||
stream
.
match
(
'\'\''
,
false
)
)
)
{
this
.
prepareItalicForCorrection
(
stream
);
}
this
.
isBold
=
!
this
.
isBold
;
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
apostrophesBold
,
state
);
}
else
if
(
stream
.
eat
(
'\''
)
)
{
// italic
this
.
isItalic
=
!
this
.
isItalic
;
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
apostrophesItalic
,
state
);
}
break
;
case
'['
:
if
(
stream
.
eat
(
'['
)
)
{
// Link Example: [[ Foo | Bar ]]
stream
.
eatSpace
();
if
(
/[^\]|[]/
.
test
(
stream
.
peek
()
)
)
{
state
.
nLink
++
;
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
inLink
.
bind
(
this
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
linkBracket
,
state
);
}
}
else
{
mt
=
stream
.
match
(
this
.
urlProtocols
);
if
(
mt
)
{
state
.
nLink
++
;
stream
.
backUp
(
mt
[
0
].
length
);
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
eatExternalLinkProtocol
(
mt
[
0
].
length
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
extLinkBracket
,
state
);
}
}
break
;
case
'{'
:
// Can't be a variable when it starts with more than 3 brackets (T108450) or
// a single { followed by a template. E.g. {{{!}} starts a table (T292967).
if
(
stream
.
match
(
/^{{(?!{|[^{}]*}}(?!}))/
)
)
{
stream
.
eatSpace
();
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
inVariable
.
bind
(
this
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateVariableBracket
,
state
);
}
else
if
(
stream
.
match
(
/^{(?!{(?!{))[\s\u00a0]*/
)
)
{
// Parser function
if
(
stream
.
peek
()
===
'#'
)
{
state
.
nExt
++
;
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
inParserFunctionName
.
bind
(
this
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
parserFunctionBracket
,
state
);
}
// Check for parser function without '#'
name
=
stream
.
match
(
/^([^}[\]<{|:]+)(.)?/
,
false
);
if
(
name
)
{
const
[
,
f
,
delimiter
]
=
name
,
ff
=
delimiter
===
':'
?
f
:
f
.
trim
(),
ffLower
=
ff
.
toLowerCase
(),
{
config
:
{
functionSynonyms
}
}
=
this
;
if
(
(
!
delimiter
||
delimiter
===
':'
||
delimiter
===
'}'
)
&&
(
Object
.
prototype
.
hasOwnProperty
.
call
(
functionSynonyms
[
0
],
ffLower
)
||
Object
.
prototype
.
hasOwnProperty
.
call
(
functionSynonyms
[
1
],
ff
)
)
)
{
state
.
nExt
++
;
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
inParserFunctionName
.
bind
(
this
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
parserFunctionBracket
,
state
);
}
}
// Template
state
.
nTemplate
++
;
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
eatTemplatePageName
(
false
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
templateBracket
,
state
);
}
break
;
case
'<'
:
if
(
stream
.
match
(
'!--'
)
)
{
// comment
return
chain
(
this
.
eatBlock
(
mwModeConfig
.
tags
.
comment
,
'-->'
)
);
}
isCloseTag
=
!!
stream
.
eat
(
'/'
);
tagname
=
stream
.
match
(
/^[a-z][^>/\s\u00a0]*/i
);
if
(
tagname
)
{
tagname
=
tagname
[
0
].
toLowerCase
();
if
(
tagname
in
this
.
config
.
tags
)
{
// Parser function
if
(
isCloseTag
===
true
)
{
return
mwModeConfig
.
tags
.
error
;
}
stream
.
backUp
(
tagname
.
length
);
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
eatTagName
(
tagname
.
length
,
isCloseTag
,
false
);
return
this
.
makeLocalStyle
(
`
${
mwModeConfig
.
tags
.
extTagBracket
}
mw-ext-
${
tagname
}
`
,
state
);
}
if
(
tagname
in
mwModeConfig
.
permittedHtmlTags
)
{
// Html tag
if
(
isCloseTag
===
true
&&
tagname
!==
state
.
inHtmlTag
.
pop
()
)
{
// Increment position so that the closing '>' gets highlighted red.
stream
.
pos
++
;
return
mwModeConfig
.
tags
.
error
;
}
if
(
isCloseTag
===
true
&&
tagname
in
mwModeConfig
.
implicitlyClosedHtmlTags
)
{
return
mwModeConfig
.
tags
.
error
;
}
stream
.
backUp
(
tagname
.
length
);
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
eatTagName
(
tagname
.
length
,
// Opening void tags should also be treated as the closing tag.
isCloseTag
||
(
tagname
in
mwModeConfig
.
implicitlyClosedHtmlTags
),
true
);
return
this
.
makeLocalStyle
(
mwModeConfig
.
tags
.
htmlTagBracket
,
state
);
}
stream
.
backUp
(
tagname
.
length
);
}
break
;
case
'~'
:
if
(
stream
.
match
(
/^~{2,4}/
)
)
{
return
mwModeConfig
.
tags
.
signature
;
}
break
;
// Maybe double underscored Magic Word such as __TOC__
case
'_'
:
tmp
=
1
;
// Optimize processing of many underscore symbols
while
(
stream
.
eat
(
'_'
)
)
{
tmp
++
;
}
// Many underscore symbols
if
(
tmp
>
2
)
{
if
(
!
stream
.
eol
()
)
{
// Leave last two underscore symbols for processing in next iteration
stream
.
backUp
(
2
);
}
// Optimization: skip regex function for EOL and backup-ed symbols
return
this
.
makeStyle
(
style
,
state
);
// Check on double underscore Magic Word
}
else
if
(
tmp
===
2
)
{
// The same as the end of function except '_' inside and '__' at the end.
name
=
stream
.
match
(
/^([^\s\u00a0>}[\]<{'|&:~]+?)__/
);
if
(
name
&&
name
[
0
]
)
{
if
(
'__'
+
name
[
0
].
toLowerCase
()
in
this
.
config
.
doubleUnderscore
[
0
]
||
'__'
+
name
[
0
]
in
this
.
config
.
doubleUnderscore
[
1
]
)
{
return
mwModeConfig
.
tags
.
doubleUnderscore
;
}
if
(
!
stream
.
eol
()
)
{
// Two underscore symbols at the end can be the
// beginning of another double underscored Magic Word
stream
.
backUp
(
2
);
}
// Optimization: skip regex for EOL and backup-ed symbols
return
this
.
makeStyle
(
style
,
state
);
}
}
break
;
case
':'
:
if
(
state
.
nDt
>
0
&&
!
this
.
isNested
(
state
)
)
{
state
.
nDt
--
;
return
mwModeConfig
.
tags
.
indenting
;
}
break
;
default
:
if
(
/[\s\u00a0]/
.
test
(
ch
)
)
{
stream
.
eatSpace
();
// highlight free external links, bug T108448
if
(
stream
.
match
(
this
.
urlProtocols
,
false
)
&&
!
stream
.
match
(
'//'
)
)
{
state
.
stack
.
push
(
state
.
tokenize
);
state
.
tokenize
=
this
.
eatFreeExternalLinkProtocol
.
bind
(
this
);
return
this
.
makeStyle
(
style
,
state
);
}
}
break
;
}
stream
.
match
(
/^[^\s\u00a0_>}[\]<{'|&:~=]+/
);
return
this
.
makeStyle
(
style
,
state
);
};
}
/**
* Remembers position and status for rollbacking.
* It is needed for changing from bold to italic with apostrophes before it, if required.
*
* @see https://phabricator.wikimedia.org/T108455
*
* @param {StringStream} stream
* @private
*/
prepareItalicForCorrection
(
stream
)
{
// See Parser::doQuotes() in MediaWiki Core, it works similarly.
// this.firstSingleLetterWord has maximum priority
// this.firstMultiLetterWord has medium priority
// this.firstSpace has low priority
const
end
=
stream
.
pos
,
str
=
stream
.
string
.
slice
(
0
,
end
-
3
),
x1
=
str
.
slice
(
-
1
),
x2
=
str
.
slice
(
-
2
,
-
1
);
// this.firstSingleLetterWord always is undefined here
if
(
x1
===
' '
)
{
if
(
this
.
firstMultiLetterWord
||
this
.
firstSpace
)
{
return
;
}
this
.
firstSpace
=
end
;
}
else
if
(
x2
===
' '
)
{
this
.
firstSingleLetterWord
=
end
;
}
else
if
(
this
.
firstMultiLetterWord
)
{
return
;
}
else
{
this
.
firstMultiLetterWord
=
end
;
}
// remember bold and italic state for later restoration
this
.
wasBold
=
this
.
isBold
;
this
.
wasItalic
=
this
.
isItalic
;
}
/**
* @see https://codemirror.net/docs/ref/#language.StreamParser
* @return {StreamParser}
* @private
*/
get
mediawiki
()
{
return
{
name
:
'mediawiki'
,
/**
* Initial State for the parser.
*
* @return {Object}
* @private
*/
startState
:
()
=>
(
{
tokenize
:
this
.
eatWikiText
(
''
),
stack
:
[],
inHtmlTag
:
[],
extName
:
false
,
extMode
:
false
,
extState
:
false
,
nTemplate
:
0
,
nLink
:
0
,
nExt
:
0
,
nDt
:
0
}
),
/**
* Copies the given state.
*
* @param {Object} state
* @return {Object}
* @private
*/
copyState
:
(
state
)
=>
(
{
tokenize
:
state
.
tokenize
,
stack
:
state
.
stack
.
concat
(
[]
),
inHtmlTag
:
state
.
inHtmlTag
.
concat
(
[]
),
extName
:
state
.
extName
,
extMode
:
state
.
extMode
,
extState
:
state
.
extMode
!==
false
&&
state
.
extMode
.
copyState
(
state
.
extState
),
nTemplate
:
state
.
nTemplate
,
nLink
:
state
.
nLink
,
nExt
:
state
.
nExt
,
nDt
:
state
.
nDt
}
),
/**
* Reads one token, advancing the stream past it,
* and returning a string indicating the token's style tag.
*
* @param {StringStream} stream
* @param {Object} state
* @return {string|null}
* @private
*/
token
:
(
stream
,
state
)
=>
{
let
style
,
p
,
t
,
f
,
readyTokens
=
[],
tmpTokens
=
[];
if
(
this
.
oldTokens
.
length
>
0
)
{
// just send saved tokens till they exists
t
=
this
.
oldTokens
.
shift
();
stream
.
pos
=
t
.
pos
;
state
=
t
.
state
;
return
t
.
style
;
}
if
(
stream
.
sol
()
)
{
// reset bold and italic status in every new line
state
.
nDt
=
0
;
this
.
isBold
=
false
;
this
.
isItalic
=
false
;
this
.
firstSingleLetterWord
=
null
;
this
.
firstMultiLetterWord
=
null
;
this
.
firstSpace
=
null
;
}
do
{
// get token style
style
=
state
.
tokenize
(
stream
,
state
);
f
=
this
.
firstSingleLetterWord
||
this
.
firstMultiLetterWord
||
this
.
firstSpace
;
if
(
f
)
{
// rollback point exists
if
(
f
!==
p
)
{
// new rollback point
p
=
f
;
// it's not first rollback point
if
(
tmpTokens
.
length
>
0
)
{
// save tokens
readyTokens
=
readyTokens
.
concat
(
tmpTokens
);
tmpTokens
=
[];
}
}
// save token
tmpTokens
.
push
(
{
pos
:
stream
.
pos
,
style
,
state
:
(
state
.
extMode
||
this
.
mediawiki
).
copyState
(
state
)
}
);
}
else
{
// rollback point does not exist
// remember style before possible rollback point
this
.
oldStyle
=
style
;
// just return token style
return
style
;
}
}
while
(
!
stream
.
eol
()
);
if
(
this
.
isBold
&&
this
.
isItalic
)
{
// needs to rollback
// restore status
this
.
isItalic
=
this
.
wasItalic
;
this
.
isBold
=
this
.
wasBold
;
this
.
firstSingleLetterWord
=
null
;
this
.
firstMultiLetterWord
=
null
;
this
.
firstSpace
=
null
;
if
(
readyTokens
.
length
>
0
)
{
// it contains tickets before the point of rollback
// add one apostrophe, next token will be italic (two apostrophes)
readyTokens
[
readyTokens
.
length
-
1
].
pos
++
;
// for sending tokens till the point of rollback
this
.
oldTokens
=
readyTokens
;
}
else
{
// there are no tickets before the point of rollback
stream
.
pos
=
tmpTokens
[
0
].
pos
-
2
;
// eat( '\'')
// send saved Style
return
this
.
oldStyle
;
}
}
else
{
// do not need to rollback
// send all saved tokens
this
.
oldTokens
=
readyTokens
.
concat
(
tmpTokens
);
}
// return first saved token
t
=
this
.
oldTokens
.
shift
();
stream
.
pos
=
t
.
pos
;
state
=
t
.
state
;
return
t
.
style
;
},
/**
* @param {Object} state
* @private
*/
blankLine
:
(
state
)
=>
{
if
(
state
.
extMode
&&
state
.
extMode
.
blankLine
)
{
state
.
extMode
.
blankLine
(
state
.
extState
);
}
},
/**
* Extra tokens to use in this parser.
*
* @see CodeMirrorModeMediaWikiConfig.defaultTokenTable
* @return {Object<Tag>}
* @private
*/
tokenTable
:
this
.
tokenTable
};
}
}
/**
* Gets a LanguageSupport instance for the MediaWiki mode.
*
* @member CodeMirrorModeMediaWiki
* @method
* @param {Object} [config] Configuration options for the MediaWiki mode.
* @param {boolean} [config.bidiIsolation=false] Enable bidi isolation around HTML tags.
* This should generally always be enabled on RTL pages, but it comes with a performance cost.
* @param {boolean} [config.templateFolding=true] Enable template folding.
* @param {Object|null} [mwConfig] Ignore; used only by unit tests.
* @return {LanguageSupport}
* @stable to call
*/
const
mediaWikiLang
=
(
config
=
{
bidiIsolation
:
false
},
mwConfig
=
null
)
=>
{
mwConfig
=
mwConfig
||
mw
.
config
.
get
(
'extCodeMirrorConfig'
);
const
mode
=
new
CodeMirrorModeMediaWiki
(
mwConfig
);
const
parser
=
mode
.
mediawiki
;
const
lang
=
StreamLanguage
.
define
(
parser
);
const
langExtension
=
[
syntaxHighlighting
(
HighlightStyle
.
define
(
mwModeConfig
.
getTagStyles
(
parser
)
)
)
];
// Set to [] to disable everywhere, or null to enable everywhere.
const
templateFoldingNs
=
mwConfig
.
templateFoldingNamespaces
;
const
shouldUseFolding
=
!
templateFoldingNs
||
templateFoldingNs
.
includes
(
mw
.
config
.
get
(
'wgNamespaceNumber'
)
);
// Add template folding if in supported namespace.
if
(
shouldUseFolding
&&
(
config
.
templateFolding
||
config
.
templateFolding
===
undefined
)
)
{
langExtension
.
push
(
templateFoldingExtension
);
}
// Bundle the bidi isolation extension, as it's coded specifically for MediaWiki.
// This is behind a config option for performance reasons (we only use it on RTL pages).
if
(
config
.
bidiIsolation
)
{
langExtension
.
push
(
bidiIsolationExtension
);
}
return
new
LanguageSupport
(
lang
,
langExtension
);
};
module
.
exports
=
mediaWikiLang
;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, May 16, 22:06 (1 d, 3 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
7e/f2/6aa9ea1e367f8fcfd17c53282332
Default Alt Text
codemirror.mediawiki.js (39 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment