MediaWiki:Gadget-DotsSyntaxHighlighter.js: Difference between revisions

From Elwiki
mNo edit summary
mNo edit summary
Line 1: Line 1:
//variables that are preserved between function calls
$(function() {
var wpTextbox0;
    //variables that are preserved between function calls
var wpTextbox1;
    var wpTextbox0;
var syntaxStyleTextNode;
    var wpTextbox1;
var lastText;
    var syntaxStyleTextNode;
var maxSpanNumber = -1; //the number of the last span available, used to tell if creating additional spans is necessary
    var lastText;
var highlightSyntaxIfNeededIntervalID;
    var maxSpanNumber = -1; //the number of the last span available, used to tell if creating additional spans is necessary
var attributeObserver;
    var highlightSyntaxIfNeededIntervalID;
var parentObserver;
    var attributeObserver;
    var parentObserver;


/* Define context-specific regexes, one for every common token that ends the
    /* Define context-specific regexes, one for every common token that ends the
  current context.
      current context.


  An attempt has been made to search for the most common syntaxes first,
      An attempt has been made to search for the most common syntaxes first,
  thus maximizing performance. Syntaxes that begin with the same character
      thus maximizing performance. Syntaxes that begin with the same character
  are searched for at the same time.
      are searched for at the same time.


  Supported wiki syntaxes from most common to least common:
      Supported wiki syntaxes from most common to least common:
      [[internal link]] [http:// named external link]
          [[internal link]] [http:// named external link]
      {{template}} {{{template parameter}}} {| table |}
          {{template}} {{{template parameter}}} {| table |}
      <tag> <!-- comment -->
          <tag> <!-- comment -->
      http:// bare external link
          http:// bare external link
      =Heading= * unordered list # ordered list : indent ; small heading ---- horizontal line
          =Heading= * unordered list # ordered list : indent ; small heading ---- horizontal line
      ''italic'' '''bold'''
          ''italic'' '''bold'''
      three tildes username four tildes signature five tildes timestamp
          three tildes username four tildes signature five tildes timestamp
      &entity;
          &entity;


  The tag-matching regex follows the XML standard closely so that users
      The tag-matching regex follows the XML standard closely so that users
  won't feel like they have to escape sequences that MediaWiki will never
      won't feel like they have to escape sequences that MediaWiki will never
  consider to be tags.
      consider to be tags.


  Only entities for characters which need to be escaped or cannot be
      Only entities for characters which need to be escaped or cannot be
  unambiguously represented in a monospace font are highlighted, such as
      unambiguously represented in a monospace font are highlighted, such as
  Greek letters that strongly resemble Latin letters. Use of other entities
      Greek letters that strongly resemble Latin letters. Use of other entities
  is discouraged as a matter of style. For the same reasons, numeric
      is discouraged as a matter of style. For the same reasons, numeric
  entities should be in hexadecimal (giving character codes in decimal only
      entities should be in hexadecimal (giving character codes in decimal only
  adds confusion).
      adds confusion).


  Newlines are sucked up into ending tokens (including comments, bare
      Newlines are sucked up into ending tokens (including comments, bare
  external links, lists, horizontal lines, signatures, entities, etc.) to
      external links, lists, horizontal lines, signatures, entities, etc.) to
  avoid creating spans with nothing but newlines in them.
      avoid creating spans with nothing but newlines in them.


  Flags: g for global search, m for make ^ match the beginning of each line
      Flags: g for global search, m for make ^ match the beginning of each line
  and $ the end of each line
      and $ the end of each line
*/
    */
var wgUrlProtocols = mw.config.get("wgUrlProtocols");
    var wgUrlProtocols = mw.config.get("wgUrlProtocols");
var entityRegexBase = "&(?:(?:n(?:bsp|dash)|m(?:dash|inus)|lt|e[mn]sp|thinsp|amp|quot|gt|shy|zwn?j|lrm|rlm|Alpha|Beta|Epsilon|Zeta|Eta|Iota|Kappa|[Mm]u|micro|Nu|[Oo]micron|[Rr]ho|Tau|Upsilon|Chi)|#x[0-9a-fA-F]+);\n*";
    var entityRegexBase = "&(?:(?:n(?:bsp|dash)|m(?:dash|inus)|lt|e[mn]sp|thinsp|amp|quot|gt|shy|zwn?j|lrm|rlm|Alpha|Beta|Epsilon|Zeta|Eta|Iota|Kappa|[Mm]u|micro|Nu|[Oo]micron|[Rr]ho|Tau|Upsilon|Chi)|#x[0-9a-fA-F]+);\n*";
var breakerRegexBase = "\\[(?:\\[|(?:" + wgUrlProtocols + "))|\\{(?:\\{\\{?|\\|)|<(?:[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:\\w\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD-\\.\u00B7\u0300-\u036F\u203F-\u203F-\u2040]*(?=/?>| |\n)|!--[^]*?-->\n*)|(?:" + wgUrlProtocols.replace("|\\/\\/", "") + ")[^\\s\"<>[\\]{-}]*[^\\s\",\\.:;<>[\\]{-}]\n*|^(?:=|[*#:;]+\n*|-{4,}\n*)|\\\\'\\\\'(?:\\\\')?|~{3,5}\n*|" + entityRegexBase;
    var breakerRegexBase = "\\[(?:\\[|(?:" + wgUrlProtocols + "))|\\{(?:\\{\\{?|\\|)|<(?:[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:\\w\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD-\\.\u00B7\u0300-\u036F\u203F-\u203F-\u2040]*(?=/?>| |\n)|!--[^]*?-->\n*)|(?:" + wgUrlProtocols.replace("|\\/\\/", "") + ")[^\\s\"<>[\\]{-}]*[^\\s\",\\.:;<>[\\]{-}]\n*|^(?:=|[*#:;]+\n*|-{4,}\n*)|\\\\'\\\\'(?:\\\\')?|~{3,5}\n*|" + entityRegexBase;
function breakerRegexWithPrefix(prefix)
    function breakerRegexWithPrefix(prefix)
{
    {
    //the stop token has to be at the beginning of the regex so that it takes precedence over substrings of itself.
        //the stop token has to be at the beginning of the regex so that it takes precedence over substrings of itself.
    return new RegExp("(" + prefix + ")\n*|" + breakerRegexBase, "gm");
        return new RegExp("(" + prefix + ")\n*|" + breakerRegexBase, "gm");
}
    }
function nowikiTagBreakerRegex(tagName)
    function nowikiTagBreakerRegex(tagName)
{
    {
    return new RegExp("(</" + tagName + ">)\n*|" + entityRegexBase, "gm");
        return new RegExp("(</" + tagName + ">)\n*|" + entityRegexBase, "gm");
}
    }
var defaultBreakerRegex          = new RegExp(breakerRegexBase, "gm");
    var defaultBreakerRegex          = new RegExp(breakerRegexBase, "gm");
var wikilinkBreakerRegex          = breakerRegexWithPrefix("]][a-zA-Z]*");
    var wikilinkBreakerRegex          = breakerRegexWithPrefix("]][a-zA-Z]*");
var namedExternalLinkBreakerRegex = breakerRegexWithPrefix("]");
    var namedExternalLinkBreakerRegex = breakerRegexWithPrefix("]");
var parameterBreakerRegex        = breakerRegexWithPrefix("}}}");
    var parameterBreakerRegex        = breakerRegexWithPrefix("}}}");
var templateBreakerRegex          = breakerRegexWithPrefix("}}");
    var templateBreakerRegex          = breakerRegexWithPrefix("}}");
var tableBreakerRegex            = breakerRegexWithPrefix("\\|}");
    var tableBreakerRegex            = breakerRegexWithPrefix("\\|}");
var headingBreakerRegex          = breakerRegexWithPrefix("\n");
    var headingBreakerRegex          = breakerRegexWithPrefix("\n");
var tagBreakerRegexCache          = {};
    var tagBreakerRegexCache          = {};
var nowikiTagBreakerRegexCache    = {};
    var nowikiTagBreakerRegexCache    = {};


function highlightSyntax()
    function highlightSyntax()
{
    {
    lastText = wpTextbox1.value;
        lastText = wpTextbox1.value;
    /* Backslashes and apostrophes are CSS-escaped at the beginning and all
        /* Backslashes and apostrophes are CSS-escaped at the beginning and all
      parsing regexes and functions are designed to match. On the other hand,
          parsing regexes and functions are designed to match. On the other hand,
      newlines are not escaped until written so that in the regexes ^ and $
          newlines are not escaped until written so that in the regexes ^ and $
      work for both newlines and the beginning or end of the string. */
          work for both newlines and the beginning or end of the string. */
    var text = lastText.replace(/['\\]/g, "\\$&") + "\n"; //add a newline to fix scrolling and parsing issues
        var text = lastText.replace(/['\\]/g, "\\$&") + "\n"; //add a newline to fix scrolling and parsing issues
    var i = 0; //the location of the parser as it goes through var text
        var i = 0; //the location of the parser as it goes through var text


    var css = "";
        var css = "";
    var spanNumber = 0;
        var spanNumber = 0;
    var lastColor;
        var lastColor;
    var before = true;
        var before = true;


    //writes text into to-be-created span elements of wpTextbox0 using :before and :after pseudo-elements
        //writes text into to-be-created span elements of wpTextbox0 using :before and :after pseudo-elements
    //both :before and :after are used because using two pseudo-elements per span is significantly faster than doubling the number of spans required
        //both :before and :after are used because using two pseudo-elements per span is significantly faster than doubling the number of spans required
    function writeText(text, color)
        function writeText(text, color)
    {
        //no need to use another span if using the same color
        if (color != lastColor)
         {
         {
             //whitespace is omitted in the hope of increasing performance
             //no need to use another span if using the same color
            css += "'}#s" + spanNumber; //spans will be created with IDs s0 through sN
             if (color != lastColor)
             if (before)
            {
                css += ":before{";
                before = false;
            }
            else
            {
                css += ":after{";
                before = true;
                ++spanNumber;
            }
            if (color)
             {
             {
                 //"background-color" is 6 characters longer than "background" but the browser processes it faster
                 //whitespace is omitted in the hope of increasing performance
                css += "background-color:" + color + ";";
                css += "'}#s" + spanNumber; //spans will be created with IDs s0 through sN
                if (before)
                {
                    css += ":before{";
                    before = false;
                }
                else
                {
                    css += ":after{";
                    before = true;
                    ++spanNumber;
                }
                if (color)
                {
                    //"background-color" is 6 characters longer than "background" but the browser processes it faster
                    css += "background-color:" + color + ";";
                }
                css += "content:'";
                lastColor = color;
             }
             }
             css += "content:'";
             css += text;
            lastColor = color;
         }
         }
        css += text;
    }


    /* About assumedBold and assumedItalic:
        /* About assumedBold and assumedItalic:


      Highlighting bold or italic markup presents a special challenge
          Highlighting bold or italic markup presents a special challenge
      because the actual MediaWiki parser uses multiple passes to determine
          because the actual MediaWiki parser uses multiple passes to determine
      which ticks represent start tags and which represent end tags.
          which ticks represent start tags and which represent end tags.
      Because that would be too slow for us here, we instead keep track of
          Because that would be too slow for us here, we instead keep track of
      what kinds of unclosed opening ticks have been encountered and use
          what kinds of unclosed opening ticks have been encountered and use
      that to make a good guess as to whether the next ticks encountered
          that to make a good guess as to whether the next ticks encountered
      are an opening tag or a closing tag.
          are an opening tag or a closing tag.


      The major downsides to this method are that '''apostrophe italic''
          The major downsides to this method are that '''apostrophe italic''
      and ''italic apostrophe''' are not highlighted correctly, and bold
          and ''italic apostrophe''' are not highlighted correctly, and bold
      and italic are both highlighted in the same color.
          and italic are both highlighted in the same color.


      To permit ''The best<ref>''Reference Title''</ref> book ever'',
          To permit ''The best<ref>''Reference Title''</ref> book ever'',
      assumedBold and assumedItalic are saved on the stack and reset to
          assumedBold and assumedItalic are saved on the stack and reset to
      undefined (essentially, false) when recursing into a new block. */
          undefined (essentially, false) when recursing into a new block. */


    function highlightBlock(color, breakerRegex, assumedBold, assumedItalic)
        function highlightBlock(color, breakerRegex, assumedBold, assumedItalic)
    {
        {
        var match;
            var match;


        for (breakerRegex.lastIndex = i; match = breakerRegex.exec(text); breakerRegex.lastIndex = i)
            for (breakerRegex.lastIndex = i; match = breakerRegex.exec(text); breakerRegex.lastIndex = i)
        {
            if (match[1])
             {
             {
                 //end token found
                 if (match[1])
                writeText(text.substring(i, breakerRegex.lastIndex), color);
                {
                i = breakerRegex.lastIndex;
                    //end token found
                return;
                    writeText(text.substring(i, breakerRegex.lastIndex), color);
            }
                    i = breakerRegex.lastIndex;
                    return;
                }


            var endIndexOfLastColor = breakerRegex.lastIndex - match[0].length;
                var endIndexOfLastColor = breakerRegex.lastIndex - match[0].length;
            if (i < endIndexOfLastColor) //avoid calling writeText with text == "" to improve performance
                if (i < endIndexOfLastColor) //avoid calling writeText with text == "" to improve performance
            {
                {
                writeText(text.substring(i, endIndexOfLastColor), color);
                    writeText(text.substring(i, endIndexOfLastColor), color);
            }
                }


            i = breakerRegex.lastIndex;
                i = breakerRegex.lastIndex;


            switch (match[0].charAt(0)) //cases in this switch should be arranged from most common to least common
                switch (match[0].charAt(0)) //cases in this switch should be arranged from most common to least common
            {
                {
                case "[":
                    case "[":
                    if (match[0].charAt(1) == "[")
                        if (match[0].charAt(1) == "[")
                    {
                        //wikilink
                        writeText("[[", syntaxHighlighterConfig.wikilinkColor || color);
                        highlightBlock(syntaxHighlighterConfig.wikilinkColor || color, wikilinkBreakerRegex);
                    }
                    else
                    {
                        //named external link
                        writeText(match[0], syntaxHighlighterConfig.externalLinkColor || color);
                        highlightBlock(syntaxHighlighterConfig.externalLinkColor || color, namedExternalLinkBreakerRegex);
                    }
                    break;
                case "{":
                    if (match[0].charAt(1) == "{")
                    {
                        if (match[0].length == 3)
                         {
                         {
                             //parameter
                             //wikilink
                             writeText("{{{", syntaxHighlighterConfig.parameterColor || color);
                             writeText("[[", syntaxHighlighterConfig.wikilinkColor || color);
                             highlightBlock(syntaxHighlighterConfig.parameterColor || color, parameterBreakerRegex);
                             highlightBlock(syntaxHighlighterConfig.wikilinkColor || color, wikilinkBreakerRegex);
                         }
                         }
                         else
                         else
                         {
                         {
                             //template
                             //named external link
                             writeText("{{", syntaxHighlighterConfig.templateColor || color);
                             writeText(match[0], syntaxHighlighterConfig.externalLinkColor || color);
                             highlightBlock(syntaxHighlighterConfig.templateColor || color, templateBreakerRegex);
                             highlightBlock(syntaxHighlighterConfig.externalLinkColor || color, namedExternalLinkBreakerRegex);
                         }
                         }
                    }
                    else //|
                    {
                        //table
                        writeText("{|", syntaxHighlighterConfig.tableColor || color);
                        highlightBlock(syntaxHighlighterConfig.tableColor || color, tableBreakerRegex);
                    }
                    break;
                case "<":
                    if (match[0].charAt(1) == "!")
                    {
                        //comment tag
                        writeText(match[0], syntaxHighlighterConfig.commentColor || color);
                         break;
                         break;
                     }
                     case "{":
                    else
                        if (match[0].charAt(1) == "{")
                    {
                        {
                        //some other kind of tag, search for its end
                            if (match[0].length == 3)
                        //the search is made easier because XML attributes may not contain the character ">"
                            {
                        var tagEnd = text.indexOf(">", i) + 1;
                                //parameter
                        if (tagEnd == 0)
                                writeText("{{{", syntaxHighlighterConfig.parameterColor || color);
                                highlightBlock(syntaxHighlighterConfig.parameterColor || color, parameterBreakerRegex);
                            }
                            else
                            {
                                //template
                                writeText("{{", syntaxHighlighterConfig.templateColor || color);
                                highlightBlock(syntaxHighlighterConfig.templateColor || color, templateBreakerRegex);
                            }
                        }
                        else //|
                         {
                         {
                             //not a tag, just a "<" with some text after it
                             //table
                             writeText("<", color);
                             writeText("{|", syntaxHighlighterConfig.tableColor || color);
                             i = i - match[0].length + 1;
                             highlightBlock(syntaxHighlighterConfig.tableColor || color, tableBreakerRegex);
                            break;
                         }
                         }
 
                        break;
                         if (text.charAt(tagEnd - 2) == "/")
                    case "<":
                         if (match[0].charAt(1) == "!")
                         {
                         {
                             //empty tag
                             //comment tag
                             writeText(text.substring(i - match[0].length, tagEnd), syntaxHighlighterConfig.tagColor || color);
                             writeText(match[0], syntaxHighlighterConfig.commentColor || color);
                             i = tagEnd;
                             break;
                         }
                         }
                         else
                         else
                         {
                         {
                             var tagName = match[0].substring(1);
                            //some other kind of tag, search for its end
 
                            //the search is made easier because XML attributes may not contain the character ">"
                             if (syntaxHighlighterConfig.sourceTags.indexOf(tagName) != -1)
                             var tagEnd = text.indexOf(">", i) + 1;
                             if (tagEnd == 0)
                             {
                             {
                                 //tag that contains text in a different programming language
                                 //not a tag, just a "<" with some text after it
                                var stopAfter = "</" + tagName + ">";
                                 writeText("<", color);
                                 var endIndex = text.indexOf(stopAfter, i);
                                 i = i - match[0].length + 1;
                                 if (endIndex == -1)
                                 break;
                                {
                                    endIndex = text.length;
                                }
                                else
                                {
                                    endIndex += stopAfter.length;
                                }
                                writeText(text.substring(i - match[0].length, endIndex), syntaxHighlighterConfig.tagColor || color);
                                 i = endIndex;
                             }
                             }
                             else if (syntaxHighlighterConfig.nowikiTags.indexOf(tagName) != -1)
 
                             if (text.charAt(tagEnd - 2) == "/")
                             {
                             {
                                 //tag that can contain only HTML entities
                                 //empty tag
                                 writeText(text.substring(i - match[0].length, tagEnd), syntaxHighlighterConfig.tagColor || color);
                                 writeText(text.substring(i - match[0].length, tagEnd), syntaxHighlighterConfig.tagColor || color);
                                 i = tagEnd;
                                 i = tagEnd;
                                highlightBlock(syntaxHighlighterConfig.tagColor || color, nowikiTagBreakerRegexCache[tagName]);
                             }
                             }
                             else
                             else
                             {
                             {
                                 //ordinary tag
                                 var tagName = match[0].substring(1);
                                 writeText(text.substring(i - match[0].length, tagEnd), syntaxHighlighterConfig.tagColor || color);
 
                                i = tagEnd;
                                if (syntaxHighlighterConfig.sourceTags.indexOf(tagName) != -1)
                                if (!tagBreakerRegexCache[tagName])
                                {
                                    //tag that contains text in a different programming language
                                    var stopAfter = "</" + tagName + ">";
                                    var endIndex = text.indexOf(stopAfter, i);
                                    if (endIndex == -1)
                                    {
                                        endIndex = text.length;
                                    }
                                    else
                                    {
                                        endIndex += stopAfter.length;
                                    }
                                    writeText(text.substring(i - match[0].length, endIndex), syntaxHighlighterConfig.tagColor || color);
                                    i = endIndex;
                                }
                                 else if (syntaxHighlighterConfig.nowikiTags.indexOf(tagName) != -1)
                                {
                                    //tag that can contain only HTML entities
                                    writeText(text.substring(i - match[0].length, tagEnd), syntaxHighlighterConfig.tagColor || color);
                                    i = tagEnd;
                                    highlightBlock(syntaxHighlighterConfig.tagColor || color, nowikiTagBreakerRegexCache[tagName]);
                                }
                                else
                                 {
                                 {
                                     tagBreakerRegexCache[tagName] = breakerRegexWithPrefix("</" + tagName + ">");
                                     //ordinary tag
                                    writeText(text.substring(i - match[0].length, tagEnd), syntaxHighlighterConfig.tagColor || color);
                                    i = tagEnd;
                                    if (!tagBreakerRegexCache[tagName])
                                    {
                                        tagBreakerRegexCache[tagName] = breakerRegexWithPrefix("</" + tagName + ">");
                                    }
                                    highlightBlock(syntaxHighlighterConfig.tagColor || color, tagBreakerRegexCache[tagName]);
                                 }
                                 }
                                highlightBlock(syntaxHighlighterConfig.tagColor || color, tagBreakerRegexCache[tagName]);
                             }
                             }
                         }
                         }
                    }
                        break;
                    break;
                    case "=":
                case "=":
                        if (/[^=]=+$/.test(text.substring(i, text.indexOf("\n", i)))) //the line begins and ends with an equals sign and has something else in the middle
                    if (/[^=]=+$/.test(text.substring(i, text.indexOf("\n", i)))) //the line begins and ends with an equals sign and has something else in the middle
                    {
                        //heading
                        writeText("=", syntaxHighlighterConfig.headingColor || color);
                        highlightBlock(syntaxHighlighterConfig.headingColor || color, headingBreakerRegex);
                    }
                    else
                    {
                        writeText("=", color); //move on, process this line as regular wikitext
                    }
                    break;
                case "*":
                case "#":
                case ":":
                    //unordered list, ordered list, indent, small heading
                    //just highlight the marker
                    writeText(match[0], syntaxHighlighterConfig.listOrIndentColor || color);
                    break;
                case ";":
                    //small heading
                    writeText(";", syntaxHighlighterConfig.headingColor || color);
                    highlightBlock(syntaxHighlighterConfig.headingColor || color, headingBreakerRegex);
                    break;
                case "-":
                    //horizontal line
                    writeText(match[0], syntaxHighlighterConfig.hrColor || color);
                    break;
                case "\\":
                    writeText(match[0], syntaxHighlighterConfig.boldOrItalicColor || color);
                    if (match[0].length == 6)
                    {
                        //bold
                        if (assumedBold)
                         {
                         {
                             //end tag
                             //heading
                             if (assumedItalic)
                             writeText("=", syntaxHighlighterConfig.headingColor || color);
                            {
                             highlightBlock(syntaxHighlighterConfig.headingColor || color, headingBreakerRegex);
                                //end of bold part of bold-italic block
                                //block is now italic-only
                                assumedBold = false;
                             }
                            else
                            {
                                //end of bold block
                                return;
                            }
                         }
                         }
                         else
                         else
                         {
                         {
                             //start tag
                             writeText("=", color); //move on, process this line as regular wikitext
                            if (assumedItalic)
                            {
                                //start of bold part of previously italic-only block
                                //block is now bold-italic
                                assumedBold = true;
                            }
                            else
                            {
                                //start of bold block
                                highlightBlock(syntaxHighlighterConfig.boldOrItalicColor || color, defaultBreakerRegex, true, false);
                            }
                         }
                         }
                     }
                        break;
                     else
                     case "*":
                     {
                     case "#":
                         //italic
                     case ":":
                         if (assumedItalic)
                        //unordered list, ordered list, indent, small heading
                         //just highlight the marker
                        writeText(match[0], syntaxHighlighterConfig.listOrIndentColor || color);
                        break;
                    case ";":
                        //small heading
                        writeText(";", syntaxHighlighterConfig.headingColor || color);
                        highlightBlock(syntaxHighlighterConfig.headingColor || color, headingBreakerRegex);
                        break;
                    case "-":
                        //horizontal line
                        writeText(match[0], syntaxHighlighterConfig.hrColor || color);
                        break;
                    case "\\":
                        writeText(match[0], syntaxHighlighterConfig.boldOrItalicColor || color);
                         if (match[0].length == 6)
                         {
                         {
                             //end tag
                             //bold
                             if (assumedBold)
                             if (assumedBold)
                             {
                             {
                                 //end of italic part of bold-italic block
                                 //end tag
                                //block is now bold-only
                                if (assumedItalic)
                                assumedItalic = false;
                                {
                                    //end of bold part of bold-italic block
                                    //block is now italic-only
                                    assumedBold = false;
                                }
                                else
                                {
                                    //end of bold block
                                    return;
                                }
                             }
                             }
                             else
                             else
                             {
                             {
                                 //end of italic block
                                 //start tag
                                 return;
                                if (assumedItalic)
                                {
                                    //start of bold part of previously italic-only block
                                    //block is now bold-italic
                                    assumedBold = true;
                                 }
                                else
                                {
                                    //start of bold block
                                    highlightBlock(syntaxHighlighterConfig.boldOrItalicColor || color, defaultBreakerRegex, true, false);
                                }
                             }
                             }
                         }
                         }
                         else
                         else
                         {
                         {
                             //start tag
                             //italic
                             if (assumedBold)
                             if (assumedItalic)
                             {
                             {
                                 //start of italic part of previously bold-only block
                                 //end tag
                                //block is now bold-italic
                                if (assumedBold)
                                assumedItalic = true;
                                {
                                    //end of italic part of bold-italic block
                                    //block is now bold-only
                                    assumedItalic = false;
                                }
                                else
                                {
                                    //end of italic block
                                    return;
                                }
                             }
                             }
                             else
                             else
                             {
                             {
                                 //start of italic block
                                 //start tag
                                 highlightBlock(syntaxHighlighterConfig.boldOrItalicColor || color, defaultBreakerRegex, false, true);
                                if (assumedBold)
                                {
                                    //start of italic part of previously bold-only block
                                    //block is now bold-italic
                                    assumedItalic = true;
                                }
                                else
                                 {
                                    //start of italic block
                                    highlightBlock(syntaxHighlighterConfig.boldOrItalicColor || color, defaultBreakerRegex, false, true);
                                }
                             }
                             }
                         }
                         }
                    }
                        break;
                    break;
                    case "&":
                case "&":
                        //entity
                    //entity
                        writeText(match[0], syntaxHighlighterConfig.entityColor || color);
                    writeText(match[0], syntaxHighlighterConfig.entityColor || color);
                        break;
                    break;
                    case "~":
                case "~":
                        //username, signature, timestamp
                    //username, signature, timestamp
                        writeText(match[0], syntaxHighlighterConfig.signatureColor || color);
                    writeText(match[0], syntaxHighlighterConfig.signatureColor || color);
                        break;
                    break;
                    default:
                default:
                        //bare external link
                    //bare external link
                        writeText(match[0], syntaxHighlighterConfig.externalLinkColor || color);
                    writeText(match[0], syntaxHighlighterConfig.externalLinkColor || color);
                }
             }
             }
         }
         }
    }




    //start!
        //start!
    var startTime = Date.now();
        var startTime = Date.now();
    highlightBlock("", defaultBreakerRegex);
        highlightBlock("", defaultBreakerRegex);


    //output the leftovers (if any) to make sure whitespace etc. matches
        //output the leftovers (if any) to make sure whitespace etc. matches
    if (i < text.length)
        if (i < text.length)
    {
        {
        writeText(text.substring(i), "");
            writeText(text.substring(i), "");
    }
        }


    //if highlighting took too long, disable it.
        //if highlighting took too long, disable it.
    var endTime = Date.now();
        var endTime = Date.now();
    /*if (typeof(bestTime) == "undefined")
        /*if (typeof(bestTime) == "undefined")
    {
        window.bestTime = endTime - startTime;
        document.title = bestTime;
        highlightSyntaxIfNeededIntervalID = setInterval(highlightSyntax, 250);
    }
    else
    {
        if (endTime - startTime < bestTime)
         {
         {
             bestTime = endTime - startTime;
             window.bestTime = endTime - startTime;
             document.title = bestTime;
             document.title = bestTime;
            highlightSyntaxIfNeededIntervalID = setInterval(highlightSyntax, 250);
         }
         }
    }//*/
        else
    if (endTime - startTime > syntaxHighlighterConfig.timeout)
        {
    {
            if (endTime - startTime < bestTime)
        clearInterval(highlightSyntaxIfNeededIntervalID);
            {
        wpTextbox1.removeEventListener("input", highlightSyntax);
                bestTime = endTime - startTime;
        wpTextbox1.removeEventListener("scroll", syncScrollX);
                document.title = bestTime;
        wpTextbox1.removeEventListener("scroll", syncScrollY);
            }
        attributeObserver.disconnect();
        }//*/
        parentObserver.disconnect();
        if (endTime - startTime > syntaxHighlighterConfig.timeout)
        syntaxStyleTextNode.nodeValue = "";
        {
            clearInterval(highlightSyntaxIfNeededIntervalID);
            wpTextbox1.removeEventListener("input", highlightSyntax);
            wpTextbox1.removeEventListener("scroll", syncScrollX);
            wpTextbox1.removeEventListener("scroll", syncScrollY);
            attributeObserver.disconnect();
            parentObserver.disconnect();
            syntaxStyleTextNode.nodeValue = "";


        var errorMessage = {
            var errorMessage = {
            be: "Падсьветка сынтаксісу на гэтай старонцы была адключаная, бо заняла шмат часу. Максымальна дапушчальны час апэрацыі — $1мс, а на вашым кампутары яна заняла $2мс. Паспрабуйце зачыніць нейкія закладкі і праграмы і націснуць «Праглядзець» або «Паказаць зьмены». Калі гэта не дапаможа, паспрабуйце іншы броўзэр; калі й гэта не дапаможа, выкарыстайце магутнейшы кампутар.",
                be: "Падсьветка сынтаксісу на гэтай старонцы была адключаная, бо заняла шмат часу. Максымальна дапушчальны час апэрацыі — $1мс, а на вашым кампутары яна заняла $2мс. Паспрабуйце зачыніць нейкія закладкі і праграмы і націснуць «Праглядзець» або «Паказаць зьмены». Калі гэта не дапаможа, паспрабуйце іншы броўзэр; калі й гэта не дапаможа, выкарыстайце магутнейшы кампутар.",
            ca: "S'ha desactivat el remarcar de sintaxi en aquesta pàgina perquè ha trigat massa temps. El temps màxim permès per a remarcar és $1ms, i el vostre ordinador ha trigat $2ms. Proveu tancar algunes pestanyes i programes i fer clic en \"Mostra la previsualització\" o \"Mostra els canvis\". Si no funciona això, proveu un altre navegador web, i si això no funciona, proveu un ordinador més ràpid.",
                ca: "S'ha desactivat el remarcar de sintaxi en aquesta pàgina perquè ha trigat massa temps. El temps màxim permès per a remarcar és $1ms, i el vostre ordinador ha trigat $2ms. Proveu tancar algunes pestanyes i programes i fer clic en \"Mostra la previsualització\" o \"Mostra els canvis\". Si no funciona això, proveu un altre navegador web, i si això no funciona, proveu un ordinador més ràpid.",
            de: "Die Syntaxhervorhebung wurde auf dieser Seite deaktiviert, da diese zu lange gedauert hat. Die maximal erlaubte Zeit zur Hervorhebung beträgt $1ms und dein Computer benötigte $2ms. Versuche einige Tabs und Programme zu schließen und klicke \"Vorschau zeigen\" oder \"Änderungen zeigen\". Wenn das nicht funktioniert, probiere einen anderen Webbrowser und wenn immer noch nicht, probiere einen schnelleren Computer.",
                de: "Die Syntaxhervorhebung wurde auf dieser Seite deaktiviert, da diese zu lange gedauert hat. Die maximal erlaubte Zeit zur Hervorhebung beträgt $1ms und dein Computer benötigte $2ms. Versuche einige Tabs und Programme zu schließen und klicke \"Vorschau zeigen\" oder \"Änderungen zeigen\". Wenn das nicht funktioniert, probiere einen anderen Webbrowser und wenn immer noch nicht, probiere einen schnelleren Computer.",
            el: "Η έμφαση σύνταξης έχει απενεργοποιηθεί σε αυτήν τη σελίδα γιατί αργούσε πολύ. Ο μέγιστος επιτρεπτός χρόνος για την έμφαση σύνταξης είναι $1ms και ο υπολογιστής σας έκανε $2ms. Δοκιμάστε να κλείσετε μερικές καρτέλες και προγράμματα και να κάνετε κλικ στην «Εμφάνιση προεπισκόπησης» ή στην «Εμφάνιση αλλαγών». Αν αυτό δεν δουλέψει, δοκιμάστε έναν διαφορετικό περιηγητή και αν ούτε αυτό δουλέψει, δοκιμάστε έναν ταχύτερο υπολογιστή.",
                el: "Η έμφαση σύνταξης έχει απενεργοποιηθεί σε αυτήν τη σελίδα γιατί αργούσε πολύ. Ο μέγιστος επιτρεπτός χρόνος για την έμφαση σύνταξης είναι $1ms και ο υπολογιστής σας έκανε $2ms. Δοκιμάστε να κλείσετε μερικές καρτέλες και προγράμματα και να κάνετε κλικ στην «Εμφάνιση προεπισκόπησης» ή στην «Εμφάνιση αλλαγών». Αν αυτό δεν δουλέψει, δοκιμάστε έναν διαφορετικό περιηγητή και αν ούτε αυτό δουλέψει, δοκιμάστε έναν ταχύτερο υπολογιστή.",
            en: "Syntax highlighting on this page was disabled because it took too long. The maximum allowed highlighting time is $1ms, and your computer took $2ms. Try closing some tabs and programs and clicking \"Show preview\" or \"Show changes\". If that doesn't work, try a different web browser, and if that doesn't work, try a faster computer.",
                en: "Syntax highlighting on this page was disabled because it took too long. The maximum allowed highlighting time is $1ms, and your computer took $2ms. Try closing some tabs and programs and clicking \"Show preview\" or \"Show changes\". If that doesn't work, try a different web browser, and if that doesn't work, try a faster computer.",
            es: "Se desactivó el resaltar de sintaxis en esta página porque tardó demasiado. El tiempo máximo permitido para resaltar es $1ms, y tu ordenador tardó $2ms. Prueba cerrar algunas pestañas y programas y hacer clic en \"Mostrar previsualización\" o \"Mostrar cambios\". Si no funciona esto, prueba otro navegador web, y si eso no funciona, prueba un ordenador más rápido.",
                es: "Se desactivó el resaltar de sintaxis en esta página porque tardó demasiado. El tiempo máximo permitido para resaltar es $1ms, y tu ordenador tardó $2ms. Prueba cerrar algunas pestañas y programas y hacer clic en \"Mostrar previsualización\" o \"Mostrar cambios\". Si no funciona esto, prueba otro navegador web, y si eso no funciona, prueba un ordenador más rápido.",
            fa: "از آنجایی که زمان زیادی صرف آن می‌شد، برجسته‌سازی نحو در این صفحه غیرفعال شده است. بیشینهٔ زمان برجسته‌سازی برای ابزار $1ms تعریف شده در حالی که رایانهٔ شما $2ms زمان نیاز داشت. می‌توانید بستن برخی سربرگ‌ها و برنامه‌ها و سپس کلیک‌کردن دکمهٔ «پیش‌نمایش» یا «نمایش تغییرات» را بیازمایید. اگر جواب نداد مرورگر دیگری را امتحان کنید؛ و اگر باز هم جواب نداد، رایانهٔ سریع‌تری را بیازمایید.",
                fa: "از آنجایی که زمان زیادی صرف آن می‌شد، برجسته‌سازی نحو در این صفحه غیرفعال شده است. بیشینهٔ زمان برجسته‌سازی برای ابزار $1ms تعریف شده در حالی که رایانهٔ شما $2ms زمان نیاز داشت. می‌توانید بستن برخی سربرگ‌ها و برنامه‌ها و سپس کلیک‌کردن دکمهٔ «پیش‌نمایش» یا «نمایش تغییرات» را بیازمایید. اگر جواب نداد مرورگر دیگری را امتحان کنید؛ و اگر باز هم جواب نداد، رایانهٔ سریع‌تری را بیازمایید.",
            fr: "La coloration syntaxique a été désactivée sur cette page en raison d'un temps de chargement trop important ($2ms). Le temps maximum autorisé est $1ms. Vous pouvez essayer de fermer certains onglets et programmes et cliquez sur \"Prévisualisation\" ou \"Voir mes modifications\". Si cela ne fonctionne pas, essayez un autre navigateur web, et si cela ne fonctionne toujours pas, essayez un ordinateur plus rapide.",
                fr: "La coloration syntaxique a été désactivée sur cette page en raison d'un temps de chargement trop important ($2ms). Le temps maximum autorisé est $1ms. Vous pouvez essayer de fermer certains onglets et programmes et cliquez sur \"Prévisualisation\" ou \"Voir mes modifications\". Si cela ne fonctionne pas, essayez un autre navigateur web, et si cela ne fonctionne toujours pas, essayez un ordinateur plus rapide.",
            hy: "Շարադասության ընդգծումը այս էջում անջատվել է, քանի որ այն չափից շատ է տևել։ Ընդգծման թույլատրելի առավելագույն ժամանակը $1 միլիվայրկյան է, բայց այս էջում տևել է $2 միլիվայրկյան։ Փորձեք անջատել որոշ ներդիրներ կամ ծրագրեր և սեղմել «Նախադիտել» կամ «Կատարված փոփոխությունները»։ Կրկին չաշխատելու դեպքում փորձեք այլ վեբ դիտարկիչ, եթե կրկին չաշխատի, փորձեք ավելի արագ համակարգիչ։",
                hy: "Շարադասության ընդգծումը այս էջում անջատվել է, քանի որ այն չափից շատ է տևել։ Ընդգծման թույլատրելի առավելագույն ժամանակը $1 միլիվայրկյան է, բայց այս էջում տևել է $2 միլիվայրկյան։ Փորձեք անջատել որոշ ներդիրներ կամ ծրագրեր և սեղմել «Նախադիտել» կամ «Կատարված փոփոխությունները»։ Կրկին չաշխատելու դեպքում փորձեք այլ վեբ դիտարկիչ, եթե կրկին չաշխատի, փորձեք ավելի արագ համակարգիչ։",
            io: "Sintaxo-hailaitar en ca pagino esis nekapabligata pro ke konsumis tro multa tempo. La maxima permisata hailaitala tempo es $1ms, e tua ordinatro konsumis $2ms. Probez klozar kelka tabi e programi e kliktar \"Previdar\" o \"Montrez chanji\". Se to ne funcionas, probez altra brauzero, e se to ne funcionas, probez plu rapida ordinatro.",
                io: "Sintaxo-hailaitar en ca pagino esis nekapabligata pro ke konsumis tro multa tempo. La maxima permisata hailaitala tempo es $1ms, e tua ordinatro konsumis $2ms. Probez klozar kelka tabi e programi e kliktar \"Previdar\" o \"Montrez chanji\". Se to ne funcionas, probez altra brauzero, e se to ne funcionas, probez plu rapida ordinatro.",
            it: "L'evidenziazione delle sintassi su questa pagina è stata disabilitata perché ha richiesto troppo tempo. Il tempo massimo per l'evidenziazione è di $1ms e al tuo computer sono serviti $2ms. Prova a chiudere alcune schede e programmi e ricarica la pagina cliccando su \"Visualizza anteprima\" o \"Mostra modifiche\". Se non funziona ancora, prova con un web browser differente e, in ultima alternativa, prova ad utilizzare un computer più veloce.",
                it: "L'evidenziazione delle sintassi su questa pagina è stata disabilitata perché ha richiesto troppo tempo. Il tempo massimo per l'evidenziazione è di $1ms e al tuo computer sono serviti $2ms. Prova a chiudere alcune schede e programmi e ricarica la pagina cliccando su \"Visualizza anteprima\" o \"Mostra modifiche\". Se non funziona ancora, prova con un web browser differente e, in ultima alternativa, prova ad utilizzare un computer più veloce.",
            ko: "이 문서에서의 문법 강조가 너무 오래 걸러서 해제되었습니다. 최대로 할당된 강조 시간은 $1ms인데, 당신의 컴퓨터는 $2ms이나 걸렸습니다. 탭과 프로그램을 일부 닫으신 후에 \"미리 보기\"나 \"차이 보기\"를 클릭하시기 바랍니다. 만약 작동하지 않으면 다른 웹 브라우저로 시도해보시고, 그래도 안되면 더 빠른 컴퓨터를 이용하십시오",
                ko: "이 문서에서의 문법 강조가 너무 오래 걸러서 해제되었습니다. 최대로 할당된 강조 시간은 $1ms인데, 당신의 컴퓨터는 $2ms이나 걸렸습니다. 탭과 프로그램을 일부 닫으신 후에 \"미리 보기\"나 \"차이 보기\"를 클릭하시기 바랍니다. 만약 작동하지 않으면 다른 웹 브라우저로 시도해보시고, 그래도 안되면 더 빠른 컴퓨터를 이용하십시오",
            pt: "O marcador de sintaxe foi desativado nesta página porque demorou demais. O tempo máximo permitido para marcar é de $1ms, e seu computador demorou $2ms. Tente fechar algumas abas e programas e clique em \"Mostrar previsão\" ou \"Mostrar alterações\". Se isso não funcionar, tente usar um outro navegador web, e se ainda não funcionar, tente em um computador mais rápido.",
                pt: "O marcador de sintaxe foi desativado nesta página porque demorou demais. O tempo máximo permitido para marcar é de $1ms, e seu computador demorou $2ms. Tente fechar algumas abas e programas e clique em \"Mostrar previsão\" ou \"Mostrar alterações\". Se isso não funcionar, tente usar um outro navegador web, e se ainda não funcionar, tente em um computador mais rápido.",
            ru: "Подсветка синтаксиса на странице была отключена, так как заняла слишком долго. Максимальное допустимое время операции - $1мс, сейчас на вашем компьютере она заняла $2мс. Попробуйте закрыть несколько вкладок и программ, затем нажать «Предварительный просмотр» или «Внесённые изменения». Если это не поможет, попробуйте другой браузер; если и это не поможет, используйте более быстрый компьютер.",
                ru: "Подсветка синтаксиса на странице была отключена, так как заняла слишком долго. Максимальное допустимое время операции - $1мс, сейчас на вашем компьютере она заняла $2мс. Попробуйте закрыть несколько вкладок и программ, затем нажать «Предварительный просмотр» или «Внесённые изменения». Если это не поможет, попробуйте другой браузер; если и это не поможет, используйте более быстрый компьютер.",
            sr: "Истицање синтаксе на овој страници је онемогућено јер се одвија предуго. Максимално дозвољено време истицања је $1ms, а на Вашем рачунару траје $2ms. Покушајте затворити неке картице и програме или кликните на „Прикажи претпреглед” или „Прикажи измене”. Ако то не ради, покушајте са другим веб-прегледачем, а ако и тада не ради, покушајте са бржим рачунаром.",
                sr: "Истицање синтаксе на овој страници је онемогућено јер се одвија предуго. Максимално дозвољено време истицања је $1ms, а на Вашем рачунару траје $2ms. Покушајте затворити неке картице и програме или кликните на „Прикажи претпреглед” или „Прикажи измене”. Ако то не ради, покушајте са другим веб-прегледачем, а ако и тада не ради, покушајте са бржим рачунаром.",
        };
            };
        var wgUserLanguage = mw.config.get("wgUserLanguage");
            var wgUserLanguage = mw.config.get("wgUserLanguage");


        errorMessage = errorMessage[wgUserLanguage] || errorMessage[wgUserLanguage.substring(0, wgUserLanguage.indexOf("-"))] || errorMessage.en;
            errorMessage = errorMessage[wgUserLanguage] || errorMessage[wgUserLanguage.substring(0, wgUserLanguage.indexOf("-"))] || errorMessage.en;


        wpTextbox1.style.backgroundColor = "";
            wpTextbox1.style.backgroundColor = "";
        wpTextbox1.style.marginTop = "0";
            wpTextbox1.style.marginTop = "0";
        wpTextbox0.removeAttribute("dir");
            wpTextbox0.removeAttribute("dir");
        wpTextbox0.removeAttribute("lang");
            wpTextbox0.removeAttribute("lang");
        wpTextbox0.setAttribute("style", "color:red; font-size:small");
            wpTextbox0.setAttribute("style", "color:red; font-size:small");


        wpTextbox0.textContent = errorMessage.replace("$1", syntaxHighlighterConfig.timeout).replace("$2", endTime - startTime);
            wpTextbox0.textContent = errorMessage.replace("$1", syntaxHighlighterConfig.timeout).replace("$2", endTime - startTime);
        return;
            return;
    }
        }


    //do we have enough span elements to match the generated CSS?
        //do we have enough span elements to match the generated CSS?
    //this step isn't included in the above benchmark because it takes a highly variable amount of time
        //this step isn't included in the above benchmark because it takes a highly variable amount of time
    if (maxSpanNumber < spanNumber)
        if (maxSpanNumber < spanNumber)
    {
        var wpTextbox1Style = window.getComputedStyle(wpTextbox1);
        var fragment = document.createDocumentFragment();
        do
         {
         {
             var tag = document.createElement("span");
             var wpTextbox1Style = window.getComputedStyle(wpTextbox1);
            tag.id = "s" + ++maxSpanNumber;
            var fragment = document.createDocumentFragment();
            tag.style.fontFamily = wpTextbox1Style.fontFamily;
            do
            fragment.appendChild(tag);
            {
                var tag = document.createElement("span");
                tag.id = "s" + ++maxSpanNumber;
                tag.style.fontFamily = wpTextbox1Style.fontFamily;
                fragment.appendChild(tag);
            }
            while (maxSpanNumber < spanNumber);
            wpTextbox0.appendChild(fragment);
         }
         }
         while (maxSpanNumber < spanNumber);
 
        wpTextbox0.appendChild(fragment);
        /* finish CSS: move the extra '} from the beginning to the end and CSS-
          escape newlines. CSS ignores the space after the hex code of the
          escaped character */
         syntaxStyleTextNode.nodeValue = css.substring(2).replace(/\n/g, "\\A ") + "'}";
     }
     }


     /* finish CSS: move the extra '} from the beginning to the end and CSS-
     function syncScrollX()
      escape newlines. CSS ignores the space after the hex code of the
      escaped character */
    syntaxStyleTextNode.nodeValue = css.substring(2).replace(/\n/g, "\\A ") + "'}";
}
 
function syncScrollX()
{
    wpTextbox0.scrollLeft = wpTextbox1.scrollLeft;
}
 
function syncScrollY()
{
    wpTextbox0.scrollTop = wpTextbox1.scrollTop;
}
 
function syncTextDirection()
{
    wpTextbox0.dir = wpTextbox1.dir;
}
 
function syncParent()
{
    if (wpTextbox1.previousSibling != wpTextbox0)
     {
     {
         wpTextbox1.parentNode.insertBefore(wpTextbox0, wpTextbox1);
         wpTextbox0.scrollLeft = wpTextbox1.scrollLeft;
        parentObserver.disconnect();
        parentObserver.observe(wpTextbox1.parentNode, {childList: true});
     }
     }
}


//this function runs once every 500ms to detect changes to wpTextbox1's text that the input event does not catch
    function syncScrollY()
//this happens when another script changes the text without knowing that the syntax highlighter needs to be informed
function highlightSyntaxIfNeeded()
{
    if (wpTextbox1.value != lastText)
     {
     {
         highlightSyntax();
         wpTextbox0.scrollTop = wpTextbox1.scrollTop;
     }
     }
    if (wpTextbox1.scrollLeft != wpTextbox0.scrollLeft)
 
    {
     function syncTextDirection()
        syncScrollX();
    }
     if (wpTextbox1.scrollTop != wpTextbox0.scrollTop)
     {
     {
         syncScrollY();
         wpTextbox0.dir = wpTextbox1.dir;
     }
     }
    if (wpTextbox1.offsetHeight != wpTextbox0.offsetHeight)
    {
        var height = wpTextbox1.offsetHeight + "px";
        wpTextbox0.style.height = height;
        wpTextbox1.style.marginTop = "-" + height;
    }
}


function setup()
     function syncParent()
{
     function configureColor(parameterName, hardcodedFallback, defaultOk)
     {
     {
         if (typeof(syntaxHighlighterConfig[parameterName]) == "undefined")
         if (wpTextbox1.previousSibling != wpTextbox0)
         {
         {
             syntaxHighlighterConfig[parameterName] = syntaxHighlighterSiteConfig[parameterName];
             wpTextbox1.parentNode.insertBefore(wpTextbox0, wpTextbox1);
            parentObserver.disconnect();
            parentObserver.observe(wpTextbox1.parentNode, {childList: true});
         }
         }
    }


         if (syntaxHighlighterConfig[parameterName] == "normal")
    //this function runs once every 500ms to detect changes to wpTextbox1's text that the input event does not catch
    //this happens when another script changes the text without knowing that the syntax highlighter needs to be informed
    function highlightSyntaxIfNeeded()
    {
         if (wpTextbox1.value != lastText)
         {
         {
             syntaxHighlighterConfig[parameterName] = hardcodedFallback;
             highlightSyntax();
         }
         }
         else if (typeof(syntaxHighlighterConfig[parameterName]) != "undefined")
         if (wpTextbox1.scrollLeft != wpTextbox0.scrollLeft)
         {
         {
             return;
             syncScrollX();
         }
         }
         else if (typeof(syntaxHighlighterConfig.defaultColor) != "undefined" && defaultOk)
         if (wpTextbox1.scrollTop != wpTextbox0.scrollTop)
         {
         {
             syntaxHighlighterConfig[parameterName] = syntaxHighlighterConfig.defaultColor;
             syncScrollY();
         }
         }
         else
         if (wpTextbox1.offsetHeight != wpTextbox0.offsetHeight)
         {
         {
             syntaxHighlighterConfig[parameterName] = hardcodedFallback;
             var height = wpTextbox1.offsetHeight + "px";
            wpTextbox0.style.height = height;
            wpTextbox1.style.marginTop = "-" + height;
         }
         }
     }
     }


     window.syntaxHighlighterSiteConfig = window.syntaxHighlighterSiteConfig || {};
     function setup()
    window.syntaxHighlighterConfig = window.syntaxHighlighterConfig || {};
    {
        function configureColor(parameterName, hardcodedFallback, defaultOk)
        {
            if (typeof(syntaxHighlighterConfig[parameterName]) == "undefined")
            {
                syntaxHighlighterConfig[parameterName] = syntaxHighlighterSiteConfig[parameterName];
            }


    //use 3-digit colors instead of 6-digit colors for performance
            if (syntaxHighlighterConfig[parameterName] == "normal")
    configureColor("backgroundColor",    "#FFF",  false); //white
            {
    configureColor("foregroundColor",    "#000",  false); //black
                syntaxHighlighterConfig[parameterName] = hardcodedFallback;
    configureColor("boldOrItalicColor",  "#EEE",  true); //gray
            }
    configureColor("commentColor",      "#EFE",  true);  //green
            else if (typeof(syntaxHighlighterConfig[parameterName]) != "undefined")
    configureColor("entityColor",        "#DFD",  true);  //green
            {
    configureColor("externalLinkColor",  "#EFF",  true);  //cyan
                return;
    configureColor("headingColor",      "#EEE",  true);  //gray
            }
    configureColor("hrColor",            "#EEE",  true);  //gray
            else if (typeof(syntaxHighlighterConfig.defaultColor) != "undefined" && defaultOk)
    configureColor("listOrIndentColor",  "#EFE",  true); //green
            {
    configureColor("parameterColor",    "#FC6",  true);  //orange
                syntaxHighlighterConfig[parameterName] = syntaxHighlighterConfig.defaultColor;
    configureColor("signatureColor",    "#FC6",  true);  //orange
            }
    configureColor("tagColor",          "#FEF",  true);  //pink
            else
    configureColor("tableColor",        "#FFC",  true); //yellow
            {
    configureColor("templateColor",      "#FFC",  true);  //yellow
                syntaxHighlighterConfig[parameterName] = hardcodedFallback;
    configureColor("wikilinkColor",      "#EEF",  true);  //blue
            }
        }


    //tag lists are ordered from most common to least common
        window.syntaxHighlighterSiteConfig = window.syntaxHighlighterSiteConfig || {};
    syntaxHighlighterConfig.nowikiTags = syntaxHighlighterConfig.nowikiTags || syntaxHighlighterSiteConfig.nowikiTags || ["nowiki", "pre"];
        window.syntaxHighlighterConfig = window.syntaxHighlighterConfig || {};
    syntaxHighlighterConfig.sourceTags = syntaxHighlighterConfig.sourceTags || syntaxHighlighterSiteConfig.sourceTags || ["math", "syntaxhighlight", "source", "timeline", "hiero"];
    syntaxHighlighterConfig.timeout = syntaxHighlighterConfig.timeout || syntaxHighlighterSiteConfig.timeout || 50;


     syntaxHighlighterConfig.nowikiTags.forEach(function(tagName) {
        //use 3-digit colors instead of 6-digit colors for performance
         nowikiTagBreakerRegexCache[tagName] = nowikiTagBreakerRegex(tagName);
        configureColor("backgroundColor",    "#FFF",  false); //white
    });
        configureColor("foregroundColor",    "#000",  false); //black
        configureColor("boldOrItalicColor",  "#EEE",  true);  //gray
        configureColor("commentColor",      "#EFE",  true);  //green
        configureColor("entityColor",        "#DFD",  true);  //green
        configureColor("externalLinkColor",  "#EFF",  true);  //cyan
        configureColor("headingColor",      "#EEE",  true);  //gray
        configureColor("hrColor",            "#EEE",  true);  //gray
        configureColor("listOrIndentColor",  "#EFE",  true);  //green
        configureColor("parameterColor",    "#FC6",  true);  //orange
        configureColor("signatureColor",     "#FC6",  true);  //orange
        configureColor("tagColor",          "#FEF",  true);  //pink
        configureColor("tableColor",        "#FFC",  true);  //yellow
         configureColor("templateColor",      "#FFC",  true); //yellow
        configureColor("wikilinkColor",      "#EEF",  true); //blue


    wpTextbox0 = document.createElement("div");
        //tag lists are ordered from most common to least common
    wpTextbox1 = document.getElementById("wpTextbox1");
        syntaxHighlighterConfig.nowikiTags = syntaxHighlighterConfig.nowikiTags || syntaxHighlighterSiteConfig.nowikiTags || ["nowiki", "pre"];
        syntaxHighlighterConfig.sourceTags = syntaxHighlighterConfig.sourceTags || syntaxHighlighterSiteConfig.sourceTags || ["math", "syntaxhighlight", "source", "timeline", "hiero"];
        syntaxHighlighterConfig.timeout = syntaxHighlighterConfig.timeout || syntaxHighlighterSiteConfig.timeout || 50;


    var syntaxStyleElement = document.createElement("style");
        syntaxHighlighterConfig.nowikiTags.forEach(function(tagName) {
    syntaxStyleTextNode = syntaxStyleElement.appendChild(document.createTextNode(""));
            nowikiTagBreakerRegexCache[tagName] = nowikiTagBreakerRegex(tagName);
        });


    //the styling of the textbox and the background div must be kept very similar
        wpTextbox0 = document.createElement("div");
    var wpTextbox1Style = window.getComputedStyle(wpTextbox1);
        wpTextbox1 = document.getElementById("wpTextbox1");


    //horizontal resize would look horribly choppy, better to make the user resize the browser window instead
        var syntaxStyleElement = document.createElement("style");
    var resize = (wpTextbox1Style.resize == "vertical" || wpTextbox1Style.resize == "both" ? "vertical" : "none");
        syntaxStyleTextNode = syntaxStyleElement.appendChild(document.createTextNode(""));


    wpTextbox0.dir                  = wpTextbox1.dir;
        //the styling of the textbox and the background div must be kept very similar
    wpTextbox0.id                    = "wpTextbox0";
         var wpTextbox1Style = window.getComputedStyle(wpTextbox1);
    wpTextbox0.lang                  = wpTextbox1.lang; //lang determines which font "monospace" is
    wpTextbox0.style.backgroundColor = syntaxHighlighterConfig.backgroundColor;
    wpTextbox0.style.border          = "1px solid transparent";
    wpTextbox0.style.boxSizing      = "border-box";
    wpTextbox0.style.clear          = wpTextbox1Style.clear;
    wpTextbox0.style.color          = "transparent"; //makes it look just a little bit smoother
    wpTextbox0.style.fontFamily      = wpTextbox1Style.fontFamily;
    wpTextbox0.style.fontSize        = wpTextbox1Style.fontSize;
    wpTextbox0.style.lineHeight      = "1.3em";
    wpTextbox0.style.marginBottom    = "0";
    wpTextbox0.style.marginLeft      = "0";
    wpTextbox0.style.marginRight    = "0";
    wpTextbox0.style.marginTop      = "0";
    wpTextbox0.style.overflowX      = "auto";
    wpTextbox0.style.overflowY      = "scroll";
    wpTextbox0.style.resize          = resize;
    wpTextbox0.style.tabSize         = wpTextbox1Style.tabSize;
    wpTextbox0.style.whiteSpace      = "pre-wrap";
    wpTextbox0.style.width          = "100%";
    wpTextbox0.style.wordWrap        = "normal"; //see below


    wpTextbox1.style.backgroundColor = "transparent";
        //horizontal resize would look horribly choppy, better to make the user resize the browser window instead
    wpTextbox1.style.border          = "1px inset gray";
        var resize = (wpTextbox1Style.resize == "vertical" || wpTextbox1Style.resize == "both" ? "vertical" : "none");
    wpTextbox1.style.boxSizing      = "border-box";
    wpTextbox1.style.color          = syntaxHighlighterConfig.foregroundColor;
    wpTextbox1.style.fontSize        = wpTextbox1Style.fontSize; //resolves alignment problems on mobile chrome
    wpTextbox1.style.lineHeight      = "1.3em";
    wpTextbox1.style.marginBottom    = wpTextbox1Style.marginBottom; //lock to pixel value because the top margin was also locked to a pixel value when it was moved to wpTextbox0
    wpTextbox1.style.marginLeft      = "0";
    wpTextbox1.style.marginRight    = "0";
    wpTextbox1.style.overflowX      = "auto";
    wpTextbox1.style.overflowY      = "scroll";
    wpTextbox1.style.padding        = "0";
    wpTextbox1.style.resize          = resize;
    wpTextbox1.style.width          = "100%";
    wpTextbox1.style.wordWrap        = "normal"; //overall more visually appealing


    //lock both heights to pixel values so that the browser zoom feature works better
        wpTextbox0.dir                  = wpTextbox1.dir;
     wpTextbox1.style.height = wpTextbox0.style.height = wpTextbox1.offsetHeight + "px";
        wpTextbox0.id                    = "wpTextbox0";
        wpTextbox0.lang                  = wpTextbox1.lang; //lang determines which font "monospace" is
        wpTextbox0.style.backgroundColor = syntaxHighlighterConfig.backgroundColor;
        wpTextbox0.style.border          = "1px solid transparent";
        wpTextbox0.style.boxSizing      = "border-box";
        wpTextbox0.style.clear          = wpTextbox1Style.clear;
        wpTextbox0.style.color          = "transparent"; //makes it look just a little bit smoother
        wpTextbox0.style.fontFamily      = wpTextbox1Style.fontFamily;
        wpTextbox0.style.fontSize        = wpTextbox1Style.fontSize;
        wpTextbox0.style.lineHeight      = "1.3em";
        wpTextbox0.style.marginBottom    = "0";
        wpTextbox0.style.marginLeft      = "0";
        wpTextbox0.style.marginRight     = "0";
        wpTextbox0.style.marginTop      = "0";
        wpTextbox0.style.overflowX      = "auto";
        wpTextbox0.style.overflowY      = "scroll";
        wpTextbox0.style.resize          = resize;
        wpTextbox0.style.tabSize        = wpTextbox1Style.tabSize;
        wpTextbox0.style.whiteSpace      = "pre-wrap";
        wpTextbox0.style.width          = "100%";
        wpTextbox0.style.wordWrap        = "normal"; //see below


    //insert wpTextbox0 underneath wpTextbox1
        wpTextbox1.style.backgroundColor = "transparent";
     wpTextbox1.style.marginTop       = -wpTextbox1.offsetHeight + "px";
        wpTextbox1.style.border          = "1px inset gray";
    wpTextbox1.parentNode.insertBefore(wpTextbox0, wpTextbox1);
        wpTextbox1.style.boxSizing      = "border-box";
        wpTextbox1.style.color          = syntaxHighlighterConfig.foregroundColor;
        wpTextbox1.style.fontSize        = wpTextbox1Style.fontSize; //resolves alignment problems on mobile chrome
        wpTextbox1.style.lineHeight      = "1.3em";
        wpTextbox1.style.marginBottom    = wpTextbox1Style.marginBottom; //lock to pixel value because the top margin was also locked to a pixel value when it was moved to wpTextbox0
        wpTextbox1.style.marginLeft      = "0";
        wpTextbox1.style.marginRight     = "0";
        wpTextbox1.style.overflowX       = "auto";
        wpTextbox1.style.overflowY      = "scroll";
        wpTextbox1.style.padding        = "0";
        wpTextbox1.style.resize          = resize;
        wpTextbox1.style.width          = "100%";
        wpTextbox1.style.wordWrap        = "normal"; //overall more visually appealing


    document.head.appendChild(syntaxStyleElement);
        //lock both heights to pixel values so that the browser zoom feature works better
        wpTextbox1.style.height = wpTextbox0.style.height = wpTextbox1.offsetHeight + "px";


    wpTextbox1.addEventListener("input", highlightSyntax);
        //insert wpTextbox0 underneath wpTextbox1
    wpTextbox1.addEventListener("scroll", syncScrollX);
        wpTextbox1.style.marginTop      = -wpTextbox1.offsetHeight + "px";
    wpTextbox1.addEventListener("scroll", syncScrollY);
        wpTextbox1.parentNode.insertBefore(wpTextbox0, wpTextbox1);
    attributeObserver = new MutationObserver(syncTextDirection);
    attributeObserver.observe(wpTextbox1, {attributes: true});
    parentObserver = new MutationObserver(syncParent);
    parentObserver.observe(wpTextbox1.parentNode, {childList: true});
    highlightSyntaxIfNeededIntervalID = setInterval(highlightSyntaxIfNeeded, 500);
    highlightSyntax();
}


        document.head.appendChild(syntaxStyleElement);


//enable the highlighter only when editing wikitext pages
        wpTextbox1.addEventListener("input", highlightSyntax);
//in the future a separate parser could be added for CSS and JS pages
        wpTextbox1.addEventListener("scroll", syncScrollX);
//blacklist Internet Explorer and Edge, they're just too broken
        wpTextbox1.addEventListener("scroll", syncScrollY);
var wgAction = mw.config.get("wgAction");
        attributeObserver = new MutationObserver(syncTextDirection);
var layoutEngine = $.client.profile().layout;
        attributeObserver.observe(wpTextbox1, {attributes: true});
if ((wgAction == "edit" || wgAction == "submit") && mw.config.get("wgPageContentModel") == "wikitext" && layoutEngine != "trident" && layoutEngine != "edge")
        parentObserver = new MutationObserver(syncParent);
{
        parentObserver.observe(wpTextbox1.parentNode, {childList: true});
    //give other scripts an opportunity to set syntaxHighlighterConfig
        highlightSyntaxIfNeededIntervalID = setInterval(highlightSyntaxIfNeeded, 500);
    if (document.readyState == "complete")
         highlightSyntax();
    {
         setup();
     }
     }
     else
 
 
     //enable the highlighter only when editing wikitext pages
    //in the future a separate parser could be added for CSS and JS pages
    //blacklist Internet Explorer and Edge, they're just too broken
    var wgAction = mw.config.get("wgAction");
    var layoutEngine = $.client.profile().layout;
    if ((wgAction == "edit" || wgAction == "submit") && mw.config.get("wgPageContentModel") == "wikitext" && layoutEngine != "trident" && layoutEngine != "edge")
     {
     {
         window.addEventListener("load", setup);
         //give other scripts an opportunity to set syntaxHighlighterConfig
        if (document.readyState == "complete")
        {
            setup();
        }
        else
        {
            window.addEventListener("load", setup);
        }
     }
     }
}
});

Revision as of 14:30, 1 May 2019

$(function() {
    //variables that are preserved between function calls
    var wpTextbox0;
    var wpTextbox1;
    var syntaxStyleTextNode;
    var lastText;
    var maxSpanNumber = -1; //the number of the last span available, used to tell if creating additional spans is necessary
    var highlightSyntaxIfNeededIntervalID;
    var attributeObserver;
    var parentObserver;

    /* Define context-specific regexes, one for every common token that ends the
       current context.

       An attempt has been made to search for the most common syntaxes first,
       thus maximizing performance. Syntaxes that begin with the same character
       are searched for at the same time.

       Supported wiki syntaxes from most common to least common:
           [[internal link]] [http:// named external link]
           {{template}} {{{template parameter}}} {| table |}
           <tag> <!-- comment -->
           http:// bare external link
           =Heading= * unordered list # ordered list : indent ; small heading ---- horizontal line
           ''italic'' '''bold'''
           three tildes username four tildes signature five tildes timestamp
           &entity;

       The tag-matching regex follows the XML standard closely so that users
       won't feel like they have to escape sequences that MediaWiki will never
       consider to be tags.

       Only entities for characters which need to be escaped or cannot be
       unambiguously represented in a monospace font are highlighted, such as
       Greek letters that strongly resemble Latin letters. Use of other entities
       is discouraged as a matter of style. For the same reasons, numeric
       entities should be in hexadecimal (giving character codes in decimal only
       adds confusion).

       Newlines are sucked up into ending tokens (including comments, bare
       external links, lists, horizontal lines, signatures, entities, etc.) to
       avoid creating spans with nothing but newlines in them.

       Flags: g for global search, m for make ^ match the beginning of each line
       and $ the end of each line
    */
    var wgUrlProtocols = mw.config.get("wgUrlProtocols");
    var entityRegexBase = "&(?:(?:n(?:bsp|dash)|m(?:dash|inus)|lt|e[mn]sp|thinsp|amp|quot|gt|shy|zwn?j|lrm|rlm|Alpha|Beta|Epsilon|Zeta|Eta|Iota|Kappa|[Mm]u|micro|Nu|[Oo]micron|[Rr]ho|Tau|Upsilon|Chi)|#x[0-9a-fA-F]+);\n*";
    var breakerRegexBase = "\\[(?:\\[|(?:" + wgUrlProtocols + "))|\\{(?:\\{\\{?|\\|)|<(?:[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:\\w\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD-\\.\u00B7\u0300-\u036F\u203F-\u203F-\u2040]*(?=/?>| |\n)|!--[^]*?-->\n*)|(?:" + wgUrlProtocols.replace("|\\/\\/", "") + ")[^\\s\"<>[\\]{-}]*[^\\s\",\\.:;<>[\\]{-}]\n*|^(?:=|[*#:;]+\n*|-{4,}\n*)|\\\\'\\\\'(?:\\\\')?|~{3,5}\n*|" + entityRegexBase;
    function breakerRegexWithPrefix(prefix)
    {
        //the stop token has to be at the beginning of the regex so that it takes precedence over substrings of itself.
        return new RegExp("(" + prefix + ")\n*|" + breakerRegexBase, "gm");
    }
    function nowikiTagBreakerRegex(tagName)
    {
        return new RegExp("(</" + tagName + ">)\n*|" + entityRegexBase, "gm");
    }
    var defaultBreakerRegex           = new RegExp(breakerRegexBase, "gm");
    var wikilinkBreakerRegex          = breakerRegexWithPrefix("]][a-zA-Z]*");
    var namedExternalLinkBreakerRegex = breakerRegexWithPrefix("]");
    var parameterBreakerRegex         = breakerRegexWithPrefix("}}}");
    var templateBreakerRegex          = breakerRegexWithPrefix("}}");
    var tableBreakerRegex             = breakerRegexWithPrefix("\\|}");
    var headingBreakerRegex           = breakerRegexWithPrefix("\n");
    var tagBreakerRegexCache          = {};
    var nowikiTagBreakerRegexCache    = {};

    function highlightSyntax()
    {
        lastText = wpTextbox1.value;
        /* Backslashes and apostrophes are CSS-escaped at the beginning and all
           parsing regexes and functions are designed to match. On the other hand,
           newlines are not escaped until written so that in the regexes ^ and $
           work for both newlines and the beginning or end of the string. */
        var text = lastText.replace(/['\\]/g, "\\$&") + "\n"; //add a newline to fix scrolling and parsing issues
        var i = 0; //the location of the parser as it goes through var text

        var css = "";
        var spanNumber = 0;
        var lastColor;
        var before = true;

        //writes text into to-be-created span elements of wpTextbox0 using :before and :after pseudo-elements
        //both :before and :after are used because using two pseudo-elements per span is significantly faster than doubling the number of spans required
        function writeText(text, color)
        {
            //no need to use another span if using the same color
            if (color != lastColor)
            {
                //whitespace is omitted in the hope of increasing performance
                css += "'}#s" + spanNumber; //spans will be created with IDs s0 through sN
                if (before)
                {
                    css += ":before{";
                    before = false;
                }
                else
                {
                    css += ":after{";
                    before = true;
                    ++spanNumber;
                }
                if (color)
                {
                    //"background-color" is 6 characters longer than "background" but the browser processes it faster
                    css += "background-color:" + color + ";";
                }
                css += "content:'";
                lastColor = color;
            }
            css += text;
        }

        /* About assumedBold and assumedItalic:

           Highlighting bold or italic markup presents a special challenge
           because the actual MediaWiki parser uses multiple passes to determine
           which ticks represent start tags and which represent end tags.
           Because that would be too slow for us here, we instead keep track of
           what kinds of unclosed opening ticks have been encountered and use
           that to make a good guess as to whether the next ticks encountered
           are an opening tag or a closing tag.

           The major downsides to this method are that '''apostrophe italic''
           and ''italic apostrophe''' are not highlighted correctly, and bold
           and italic are both highlighted in the same color.

           To permit ''The best<ref>''Reference Title''</ref> book ever'',
           assumedBold and assumedItalic are saved on the stack and reset to
           undefined (essentially, false) when recursing into a new block. */

        function highlightBlock(color, breakerRegex, assumedBold, assumedItalic)
        {
            var match;

            for (breakerRegex.lastIndex = i; match = breakerRegex.exec(text); breakerRegex.lastIndex = i)
            {
                if (match[1])
                {
                    //end token found
                    writeText(text.substring(i, breakerRegex.lastIndex), color);
                    i = breakerRegex.lastIndex;
                    return;
                }

                var endIndexOfLastColor = breakerRegex.lastIndex - match[0].length;
                if (i < endIndexOfLastColor) //avoid calling writeText with text == "" to improve performance
                {
                    writeText(text.substring(i, endIndexOfLastColor), color);
                }

                i = breakerRegex.lastIndex;

                switch (match[0].charAt(0)) //cases in this switch should be arranged from most common to least common
                {
                    case "[":
                        if (match[0].charAt(1) == "[")
                        {
                            //wikilink
                            writeText("[[", syntaxHighlighterConfig.wikilinkColor || color);
                            highlightBlock(syntaxHighlighterConfig.wikilinkColor || color, wikilinkBreakerRegex);
                        }
                        else
                        {
                            //named external link
                            writeText(match[0], syntaxHighlighterConfig.externalLinkColor || color);
                            highlightBlock(syntaxHighlighterConfig.externalLinkColor || color, namedExternalLinkBreakerRegex);
                        }
                        break;
                    case "{":
                        if (match[0].charAt(1) == "{")
                        {
                            if (match[0].length == 3)
                            {
                                //parameter
                                writeText("{{{", syntaxHighlighterConfig.parameterColor || color);
                                highlightBlock(syntaxHighlighterConfig.parameterColor || color, parameterBreakerRegex);
                            }
                            else
                            {
                                //template
                                writeText("{{", syntaxHighlighterConfig.templateColor || color);
                                highlightBlock(syntaxHighlighterConfig.templateColor || color, templateBreakerRegex);
                            }
                        }
                        else //|
                        {
                            //table
                            writeText("{|", syntaxHighlighterConfig.tableColor || color);
                            highlightBlock(syntaxHighlighterConfig.tableColor || color, tableBreakerRegex);
                        }
                        break;
                    case "<":
                        if (match[0].charAt(1) == "!")
                        {
                            //comment tag
                            writeText(match[0], syntaxHighlighterConfig.commentColor || color);
                            break;
                        }
                        else
                        {
                            //some other kind of tag, search for its end
                            //the search is made easier because XML attributes may not contain the character ">"
                            var tagEnd = text.indexOf(">", i) + 1;
                            if (tagEnd == 0)
                            {
                                //not a tag, just a "<" with some text after it
                                writeText("<", color);
                                i = i - match[0].length + 1;
                                break;
                            }

                            if (text.charAt(tagEnd - 2) == "/")
                            {
                                //empty tag
                                writeText(text.substring(i - match[0].length, tagEnd), syntaxHighlighterConfig.tagColor || color);
                                i = tagEnd;
                            }
                            else
                            {
                                var tagName = match[0].substring(1);

                                if (syntaxHighlighterConfig.sourceTags.indexOf(tagName) != -1)
                                {
                                    //tag that contains text in a different programming language
                                    var stopAfter = "</" + tagName + ">";
                                    var endIndex = text.indexOf(stopAfter, i);
                                    if (endIndex == -1)
                                    {
                                        endIndex = text.length;
                                    }
                                    else
                                    {
                                        endIndex += stopAfter.length;
                                    }
                                    writeText(text.substring(i - match[0].length, endIndex), syntaxHighlighterConfig.tagColor || color);
                                    i = endIndex;
                                }
                                else if (syntaxHighlighterConfig.nowikiTags.indexOf(tagName) != -1)
                                {
                                    //tag that can contain only HTML entities
                                    writeText(text.substring(i - match[0].length, tagEnd), syntaxHighlighterConfig.tagColor || color);
                                    i = tagEnd;
                                    highlightBlock(syntaxHighlighterConfig.tagColor || color, nowikiTagBreakerRegexCache[tagName]);
                                }
                                else
                                {
                                    //ordinary tag
                                    writeText(text.substring(i - match[0].length, tagEnd), syntaxHighlighterConfig.tagColor || color);
                                    i = tagEnd;
                                    if (!tagBreakerRegexCache[tagName])
                                    {
                                        tagBreakerRegexCache[tagName] = breakerRegexWithPrefix("</" + tagName + ">");
                                    }
                                    highlightBlock(syntaxHighlighterConfig.tagColor || color, tagBreakerRegexCache[tagName]);
                                }
                            }
                        }
                        break;
                    case "=":
                        if (/[^=]=+$/.test(text.substring(i, text.indexOf("\n", i)))) //the line begins and ends with an equals sign and has something else in the middle
                        {
                            //heading
                            writeText("=", syntaxHighlighterConfig.headingColor || color);
                            highlightBlock(syntaxHighlighterConfig.headingColor || color, headingBreakerRegex);
                        }
                        else
                        {
                            writeText("=", color); //move on, process this line as regular wikitext
                        }
                        break;
                    case "*":
                    case "#":
                    case ":":
                        //unordered list, ordered list, indent, small heading
                        //just highlight the marker
                        writeText(match[0], syntaxHighlighterConfig.listOrIndentColor || color);
                        break;
                    case ";":
                        //small heading
                        writeText(";", syntaxHighlighterConfig.headingColor || color);
                        highlightBlock(syntaxHighlighterConfig.headingColor || color, headingBreakerRegex);
                        break;
                    case "-":
                        //horizontal line
                        writeText(match[0], syntaxHighlighterConfig.hrColor || color);
                        break;
                    case "\\":
                        writeText(match[0], syntaxHighlighterConfig.boldOrItalicColor || color);
                        if (match[0].length == 6)
                        {
                            //bold
                            if (assumedBold)
                            {
                                //end tag
                                if (assumedItalic)
                                {
                                    //end of bold part of bold-italic block
                                    //block is now italic-only
                                    assumedBold = false;
                                }
                                else
                                {
                                    //end of bold block
                                    return;
                                }
                            }
                            else
                            {
                                //start tag
                                if (assumedItalic)
                                {
                                    //start of bold part of previously italic-only block
                                    //block is now bold-italic
                                    assumedBold = true;
                                }
                                else
                                {
                                    //start of bold block
                                    highlightBlock(syntaxHighlighterConfig.boldOrItalicColor || color, defaultBreakerRegex, true, false);
                                }
                            }
                        }
                        else
                        {
                            //italic
                            if (assumedItalic)
                            {
                                //end tag
                                if (assumedBold)
                                {
                                    //end of italic part of bold-italic block
                                    //block is now bold-only
                                    assumedItalic = false;
                                }
                                else
                                {
                                    //end of italic block
                                    return;
                                }
                            }
                            else
                            {
                                //start tag
                                if (assumedBold)
                                {
                                    //start of italic part of previously bold-only block
                                    //block is now bold-italic
                                    assumedItalic = true;
                                }
                                else
                                {
                                    //start of italic block
                                    highlightBlock(syntaxHighlighterConfig.boldOrItalicColor || color, defaultBreakerRegex, false, true);
                                }
                            }
                        }
                        break;
                    case "&":
                        //entity
                        writeText(match[0], syntaxHighlighterConfig.entityColor || color);
                        break;
                    case "~":
                        //username, signature, timestamp
                        writeText(match[0], syntaxHighlighterConfig.signatureColor || color);
                        break;
                    default:
                        //bare external link
                        writeText(match[0], syntaxHighlighterConfig.externalLinkColor || color);
                }
            }
        }


        //start!
        var startTime = Date.now();
        highlightBlock("", defaultBreakerRegex);

        //output the leftovers (if any) to make sure whitespace etc. matches
        if (i < text.length)
        {
            writeText(text.substring(i), "");
        }

        //if highlighting took too long, disable it.
        var endTime = Date.now();
        /*if (typeof(bestTime) == "undefined")
        {
            window.bestTime = endTime - startTime;
            document.title = bestTime;
            highlightSyntaxIfNeededIntervalID = setInterval(highlightSyntax, 250);
        }
        else
        {
            if (endTime - startTime < bestTime)
            {
                bestTime = endTime - startTime;
                document.title = bestTime;
            }
        }//*/
        if (endTime - startTime > syntaxHighlighterConfig.timeout)
        {
            clearInterval(highlightSyntaxIfNeededIntervalID);
            wpTextbox1.removeEventListener("input", highlightSyntax);
            wpTextbox1.removeEventListener("scroll", syncScrollX);
            wpTextbox1.removeEventListener("scroll", syncScrollY);
            attributeObserver.disconnect();
            parentObserver.disconnect();
            syntaxStyleTextNode.nodeValue = "";

            var errorMessage = {
                be: "Падсьветка сынтаксісу на гэтай старонцы была адключаная, бо заняла шмат часу. Максымальна дапушчальны час апэрацыі — $1мс, а на вашым кампутары яна заняла $2мс. Паспрабуйце зачыніць нейкія закладкі і праграмы і націснуць «Праглядзець» або «Паказаць зьмены». Калі гэта не дапаможа, паспрабуйце іншы броўзэр; калі й гэта не дапаможа, выкарыстайце магутнейшы кампутар.",
                ca: "S'ha desactivat el remarcar de sintaxi en aquesta pàgina perquè ha trigat massa temps. El temps màxim permès per a remarcar és $1ms, i el vostre ordinador ha trigat $2ms. Proveu tancar algunes pestanyes i programes i fer clic en \"Mostra la previsualització\" o \"Mostra els canvis\". Si no funciona això, proveu un altre navegador web, i si això no funciona, proveu un ordinador més ràpid.",
                de: "Die Syntaxhervorhebung wurde auf dieser Seite deaktiviert, da diese zu lange gedauert hat. Die maximal erlaubte Zeit zur Hervorhebung beträgt $1ms und dein Computer benötigte $2ms. Versuche einige Tabs und Programme zu schließen und klicke \"Vorschau zeigen\" oder \"Änderungen zeigen\". Wenn das nicht funktioniert, probiere einen anderen Webbrowser und wenn immer noch nicht, probiere einen schnelleren Computer.",
                el: "Η έμφαση σύνταξης έχει απενεργοποιηθεί σε αυτήν τη σελίδα γιατί αργούσε πολύ. Ο μέγιστος επιτρεπτός χρόνος για την έμφαση σύνταξης είναι $1ms και ο υπολογιστής σας έκανε $2ms. Δοκιμάστε να κλείσετε μερικές καρτέλες και προγράμματα και να κάνετε κλικ στην «Εμφάνιση προεπισκόπησης» ή στην «Εμφάνιση αλλαγών». Αν αυτό δεν δουλέψει, δοκιμάστε έναν διαφορετικό περιηγητή και αν ούτε αυτό δουλέψει, δοκιμάστε έναν ταχύτερο υπολογιστή.",
                en: "Syntax highlighting on this page was disabled because it took too long. The maximum allowed highlighting time is $1ms, and your computer took $2ms. Try closing some tabs and programs and clicking \"Show preview\" or \"Show changes\". If that doesn't work, try a different web browser, and if that doesn't work, try a faster computer.",
                es: "Se desactivó el resaltar de sintaxis en esta página porque tardó demasiado. El tiempo máximo permitido para resaltar es $1ms, y tu ordenador tardó $2ms. Prueba cerrar algunas pestañas y programas y hacer clic en \"Mostrar previsualización\" o \"Mostrar cambios\". Si no funciona esto, prueba otro navegador web, y si eso no funciona, prueba un ordenador más rápido.",
                fa: "از آنجایی که زمان زیادی صرف آن می‌شد، برجسته‌سازی نحو در این صفحه غیرفعال شده است. بیشینهٔ زمان برجسته‌سازی برای ابزار $1ms تعریف شده در حالی که رایانهٔ شما $2ms زمان نیاز داشت. می‌توانید بستن برخی سربرگ‌ها و برنامه‌ها و سپس کلیک‌کردن دکمهٔ «پیش‌نمایش» یا «نمایش تغییرات» را بیازمایید. اگر جواب نداد مرورگر دیگری را امتحان کنید؛ و اگر باز هم جواب نداد، رایانهٔ سریع‌تری را بیازمایید.",
                fr: "La coloration syntaxique a été désactivée sur cette page en raison d'un temps de chargement trop important ($2ms). Le temps maximum autorisé est $1ms. Vous pouvez essayer de fermer certains onglets et programmes et cliquez sur \"Prévisualisation\" ou \"Voir mes modifications\". Si cela ne fonctionne pas, essayez un autre navigateur web, et si cela ne fonctionne toujours pas, essayez un ordinateur plus rapide.",
                hy: "Շարադասության ընդգծումը այս էջում անջատվել է, քանի որ այն չափից շատ է տևել։ Ընդգծման թույլատրելի առավելագույն ժամանակը $1 միլիվայրկյան է, բայց այս էջում տևել է $2 միլիվայրկյան։ Փորձեք անջատել որոշ ներդիրներ կամ ծրագրեր և սեղմել «Նախադիտել» կամ «Կատարված փոփոխությունները»։ Կրկին չաշխատելու դեպքում փորձեք այլ վեբ դիտարկիչ, եթե կրկին չաշխատի, փորձեք ավելի արագ համակարգիչ։",
                io: "Sintaxo-hailaitar en ca pagino esis nekapabligata pro ke konsumis tro multa tempo. La maxima permisata hailaitala tempo es $1ms, e tua ordinatro konsumis $2ms. Probez klozar kelka tabi e programi e kliktar \"Previdar\" o \"Montrez chanji\". Se to ne funcionas, probez altra brauzero, e se to ne funcionas, probez plu rapida ordinatro.",
                it: "L'evidenziazione delle sintassi su questa pagina è stata disabilitata perché ha richiesto troppo tempo. Il tempo massimo per l'evidenziazione è di $1ms e al tuo computer sono serviti $2ms. Prova a chiudere alcune schede e programmi e ricarica la pagina cliccando su \"Visualizza anteprima\" o \"Mostra modifiche\". Se non funziona ancora, prova con un web browser differente e, in ultima alternativa, prova ad utilizzare un computer più veloce.",
                ko: "이 문서에서의 문법 강조가 너무 오래 걸러서 해제되었습니다. 최대로 할당된 강조 시간은 $1ms인데, 당신의 컴퓨터는 $2ms이나 걸렸습니다. 탭과 프로그램을 일부 닫으신 후에 \"미리 보기\"나 \"차이 보기\"를 클릭하시기 바랍니다. 만약 작동하지 않으면 다른 웹 브라우저로 시도해보시고, 그래도 안되면 더 빠른 컴퓨터를 이용하십시오",
                pt: "O marcador de sintaxe foi desativado nesta página porque demorou demais. O tempo máximo permitido para marcar é de $1ms, e seu computador demorou $2ms. Tente fechar algumas abas e programas e clique em \"Mostrar previsão\" ou \"Mostrar alterações\". Se isso não funcionar, tente usar um outro navegador web, e se ainda não funcionar, tente em um computador mais rápido.",
                ru: "Подсветка синтаксиса на странице была отключена, так как заняла слишком долго. Максимальное допустимое время операции - $1мс, сейчас на вашем компьютере она заняла $2мс. Попробуйте закрыть несколько вкладок и программ, затем нажать «Предварительный просмотр» или «Внесённые изменения». Если это не поможет, попробуйте другой браузер; если и это не поможет, используйте более быстрый компьютер.",
                sr: "Истицање синтаксе на овој страници је онемогућено јер се одвија предуго. Максимално дозвољено време истицања је $1ms, а на Вашем рачунару траје $2ms. Покушајте затворити неке картице и програме или кликните на „Прикажи претпреглед” или „Прикажи измене”. Ако то не ради, покушајте са другим веб-прегледачем, а ако и тада не ради, покушајте са бржим рачунаром.",
            };
            var wgUserLanguage = mw.config.get("wgUserLanguage");

            errorMessage = errorMessage[wgUserLanguage] || errorMessage[wgUserLanguage.substring(0, wgUserLanguage.indexOf("-"))] || errorMessage.en;

            wpTextbox1.style.backgroundColor = "";
            wpTextbox1.style.marginTop = "0";
            wpTextbox0.removeAttribute("dir");
            wpTextbox0.removeAttribute("lang");
            wpTextbox0.setAttribute("style", "color:red; font-size:small");

            wpTextbox0.textContent = errorMessage.replace("$1", syntaxHighlighterConfig.timeout).replace("$2", endTime - startTime);
            return;
        }

        //do we have enough span elements to match the generated CSS?
        //this step isn't included in the above benchmark because it takes a highly variable amount of time
        if (maxSpanNumber < spanNumber)
        {
            var wpTextbox1Style = window.getComputedStyle(wpTextbox1);
            var fragment = document.createDocumentFragment();
            do
            {
                var tag = document.createElement("span");
                tag.id = "s" + ++maxSpanNumber;
                tag.style.fontFamily = wpTextbox1Style.fontFamily;
                fragment.appendChild(tag);
            }
            while (maxSpanNumber < spanNumber);
            wpTextbox0.appendChild(fragment);
        }

        /* finish CSS: move the extra '} from the beginning to the end and CSS-
           escape newlines. CSS ignores the space after the hex code of the
           escaped character */
        syntaxStyleTextNode.nodeValue = css.substring(2).replace(/\n/g, "\\A ") + "'}";
    }

    function syncScrollX()
    {
        wpTextbox0.scrollLeft = wpTextbox1.scrollLeft;
    }

    function syncScrollY()
    {
        wpTextbox0.scrollTop = wpTextbox1.scrollTop;
    }

    function syncTextDirection()
    {
        wpTextbox0.dir = wpTextbox1.dir;
    }

    function syncParent()
    {
        if (wpTextbox1.previousSibling != wpTextbox0)
        {
            wpTextbox1.parentNode.insertBefore(wpTextbox0, wpTextbox1);
            parentObserver.disconnect();
            parentObserver.observe(wpTextbox1.parentNode, {childList: true});
        }
    }

    //this function runs once every 500ms to detect changes to wpTextbox1's text that the input event does not catch
    //this happens when another script changes the text without knowing that the syntax highlighter needs to be informed
    function highlightSyntaxIfNeeded()
    {
        if (wpTextbox1.value != lastText)
        {
            highlightSyntax();
        }
        if (wpTextbox1.scrollLeft != wpTextbox0.scrollLeft)
        {
            syncScrollX();
        }
        if (wpTextbox1.scrollTop != wpTextbox0.scrollTop)
        {
            syncScrollY();
        }
        if (wpTextbox1.offsetHeight != wpTextbox0.offsetHeight)
        {
            var height = wpTextbox1.offsetHeight + "px";
            wpTextbox0.style.height = height;
            wpTextbox1.style.marginTop = "-" + height;
        }
    }

    function setup()
    {
        function configureColor(parameterName, hardcodedFallback, defaultOk)
        {
            if (typeof(syntaxHighlighterConfig[parameterName]) == "undefined")
            {
                syntaxHighlighterConfig[parameterName] = syntaxHighlighterSiteConfig[parameterName];
            }

            if (syntaxHighlighterConfig[parameterName] == "normal")
            {
                syntaxHighlighterConfig[parameterName] = hardcodedFallback;
            }
            else if (typeof(syntaxHighlighterConfig[parameterName]) != "undefined")
            {
                return;
            }
            else if (typeof(syntaxHighlighterConfig.defaultColor) != "undefined" && defaultOk)
            {
                syntaxHighlighterConfig[parameterName] = syntaxHighlighterConfig.defaultColor;
            }
            else
            {
                syntaxHighlighterConfig[parameterName] = hardcodedFallback;
            }
        }

        window.syntaxHighlighterSiteConfig = window.syntaxHighlighterSiteConfig || {};
        window.syntaxHighlighterConfig = window.syntaxHighlighterConfig || {};

        //use 3-digit colors instead of 6-digit colors for performance
        configureColor("backgroundColor",    "#FFF",  false); //white
        configureColor("foregroundColor",    "#000",  false); //black
        configureColor("boldOrItalicColor",  "#EEE",  true);  //gray
        configureColor("commentColor",       "#EFE",  true);  //green
        configureColor("entityColor",        "#DFD",  true);  //green
        configureColor("externalLinkColor",  "#EFF",  true);  //cyan
        configureColor("headingColor",       "#EEE",  true);  //gray
        configureColor("hrColor",            "#EEE",  true);  //gray
        configureColor("listOrIndentColor",  "#EFE",  true);  //green
        configureColor("parameterColor",     "#FC6",  true);  //orange
        configureColor("signatureColor",     "#FC6",  true);  //orange
        configureColor("tagColor",           "#FEF",  true);  //pink
        configureColor("tableColor",         "#FFC",  true);  //yellow
        configureColor("templateColor",      "#FFC",  true);  //yellow
        configureColor("wikilinkColor",      "#EEF",  true);  //blue

        //tag lists are ordered from most common to least common
        syntaxHighlighterConfig.nowikiTags = syntaxHighlighterConfig.nowikiTags || syntaxHighlighterSiteConfig.nowikiTags || ["nowiki", "pre"];
        syntaxHighlighterConfig.sourceTags = syntaxHighlighterConfig.sourceTags || syntaxHighlighterSiteConfig.sourceTags || ["math", "syntaxhighlight", "source", "timeline", "hiero"];
        syntaxHighlighterConfig.timeout = syntaxHighlighterConfig.timeout || syntaxHighlighterSiteConfig.timeout || 50;

        syntaxHighlighterConfig.nowikiTags.forEach(function(tagName) {
            nowikiTagBreakerRegexCache[tagName] = nowikiTagBreakerRegex(tagName);
        });

        wpTextbox0 = document.createElement("div");
        wpTextbox1 = document.getElementById("wpTextbox1");

        var syntaxStyleElement = document.createElement("style");
        syntaxStyleTextNode = syntaxStyleElement.appendChild(document.createTextNode(""));

        //the styling of the textbox and the background div must be kept very similar
        var wpTextbox1Style = window.getComputedStyle(wpTextbox1);

        //horizontal resize would look horribly choppy, better to make the user resize the browser window instead
        var resize = (wpTextbox1Style.resize == "vertical" || wpTextbox1Style.resize == "both" ? "vertical" : "none");

        wpTextbox0.dir                   = wpTextbox1.dir;
        wpTextbox0.id                    = "wpTextbox0";
        wpTextbox0.lang                  = wpTextbox1.lang; //lang determines which font "monospace" is
        wpTextbox0.style.backgroundColor = syntaxHighlighterConfig.backgroundColor;
        wpTextbox0.style.border          = "1px solid transparent";
        wpTextbox0.style.boxSizing       = "border-box";
        wpTextbox0.style.clear           = wpTextbox1Style.clear;
        wpTextbox0.style.color           = "transparent"; //makes it look just a little bit smoother
        wpTextbox0.style.fontFamily      = wpTextbox1Style.fontFamily;
        wpTextbox0.style.fontSize        = wpTextbox1Style.fontSize;
        wpTextbox0.style.lineHeight      = "1.3em";
        wpTextbox0.style.marginBottom    = "0";
        wpTextbox0.style.marginLeft      = "0";
        wpTextbox0.style.marginRight     = "0";
        wpTextbox0.style.marginTop       = "0";
        wpTextbox0.style.overflowX       = "auto";
        wpTextbox0.style.overflowY       = "scroll";
        wpTextbox0.style.resize          = resize;
        wpTextbox0.style.tabSize         = wpTextbox1Style.tabSize;
        wpTextbox0.style.whiteSpace      = "pre-wrap";
        wpTextbox0.style.width           = "100%";
        wpTextbox0.style.wordWrap        = "normal"; //see below

        wpTextbox1.style.backgroundColor = "transparent";
        wpTextbox1.style.border          = "1px inset gray";
        wpTextbox1.style.boxSizing       = "border-box";
        wpTextbox1.style.color           = syntaxHighlighterConfig.foregroundColor;
        wpTextbox1.style.fontSize        = wpTextbox1Style.fontSize; //resolves alignment problems on mobile chrome
        wpTextbox1.style.lineHeight      = "1.3em";
        wpTextbox1.style.marginBottom    = wpTextbox1Style.marginBottom; //lock to pixel value because the top margin was also locked to a pixel value when it was moved to wpTextbox0
        wpTextbox1.style.marginLeft      = "0";
        wpTextbox1.style.marginRight     = "0";
        wpTextbox1.style.overflowX       = "auto";
        wpTextbox1.style.overflowY       = "scroll";
        wpTextbox1.style.padding         = "0";
        wpTextbox1.style.resize          = resize;
        wpTextbox1.style.width           = "100%";
        wpTextbox1.style.wordWrap        = "normal"; //overall more visually appealing

        //lock both heights to pixel values so that the browser zoom feature works better
        wpTextbox1.style.height = wpTextbox0.style.height = wpTextbox1.offsetHeight + "px";

        //insert wpTextbox0 underneath wpTextbox1
        wpTextbox1.style.marginTop       = -wpTextbox1.offsetHeight + "px";
        wpTextbox1.parentNode.insertBefore(wpTextbox0, wpTextbox1);

        document.head.appendChild(syntaxStyleElement);

        wpTextbox1.addEventListener("input", highlightSyntax);
        wpTextbox1.addEventListener("scroll", syncScrollX);
        wpTextbox1.addEventListener("scroll", syncScrollY);
        attributeObserver = new MutationObserver(syncTextDirection);
        attributeObserver.observe(wpTextbox1, {attributes: true});
        parentObserver = new MutationObserver(syncParent);
        parentObserver.observe(wpTextbox1.parentNode, {childList: true});
        highlightSyntaxIfNeededIntervalID = setInterval(highlightSyntaxIfNeeded, 500);
        highlightSyntax();
    }


    //enable the highlighter only when editing wikitext pages
    //in the future a separate parser could be added for CSS and JS pages
    //blacklist Internet Explorer and Edge, they're just too broken
    var wgAction = mw.config.get("wgAction");
    var layoutEngine = $.client.profile().layout;
    if ((wgAction == "edit" || wgAction == "submit") && mw.config.get("wgPageContentModel") == "wikitext" && layoutEngine != "trident" && layoutEngine != "edge")
    {
        //give other scripts an opportunity to set syntaxHighlighterConfig
        if (document.readyState == "complete")
        {
            setup();
        }
        else
        {
            window.addEventListener("load", setup);
        }
    }
});