MediaWiki:Gadget-code-prettify.js:修订间差异

H萌娘,万物皆可H的百科全书!
跳到导航 跳到搜索
imported>=海豚=
无编辑摘要
imported>=海豚=
(以“//<nowiki> // 由ResourceLoader直接调用,不可使用ES6语法 /** * @Function: 高亮JavaScript、CSS、HTML和Lua,按行号跳转,并添加行号和指示色块…”替换内容)
第1行: 第1行:
//<nowiki>
// 由ResourceLoader直接调用,不可使用ES6语法
/**
/**
  * @license
  * @Function: 高亮JavaScript、CSS、HTML和Lua,按行号跳转,并添加行号和指示色块
  * Copyright (C) 2006 Google Inc.
  * @Dependencies: ext.gadget.site-lib
  *
  * @Source: [[moegirl:mediawiki:gadget-code-prettify.js]]和[[moegirl:user:机智的小鱼君/gadget/Highlight.js]]
* Licensed under the Apache License, Version 2.0 (the "License");
  * @EditedBy: [[User:Bhsd]]
* you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
  *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
  */
  */
"use strict";
/*global hljs */
const acceptLangs = {js: "javascript", javascript: "javascript", json: "json", css: "css", html: "xml",
   scribunto: "lua", lua: "lua"},
   contentModel = mw.config.get( "wgPageContentModel" ).toLowerCase();


/**
mw.hook( 'wikipage.content' ).add(function($content) {
* @fileoverview
    if (contentModel in acceptLangs) {
* some functions for browser-side pretty printing of code contained in html.
      $content.find( '.mw-code' ).addClass('hljs linenums ' + acceptLangs[contentModel]);
*
* <p>
* For a fairly comprehensive set of languages see the
* <a href="https://github.com/google/code-prettify#for-which-languages-does-it-work">README</a>
* file that came with this source.  At a minimum, the lexer should work on a
* number of languages including C and friends, Java, Python, Bash, SQL, HTML,
* XML, CSS, Javascript, and Makefiles.  It works passably on Ruby, PHP and Awk
* and a subset of Perl, but, because of commenting conventions, doesn't work on
* Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class.
* <p>
* Usage: <ol>
* <li> include this source file in an html page via
*  {@code <script type="text/javascript" src="/path/to/prettify.js"></script>}
* <li> define style rules.  See the example page for examples.
* <li> mark the {@code <pre>} and {@code <code>} tags in your source with
*   {@code class=prettyprint.}
*   You can also use the (html deprecated) {@code <xmp>} tag, but the pretty
*   printer needs to do more substantial DOM manipulations to support that, so
*   some css styles may not be preserved.
* </ol>
* That's it. I wanted to keep the API as simple as possible, so there's no
* need to specify which language the code is in, but if you wish, you can add
* another class to the {@code <pre>} or {@code <code>} element to specify the
* language, as in {@code <pre class="prettyprint lang-java">}.  Any class that
* starts with "lang-" followed by a file extension, specifies the file type.
* See the "lang-*.js" files in this directory for code that implements
* per-language file handlers.
* <p>
* Change log:<br>
* cbeust, 2006/08/22
* <blockquote>
*  Java annotations (start with "@") are now captured as literals ("lit")
* </blockquote>
* @requires console
*/
 
// JSLint declarations
/*global console, document, navigator, setTimeout, window, define */
 
/**
* @typedef {!Array.<number|string>}
* Alternating indices and the decorations that should be inserted there.
* The indices are monotonically increasing.
*/
var DecorationsT;
 
/**
* @typedef {!{
*  sourceNode: !Element,
*  pre: !(number|boolean),
*  langExtension: ?string,
*  numberLines: ?(number|boolean),
*  sourceCode: ?string,
*  spans: ?(Array.<number|Node>),
*  basePos: ?number,
*  decorations: ?DecorationsT
* }}
* <dl>
*  <dt>sourceNode<dd>the element containing the source
*  <dt>sourceCode<dd>source as plain text
*  <dt>pre<dd>truthy if white-space in text nodes
*   should be considered significant.
*  <dt>spans<dd> alternating span start indices into source
*   and the text node or element (e.g. {@code <BR>}) corresponding to that
*   span.
*  <dt>decorations<dd>an array of style classes preceded
*   by the position at which they start in job.sourceCode in order
*  <dt>basePos<dd>integer position of this.sourceCode in the larger chunk of
*   source.
* </dl>
*/
var JobT;
 
/**
* @typedef {!{
*  sourceCode: string,
*  spans: !(Array.<number|Node>)
* }}
* <dl>
*  <dt>sourceCode<dd>source as plain text
*  <dt>spans<dd> alternating span start indices into source
*   and the text node or element (e.g. {@code <BR>}) corresponding to that
*   span.
* </dl>
*/
var SourceSpansT;
 
/** @define {boolean} */
var IN_GLOBAL_SCOPE = true;
 
 
/**
* {@type !{
*  'createSimpleLexer': function (Array, Array): (function (JobT)),
*  'registerLangHandler': function (function (JobT), Array.<string>),
*  'PR_ATTRIB_NAME': string,
*  'PR_ATTRIB_NAME': string,
*  'PR_ATTRIB_VALUE': string,
*  'PR_COMMENT': string,
*  'PR_DECLARATION': string,
*  'PR_KEYWORD': string,
*  'PR_LITERAL': string,
*  'PR_NOCODE': string,
*  'PR_PLAIN': string,
*  'PR_PUNCTUATION': string,
*  'PR_SOURCE': string,
*  'PR_STRING': string,
*  'PR_TAG': string,
*  'PR_TYPE': string,
*  'prettyPrintOne': function (string, string, number|boolean),
*  'prettyPrint': function (?function, ?(HTMLElement|HTMLDocument))
* }}
* @const
*/
var PR;
 
/**
* Split {@code prettyPrint} into multiple timeouts so as not to interfere with
* UI events.
* If set to {@code false}, {@code prettyPrint()} is synchronous.
*/
var PR_SHOULD_USE_CONTINUATION = true
if (typeof window !== 'undefined') {
  window['PR_SHOULD_USE_CONTINUATION'] = PR_SHOULD_USE_CONTINUATION;
}
 
/**
* Pretty print a chunk of code.
* @param {string} sourceCodeHtml The HTML to pretty print.
* @param {string} opt_langExtension The language name to use.
*   Typically, a filename extension like 'cpp' or 'java'.
* @param {number|boolean} opt_numberLines True to number lines,
*   or the 1-indexed number of the first line in sourceCodeHtml.
* @return {string} code as html, but prettier
*/
var prettyPrintOne;
/**
* Find all the {@code <pre>} and {@code <code>} tags in the DOM with
* {@code class=prettyprint} and prettify them.
*
* @param {Function} opt_whenDone called when prettifying is done.
* @param {HTMLElement|HTMLDocument} opt_root an element or document
*  containing all the elements to pretty print.
*  Defaults to {@code document.body}.
*/
var prettyPrint;
 
 
(function () {
  var win = (typeof window !== 'undefined') ? window : {};
  // Keyword lists for various languages.
  // We use things that coerce to strings to make them compact when minified
  // and to defeat aggressive optimizers that fold large string constants.
  var FLOW_CONTROL_KEYWORDS = ["break,continue,do,else,for,if,return,while"];
  var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,"auto,case,char,const,default," +
    "double,enum,extern,float,goto,inline,int,long,register,restrict,short,signed," +
    "sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];
  var COMMON_KEYWORDS = [C_KEYWORDS,"catch,class,delete,false,import," +
    "new,operator,private,protected,public,this,throw,true,try,typeof"];
  var CPP_KEYWORDS = [COMMON_KEYWORDS,"alignas,alignof,align_union,asm,axiom,bool," +
    "concept,concept_map,const_cast,constexpr,decltype,delegate," +
    "dynamic_cast,explicit,export,friend,generic,late_check," +
    "mutable,namespace,noexcept,noreturn,nullptr,property,reinterpret_cast,static_assert," +
    "static_cast,template,typeid,typename,using,virtual,where"];
  var JAVA_KEYWORDS = [COMMON_KEYWORDS,
    "abstract,assert,boolean,byte,extends,finally,final,implements,import," +
    "instanceof,interface,null,native,package,strictfp,super,synchronized," +
    "throws,transient"];
  var CSHARP_KEYWORDS = [COMMON_KEYWORDS,
    "abstract,add,alias,as,ascending,async,await,base,bool,by,byte,checked,decimal,delegate,descending," +
    "dynamic,event,finally,fixed,foreach,from,get,global,group,implicit,in,interface," +
    "internal,into,is,join,let,lock,null,object,out,override,orderby,params," +
    "partial,readonly,ref,remove,sbyte,sealed,select,set,stackalloc,string,select,uint,ulong," +
    "unchecked,unsafe,ushort,value,var,virtual,where,yield"];
  var COFFEE_KEYWORDS = "all,and,by,catch,class,else,extends,false,finally," +
    "for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then," +
    "throw,true,try,unless,until,when,while,yes";
  var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS,
    "abstract,async,await,constructor,debugger,enum,eval,export,from,function," +
    "get,import,implements,instanceof,interface,let,null,of,set,undefined," +
    "var,with,yield,Infinity,NaN"];
  var PERL_KEYWORDS = "caller,delete,die,do,dump,elsif,eval,exit,foreach,for," +
    "goto,if,import,last,local,my,next,no,our,print,package,redo,require," +
    "sub,undef,unless,until,use,wantarray,while,BEGIN,END";
  var PYTHON_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "and,as,assert,class,def,del," +
    "elif,except,exec,finally,from,global,import,in,is,lambda," +
    "nonlocal,not,or,pass,print,raise,try,with,yield," +
    "False,True,None"];
  var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "alias,and,begin,case,class," +
    "def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo," +
    "rescue,retry,self,super,then,true,undef,unless,until,when,yield," +
    "BEGIN,END"];
  var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "case,done,elif,esac,eval,fi," +
    "function,in,local,set,then,until"];
  var ALL_KEYWORDS = [
    CPP_KEYWORDS, CSHARP_KEYWORDS, JAVA_KEYWORDS, JSCRIPT_KEYWORDS,
    PERL_KEYWORDS, PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS];
  var C_TYPES = /^(DIR|FILE|array|vector|(de|priority_)?queue|(forward_)?list|stack|(const_)?(reverse_)?iterator|(unordered_)?(multi)?(set|map)|bitset|u?(int|float)\d*)\b/;
 
  // token style names.  correspond to css classes
  /**
  * token style for a string literal
  * @const
  */
  var PR_STRING = 'str';
  /**
  * token style for a keyword
  * @const
  */
  var PR_KEYWORD = 'kwd';
  /**
  * token style for a comment
  * @const
  */
  var PR_COMMENT = 'com';
  /**
  * token style for a type
  * @const
  */
  var PR_TYPE = 'typ';
  /**
  * token style for a literal value.  e.g. 1, null, true.
  * @const
  */
  var PR_LITERAL = 'lit';
  /**
  * token style for a punctuation string.
  * @const
  */
  var PR_PUNCTUATION = 'pun';
  /**
  * token style for plain text.
  * @const
  */
  var PR_PLAIN = 'pln';
 
  /**
  * token style for an sgml tag.
  * @const
  */
  var PR_TAG = 'tag';
  /**
  * token style for a markup declaration such as a DOCTYPE.
  * @const
  */
  var PR_DECLARATION = 'dec';
  /**
  * token style for embedded source.
  * @const
  */
  var PR_SOURCE = 'src';
  /**
  * token style for an sgml attribute name.
  * @const
  */
  var PR_ATTRIB_NAME = 'atn';
  /**
  * token style for an sgml attribute value.
  * @const
  */
  var PR_ATTRIB_VALUE = 'atv';
 
  /**
  * A class that indicates a section of markup that is not code, e.g. to allow
  * embedding of line numbers within code listings.
  * @const
  */
  var PR_NOCODE = 'nocode';
 
 
  // Regex pattern below is automatically generated by regexpPrecederPatterns.pl
  // Do not modify, your changes will be erased.
 
  // CAVEAT: this does not properly handle the case where a regular
  // expression immediately follows another since a regular expression may
  // have flags for case-sensitivity and the like.  Having regexp tokens
  // adjacent is not valid in any language I'm aware of, so I'm punting.
  // TODO: maybe style special characters inside a regexp as punctuation.
 
  /**
  * A set of tokens that can precede a regular expression literal in
  * javascript
  * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html
  * has the full list, but I've removed ones that might be problematic when
  * seen in languages that don't support regular expression literals.
  *
  * Specifically, I've removed any keywords that can't precede a regexp
  * literal in a syntactically legal javascript program, and I've removed the
  * "in" keyword since it's not a keyword in many languages, and might be used
  * as a count of inches.
  *
  * The link above does not accurately describe EcmaScript rules since
  * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
  * very well in practice.
  *
  * @private
  * @const
  */
  var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*';
 
 
  /**
  * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
  * matches the union of the sets of strings matched by the input RegExp.
  * Since it matches globally, if the input strings have a start-of-input
  * anchor (/^.../), it is ignored for the purposes of unioning.
  * @param {Array.<RegExp>} regexs non multiline, non-global regexs.
  * @return {RegExp} a global regex.
  */
  function combinePrefixPatterns(regexs) {
   var capturedGroupIndex = 0;
 
   var needToFoldCase = false;
   var ignoreCase = false;
   for (var i = 0, n = regexs.length; i < n; ++i) {
    var regex = regexs[i];
    if (regex.ignoreCase) {
     ignoreCase = true;
    } else if (/[a-z]/i.test(regex.source.replace(
           /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
     needToFoldCase = true;
     ignoreCase = false;
     break;
    }
   }
 
   var escapeCharToCodeUnit = {
    'b': 8,
    't': 9,
    'n': 0xa,
    'v': 0xb,
    'f': 0xc,
    'r': 0xd
   };
 
   function decodeEscape(charsetPart) {
    var cc0 = charsetPart.charCodeAt(0);
    if (cc0 !== 92 /* \\ */) {
     return cc0;
    }
    var c1 = charsetPart.charAt(1);
    cc0 = escapeCharToCodeUnit[c1];
    if (cc0) {
     return cc0;
    } else if ('0' <= c1 && c1 <= '7') {
     return parseInt(charsetPart.substring(1), 8);
    } else if (c1 === 'u' || c1 === 'x') {
     return parseInt(charsetPart.substring(2), 16);
    } else {
     return charsetPart.charCodeAt(1);
    }
   }
 
   function encodeEscape(charCode) {
    if (charCode < 0x20) {
     return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
    }
    var ch = String.fromCharCode(charCode);
    return (ch === '\\' || ch === '-' || ch === ']' || ch === '^')
      ? "\\" + ch : ch;
    }
 
   function caseFoldCharset(charSet) {
    var charsetParts = charSet.substring(1, charSet.length - 1).match(
      new RegExp(
        '\\\\u[0-9A-Fa-f]{4}'
        + '|\\\\x[0-9A-Fa-f]{2}'
        + '|\\\\[0-3][0-7]{0,2}'
        + '|\\\\[0-7]{1,2}'
        + '|\\\\[\\s\\S]'
        + '|-'
        + '|[^-\\\\]',
        'g'));
    var ranges = [];
    var inverse = charsetParts[0] === '^';
 
    var out = ['['];
    if (inverse) { out.push('^'); }
 
    for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
     var p = charsetParts[i];
     if (/\\[bdsw]/i.test(p)) {  // Don't muck with named groups.
      out.push(p);
     } else {
      var start = decodeEscape(p);
      var end;
      if (i + 2 < n && '-' === charsetParts[i + 1]) {
       end = decodeEscape(charsetParts[i + 2]);
       i += 2;
      } else {
       end = start;
      }
      ranges.push([start, end]);
      // If the range might intersect letters, then expand it.
      // This case handling is too simplistic.
      // It does not deal with non-latin case folding.
      // It works for latin source code identifiers though.
      if (!(end < 65 || start > 122)) {
       if (!(end < 65 || start > 90)) {
        ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
       }
       if (!(end < 97 || start > 122)) {
        ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
       }
      }
     }
    }
 
    // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
    // -> [[1, 12], [14, 14], [16, 17]]
    ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });
    var consolidatedRanges = [];
    var lastRange = [];
    for (var i = 0; i < ranges.length; ++i) {
     var range = ranges[i];
     if (range[0] <= lastRange[1] + 1) {
      lastRange[1] = Math.max(lastRange[1], range[1]);
     } else {
      consolidatedRanges.push(lastRange = range);
     }
    }
 
    for (var i = 0; i < consolidatedRanges.length; ++i) {
     var range = consolidatedRanges[i];
     out.push(encodeEscape(range[0]));
     if (range[1] > range[0]) {
      if (range[1] + 1 > range[0]) { out.push('-'); }
      out.push(encodeEscape(range[1]));
     }
    }
    out.push(']');
    return out.join('');
   }
 
   function allowAnywhereFoldCaseAndRenumberGroups(regex) {
    // Split into character sets, escape sequences, punctuation strings
    // like ('(', '(?:', ')', '^'), and runs of characters that do not
    // include any of the above.
    var parts = regex.source.match(
      new RegExp(
        '(?:'
        + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]'  // a character set
        + '|\\\\u[A-Fa-f0-9]{4}'  // a unicode escape
        + '|\\\\x[A-Fa-f0-9]{2}'  // a hex escape
        + '|\\\\[0-9]+'  // a back-reference or octal escape
        + '|\\\\[^ux0-9]'  // other escape sequence
        + '|\\(\\?[:!=]'  // start of a non-capturing group
        + '|[\\(\\)\\^]'  // start/end of a group, or line start
        + '|[^\\x5B\\x5C\\(\\)\\^]+'  // run of other characters
        + ')',
        'g'));
    var n = parts.length;
 
    // Maps captured group numbers to the number they will occupy in
    // the output or to -1 if that has not been determined, or to
    // undefined if they need not be capturing in the output.
    var capturedGroups = [];
 
    // Walk over and identify back references to build the capturedGroups
    // mapping.
    for (var i = 0, groupIndex = 0; i < n; ++i) {
     var p = parts[i];
     if (p === '(') {
      // groups are 1-indexed, so max group index is count of '('
      ++groupIndex;
     } else if ('\\' === p.charAt(0)) {
      var decimalValue = +p.substring(1);
      if (decimalValue) {
       if (decimalValue <= groupIndex) {
        capturedGroups[decimalValue] = -1;
       } else {
        // Replace with an unambiguous escape sequence so that
        // an octal escape sequence does not turn into a backreference
        // to a capturing group from an earlier regex.
        parts[i] = encodeEscape(decimalValue);
       }
      }
     }
    }
 
    // Renumber groups and reduce capturing groups to non-capturing groups
    // where possible.
    for (var i = 1; i < capturedGroups.length; ++i) {
     if (-1 === capturedGroups[i]) {
      capturedGroups[i] = ++capturedGroupIndex;
     }
    }
    for (var i = 0, groupIndex = 0; i < n; ++i) {
     var p = parts[i];
     if (p === '(') {
      ++groupIndex;
      if (!capturedGroups[groupIndex]) {
       parts[i] = '(?:';
      }
     } else if ('\\' === p.charAt(0)) {
      var decimalValue = +p.substring(1);
      if (decimalValue && decimalValue <= groupIndex) {
       parts[i] = '\\' + capturedGroups[decimalValue];
      }
     }
    }
 
    // Remove any prefix anchors so that the output will match anywhere.
    // ^^ really does mean an anchored match though.
    for (var i = 0; i < n; ++i) {
     if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
    }
 
    // Expand letters to groups to handle mixing of case-sensitive and
    // case-insensitive patterns if necessary.
    if (regex.ignoreCase && needToFoldCase) {
     for (var i = 0; i < n; ++i) {
      var p = parts[i];
      var ch0 = p.charAt(0);
      if (p.length >= 2 && ch0 === '[') {
       parts[i] = caseFoldCharset(p);
      } else if (ch0 !== '\\') {
       // TODO: handle letters in numeric escapes.
       parts[i] = p.replace(
         /[a-zA-Z]/g,
         function (ch) {
          var cc = ch.charCodeAt(0);
          return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
         });
      }
     }
    }
 
    return parts.join('');
   }
 
   var rewritten = [];
   for (var i = 0, n = regexs.length; i < n; ++i) {
    var regex = regexs[i];
    if (regex.global || regex.multiline) { throw new Error('' + regex); }
    rewritten.push(
      '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
   }
 
   return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
  }
 
 
  /**
  * Split markup into a string of source code and an array mapping ranges in
  * that string to the text nodes in which they appear.
  *
  * <p>
  * The HTML DOM structure:</p>
  * <pre>
  * (Element  "p"
  *  (Element "b"
  *   (Text  "print "))    ; #1
  *  (Text   "'Hello '")    ; #2
  *  (Element "br")       ; #3
  *  (Text   "  + 'World';")) ; #4
  * </pre>
  * <p>
  * corresponds to the HTML
  * {@code <p><b>print </b>'Hello '<br>  + 'World';</p>}.</p>
  *
  * <p>
  * It will produce the output:</p>
  * <pre>
  * {
  *  sourceCode: "print 'Hello '\n  + 'World';",
  *  //           1      2
  *  //      012345678901234 5678901234567
  *  spans: [0, #1, 6, #2, 14, #3, 15, #4]
  * }
  * </pre>
  * <p>
  * where #1 is a reference to the {@code "print "} text node above, and so
  * on for the other text nodes.
  * </p>
  *
  * <p>
  * The {@code} spans array is an array of pairs.  Even elements are the start
  * indices of substrings, and odd elements are the text nodes (or BR elements)
  * that contain the text for those substrings.
  * Substrings continue until the next index or the end of the source.
  * </p>
  *
  * @param {Node} node an HTML DOM subtree containing source-code.
  * @param {boolean|number} isPreformatted truthy if white-space in
  *   text nodes should be considered significant.
  * @return {SourceSpansT} source code and the nodes in which they occur.
  */
  function extractSourceSpans(node, isPreformatted) {
   var nocode = /(?:^|\s)nocode(?:\s|$)/;
 
   var chunks = [];
   var length = 0;
   var spans = [];
   var k = 0;
 
   function walk(node) {
    var type = node.nodeType;
    if (type == 1) {  // Element
     if (nocode.test(node.className)) { return; }
     for (var child = node.firstChild; child; child = child.nextSibling) {
      walk(child);
     }
     var nodeName = node.nodeName.toLowerCase();
     if ('br' === nodeName || 'li' === nodeName) {
      chunks[k] = '\n';
      spans[k << 1] = length++;
      spans[(k++ << 1) | 1] = node;
     }
    } else if (type == 3 || type == 4) { // Text
     var text = node.nodeValue;
      if (text.length) {
      if (!isPreformatted) {
       text = text.replace(/[ \t\r\n]+/g, ' ');
      } else {
       text = text.replace(/\r\n?/g, '\n');  // Normalize newlines.
      }
      // TODO: handle tabs here?
      chunks[k] = text;
      spans[k << 1] = length;
      length += text.length;
      spans[(k++ << 1) | 1] = node;
     }
    }
   }
 
   walk(node);
 
   return {
    sourceCode: chunks.join('').replace(/\n$/, ''),
    spans: spans
   };
  }
 
 
  /**
  * Apply the given language handler to sourceCode and add the resulting
  * decorations to out.
  * @param {!Element} sourceNode
  * @param {number} basePos the index of sourceCode within the chunk of source
  *   whose decorations are already present on out.
  * @param {string} sourceCode
  * @param {function(JobT)} langHandler
  * @param {DecorationsT} out
  */
  function appendDecorations(
    sourceNode, basePos, sourceCode, langHandler, out) {
   if (!sourceCode) { return; }
   /** @type {JobT} */
   var job = {
    sourceNode: sourceNode,
    pre: 1,
    langExtension: null,
    numberLines: null,
    sourceCode: sourceCode,
    spans: null,
    basePos: basePos,
    decorations: null
   };
   langHandler(job);
   out.push.apply(out, job.decorations);
  }
 
  var notWs = /\S/;
 
  /**
  * Given an element, if it contains only one child element and any text nodes
  * it contains contain only space characters, return the sole child element.
  * Otherwise returns undefined.
  * <p>
  * This is meant to return the CODE element in {@code <pre><code ...>} when
  * there is a single child element that contains all the non-space textual
  * content, but not to return anything where there are multiple child elements
  * as in {@code <pre><code>...</code><code>...</code></pre>} or when there
  * is textual content.
  */
  function childContentWrapper(element) {
   var wrapper = undefined;
   for (var c = element.firstChild; c; c = c.nextSibling) {
    var type = c.nodeType;
    wrapper = (type === 1)  // Element Node
      ? (wrapper ? element : c)
      : (type === 3)  // Text Node
      ? (notWs.test(c.nodeValue) ? element : wrapper)
      : wrapper;
   }
   return wrapper === element ? undefined : wrapper;
  }
 
  /** Given triples of [style, pattern, context] returns a lexing function,
   * The lexing function interprets the patterns to find token boundaries and
   * returns a decoration list of the form
   * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
   * where index_n is an index into the sourceCode, and style_n is a style
   * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
   * all characters in sourceCode[index_n-1:index_n].
   *
   * The stylePatterns is a list whose elements have the form
   * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
   *
   * Style is a style constant like PR_PLAIN, or can be a string of the
   * form 'lang-FOO', where FOO is a language extension describing the
   * language of the portion of the token in $1 after pattern executes.
   * E.g., if style is 'lang-lisp', and group 1 contains the text
   * '(hello (world))', then that portion of the token will be passed to the
   * registered lisp handler for formatting.
   * The text before and after group 1 will be restyled using this decorator
   * so decorators should take care that this doesn't result in infinite
   * recursion.  For example, the HTML lexer rule for SCRIPT elements looks
   * something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
   * '<script>foo()<\/script>', which would cause the current decorator to
   * be called with '<script>' which would not match the same rule since
   * group 1 must not be empty, so it would be instead styled as PR_TAG by
   * the generic tag rule.  The handler registered for the 'js' extension would
   * then be called with 'foo()', and finally, the current decorator would
   * be called with '<\/script>' which would not match the original rule and
   * so the generic tag rule would identify it as a tag.
   *
   * Pattern must only match prefixes, and if it matches a prefix, then that
   * match is considered a token with the same style.
   *
   * Context is applied to the last non-whitespace, non-comment token
   * recognized.
   *
   * Shortcut is an optional string of characters, any of which, if the first
   * character, gurantee that this pattern and only this pattern matches.
   *
   * @param {Array} shortcutStylePatterns patterns that always start with
   *  a known character.  Must have a shortcut string.
   * @param {Array} fallthroughStylePatterns patterns that will be tried in
   *  order if the shortcut ones fail.  May have shortcuts.
   *
   * @return {function (JobT)} a function that takes an undecorated job and
   *  attaches a list of decorations.
   */
  function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
   var shortcuts = {};
   var tokenizer;
   (function () {
    var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
    var allRegexs = [];
    var regexKeys = {};
    for (var i = 0, n = allPatterns.length; i < n; ++i) {
     var patternParts = allPatterns[i];
     var shortcutChars = patternParts[3];
     if (shortcutChars) {
      for (var c = shortcutChars.length; --c >= 0;) {
       shortcuts[shortcutChars.charAt(c)] = patternParts;
      }
     }
     var regex = patternParts[1];
     var k = '' + regex;
     if (!regexKeys.hasOwnProperty(k)) {
      allRegexs.push(regex);
      regexKeys[k] = null;
     }
    }
    allRegexs.push(/[\0-\uffff]/);
    tokenizer = combinePrefixPatterns(allRegexs);
   })();
 
   var nPatterns = fallthroughStylePatterns.length;
 
   /**
   * Lexes job.sourceCode and attaches an output array job.decorations of
   * style classes preceded by the position at which they start in
   * job.sourceCode in order.
   *
   * @type{function (JobT)}
   */
   var decorate = function (job) {
    var sourceCode = job.sourceCode, basePos = job.basePos;
    var sourceNode = job.sourceNode;
    /** Even entries are positions in source in ascending order.  Odd enties
     * are style markers (e.g., PR_COMMENT) that run from that position until
     * the end.
     * @type {DecorationsT}
     */
    var decorations = [basePos, PR_PLAIN];
    var pos = 0;  // index into sourceCode
    var tokens = sourceCode.match(tokenizer) || [];
    var styleCache = {};
 
    for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
     var token = tokens[ti];
     var style = styleCache[token];
     var match = void 0;
 
     var isEmbedded;
     if (typeof style === 'string') {
      isEmbedded = false;
     } else {
      var patternParts = shortcuts[token.charAt(0)];
      if (patternParts) {
       match = token.match(patternParts[1]);
       style = patternParts[0];
      } else {
       for (var i = 0; i < nPatterns; ++i) {
        patternParts = fallthroughStylePatterns[i];
        match = token.match(patternParts[1]);
        if (match) {
         style = patternParts[0];
         break;
        }
       }
 
       if (!match) {  // make sure that we make progress
        style = PR_PLAIN;
       }
      }
 
      isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
      if (isEmbedded && !(match && typeof match[1] === 'string')) {
       isEmbedded = false;
       style = PR_SOURCE;
      }
 
      if (!isEmbedded) { styleCache[token] = style; }
     }
 
     var tokenStart = pos;
     pos += token.length;
 
     if (!isEmbedded) {
      decorations.push(basePos + tokenStart, style);
     } else {  // Treat group 1 as an embedded block of source code.
      var embeddedSource = match[1];
      var embeddedSourceStart = token.indexOf(embeddedSource);
      var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
      if (match[2]) {
       // If embeddedSource can be blank, then it would match at the
       // beginning which would cause us to infinitely recurse on the
       // entire token, so we catch the right context in match[2].
       embeddedSourceEnd = token.length - match[2].length;
       embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
      }
      var lang = style.substring(5);
      // Decorate the left of the embedded source
      appendDecorations(
        sourceNode,
        basePos + tokenStart,
        token.substring(0, embeddedSourceStart),
        decorate, decorations);
      // Decorate the embedded source
      appendDecorations(
        sourceNode,
        basePos + tokenStart + embeddedSourceStart,
        embeddedSource,
        langHandlerForExtension(lang, embeddedSource),
        decorations);
      // Decorate the right of the embedded section
      appendDecorations(
        sourceNode,
        basePos + tokenStart + embeddedSourceEnd,
        token.substring(embeddedSourceEnd),
        decorate, decorations);
     }
    }
    job.decorations = decorations;
   };
   return decorate;
  }
 
  /** returns a function that produces a list of decorations from source text.
   *
   * This code treats ", ', and ` as string delimiters, and \ as a string
   * escape.  It does not recognize perl's qq() style strings.
   * It has no special handling for double delimiter escapes as in basic, or
   * the tripled delimiters used in python, but should work on those regardless
   * although in those cases a single string literal may be broken up into
   * multiple adjacent string literals.
   *
   * It recognizes C, C++, and shell style comments.
   *
   * @param {Object} options a set of optional parameters.
   * @return {function (JobT)} a function that examines the source code
   *   in the input job and builds a decoration list which it attaches to
   *   the job.
   */
  function sourceDecorator(options) {
   var shortcutStylePatterns = [], fallthroughStylePatterns = [];
   if (options['tripleQuotedStrings']) {
    // '''multi-line-string''', 'single-line-string', and double-quoted
    shortcutStylePatterns.push(
      [PR_STRING,  /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
      null, '\'"']);
   } else if (options['multiLineStrings']) {
    // 'multi-line-string', "multi-line-string"
    shortcutStylePatterns.push(
      [PR_STRING,  /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,
      null, '\'"`']);
   } else {
    // 'single-line-string', "single-line-string"
    shortcutStylePatterns.push(
      [PR_STRING,
      /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,
      null, '"\'']);
   }
   if (options['verbatimStrings']) {
    // verbatim-string-literal production from the C# grammar.  See issue 93.
    fallthroughStylePatterns.push(
      [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]);
   }
   var hc = options['hashComments'];
   if (hc) {
    if (options['cStyleComments']) {
     if (hc > 1) {  // multiline hash comments
      shortcutStylePatterns.push(
        [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']);
     } else {
      // Stop C preprocessor declarations at an unclosed open comment
      shortcutStylePatterns.push(
        [PR_COMMENT, /^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\r\n]*)/,
        null, '#']);
     }
     // #include <stdio.h>
     fallthroughStylePatterns.push(
       [PR_STRING,
       /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,
       null]);
    } else {
     shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
    }
   }
   if (options['cStyleComments']) {
    fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
    fallthroughStylePatterns.push(
      [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
   }
   var regexLiterals = options['regexLiterals'];
   if (regexLiterals) {
    /**
    * @const
    */
    var regexExcls = regexLiterals > 1
     ? ''  // Multiline regex literals
     : '\n\r';
    /**
    * @const
    */
    var regexAny = regexExcls ? '.' : '[\\S\\s]';
    /**
    * @const
    */
    var REGEX_LITERAL = (
      // A regular expression literal starts with a slash that is
      // not followed by * or / so that it is not confused with
      // comments.
      '/(?=[^/*' + regexExcls + '])'
      // and then contains any number of raw characters,
      + '(?:[^/\\x5B\\x5C' + regexExcls + ']'
      // escape sequences (\x5C),
      +   '|\\x5C' + regexAny
      // or non-nesting character sets (\x5B\x5D);
      +   '|\\x5B(?:[^\\x5C\\x5D' + regexExcls + ']'
      +       '|\\x5C' + regexAny + ')*(?:\\x5D|$))+'
      // finally closed by a /.
      + '/');
    fallthroughStylePatterns.push(
      ['lang-regex',
      RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
      ]);
   }
 
   var types = options['types'];
   if (types) {
    fallthroughStylePatterns.push([PR_TYPE, types]);
   }
 
   var keywords = ("" + options['keywords']).replace(/^ | $/g, '');
   if (keywords.length) {
    fallthroughStylePatterns.push(
      [PR_KEYWORD,
      new RegExp('^(?:' + keywords.replace(/[\s,]+/g, '|') + ')\\b'),
      null]);
   }
 
   shortcutStylePatterns.push([PR_PLAIN,    /^\s+/, null, ' \r\n\t\xA0']);
 
   var punctuation =
    // The Bash man page says
 
    // A word is a sequence of characters considered as a single
    // unit by GRUB. Words are separated by metacharacters,
    // which are the following plus space, tab, and newline: { }
    // | & $ ; < >
    // ...
 
    // A word beginning with # causes that word and all remaining
    // characters on that line to be ignored.
 
    // which means that only a '#' after /(?:^|[{}|&$;<>\s])/ starts a
    // comment but empirically
    // $ echo {#}
    // {#}
    // $ echo \$#
    // $#
    // $ echo }#
    // }#
 
    // so /(?:^|[|&;<>\s])/ is more appropriate.
 
    // http://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC3
    // suggests that this definition is compatible with a
    // default mode that tries to use a single token definition
    // to recognize both bash/python style comments and C
    // preprocessor directives.
 
    // This definition of punctuation does not include # in the list of
    // follow-on exclusions, so # will not be broken before if preceeded
    // by a punctuation character.  We could try to exclude # after
    // [|&;<>] but that doesn't seem to cause many major problems.
    // If that does turn out to be a problem, we should change the below
    // when hc is truthy to include # in the run of punctuation characters
    // only when not followint [|&;<>].
    '^.[^\\s\\w.$@\'"`/\\\\]*';
   if (options['regexLiterals']) {
    punctuation += '(?!\s*\/)';
    }
    }
 
    $content.find('pre[lang], code[lang]').addClass(function() {
   fallthroughStylePatterns.push(
      const $self = $(this),
     // TODO(mikesamuel): recognize non-latin letters and numerals in idents
        lang = $self.attr( "lang" ).toLowerCase();
     [PR_LITERAL,    /^@[a-z_$][a-z_$@0-9]*/i, null],
      if (lang in acceptLangs) { return "hljs " + acceptLangs[lang] + ($self.is('pre') ? " linenums" : ""); }
     [PR_TYPE,     /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null],
     [PR_PLAIN,    /^[a-z_$][a-z_$@0-9]*/i, null],
     [PR_LITERAL,
     new RegExp(
       '^(?:'
       // A hex number
       + '0x[a-f0-9]+'
       // or an octal or decimal number,
       + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)'
       // possibly in scientific notation
       + '(?:e[+\\-]?\\d+)?'
       + ')'
       // with an optional modifier like UL for unsigned long
       + '[a-z]*', 'i'),
     null, '0123456789'],
     // Don't treat escaped quotes in bash as starting strings.
     // See issue 144.
     [PR_PLAIN,    /^\\[\s\S]?/, null],
     [PR_PUNCTUATION, new RegExp(punctuation), null]);
 
   return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
  }
 
  var decorateSource = sourceDecorator({
     'keywords': ALL_KEYWORDS,
     'hashComments': true,
     'cStyleComments': true,
     'multiLineStrings': true,
     'regexLiterals': true
    });
 
  /**
  * Given a DOM subtree, wraps it in a list, and puts each line into its own
  * list item.
  *
  * @param {Node} node modified in place.  Its content is pulled into an
  *   HTMLOListElement, and each line is moved into a separate list item.
  *   This requires cloning elements, so the input might not have unique
  *   IDs after numbering.
  * @param {number|null|boolean} startLineNum
  *   If truthy, coerced to an integer which is the 1-indexed line number
  *   of the first line of code.  The number of the first line will be
  *   attached to the list.
  * @param {boolean} isPreformatted true iff white-space in text nodes should
  *   be treated as significant.
  */
  function numberLines(node, startLineNum, isPreformatted) {
   var nocode = /(?:^|\s)nocode(?:\s|$)/;
   var lineBreak = /\r\n?|\n/;
 
   var document = node.ownerDocument;
 
   var li = document.createElement('li');
   while (node.firstChild) {
    li.appendChild(node.firstChild);
   }
   // An array of lines.  We split below, so this is initialized to one
   // un-split line.
   var listItems = [li];
 
   function walk(node) {
    var type = node.nodeType;
    if (type == 1 && !nocode.test(node.className)) {  // Element
     if ('br' === node.nodeName.toLowerCase()) {
      breakAfter(node);
      // Discard the <BR> since it is now flush against a </LI>.
      if (node.parentNode) {
       node.parentNode.removeChild(node);
      }
     } else {
      for (var child = node.firstChild; child; child = child.nextSibling) {
       walk(child);
      }
     }
    } else if ((type == 3 || type == 4) && isPreformatted) {  // Text
     var text = node.nodeValue;
     var match = text.match(lineBreak);
     if (match) {
      var firstLine = text.substring(0, match.index);
      node.nodeValue = firstLine;
      var tail = text.substring(match.index + match[0].length);
      if (tail) {
       var parent = node.parentNode;
       parent.insertBefore(
        document.createTextNode(tail), node.nextSibling);
      }
      breakAfter(node);
      if (!firstLine) {
       // Don't leave blank text nodes in the DOM.
       node.parentNode.removeChild(node);
      }
     }
    }
   }
 
   // Split a line after the given node.
   function breakAfter(lineEndNode) {
    // If there's nothing to the right, then we can skip ending the line
    // here, and move root-wards since splitting just before an end-tag
    // would require us to create a bunch of empty copies.
    while (!lineEndNode.nextSibling) {
      lineEndNode = lineEndNode.parentNode;
     if (!lineEndNode) { return; }
    }
 
    function breakLeftOf(limit, copy) {
     // Clone shallowly if this node needs to be on both sides of the break.
     var rightSide = copy ? limit.cloneNode(false) : limit;
     var parent = limit.parentNode;
     if (parent) {
      // We clone the parent chain.
      // This helps us resurrect important styling elements that cross lines.
      // E.g. in <i>Foo<br>Bar</i>
      // should be rewritten to <li><i>Foo</i></li><li><i>Bar</i></li>.
      var parentClone = breakLeftOf(parent, 1);
      // Move the clone and everything to the right of the original
      // onto the cloned parent.
      var next = limit.nextSibling;
      parentClone.appendChild(rightSide);
      for (var sibling = next; sibling; sibling = next) {
        next = sibling.nextSibling;
       parentClone.appendChild(sibling);
      }
     }
     return rightSide;
    }
 
    var copiedListItem = breakLeftOf(lineEndNode.nextSibling, 0);
 
    // Walk the parent chain until we reach an unattached LI.
    for (var parent;
      // Check nodeType since IE invents document fragments.
      (parent = copiedListItem.parentNode) && parent.nodeType === 1;) {
     copiedListItem = parent;
    }
    // Put it on the list of lines for later processing.
    listItems.push(copiedListItem);
   }
 
   // Split lines while there are lines left to split.
   for (var i = 0;  // Number of lines that have been split so far.
     i < listItems.length;  // length updated by breakAfter calls.
     ++i) {
    walk(listItems[i]);
   }
 
   // Make sure numeric indices show correctly.
   if (startLineNum === (startLineNum|0)) {
    listItems[0].setAttribute('value', startLineNum);
   }
 
   var ol = document.createElement('ol');
   ol.className = 'linenums';
   var offset = Math.max(0, ((startLineNum - 1 /* zero index */)) | 0) || 0;
   for (var i = 0, n = listItems.length; i < n; ++i) {
    li = listItems[i];
    // Stick a class on the LIs so that stylesheets can
    // color odd/even rows, or any other row pattern that
    // is co-prime with 10.
    li.className = 'L' + ((i + offset) % 10);
    if (!li.firstChild) {
     li.appendChild(document.createTextNode('\xA0'));
    }
    ol.appendChild(li);
   }
 
   node.appendChild(ol);
  }
 
 
  /**
  * Breaks {@code job.sourceCode} around style boundaries in
  * {@code job.decorations} and modifies {@code job.sourceNode} in place.
  * @param {JobT} job
  * @private
  */
  function recombineTagsAndDecorations(job) {
   var isIE8OrEarlier = /\bMSIE\s(\d+)/.exec(navigator.userAgent);
   isIE8OrEarlier = isIE8OrEarlier && +isIE8OrEarlier[1] <= 8;
   var newlineRe = /\n/g;
 
   var source = job.sourceCode;
   var sourceLength = source.length;
   // Index into source after the last code-unit recombined.
   var sourceIndex = 0;
 
   var spans = job.spans;
   var nSpans = spans.length;
   // Index into spans after the last span which ends at or before sourceIndex.
   var spanIndex = 0;
 
   var decorations = job.decorations;
   var nDecorations = decorations.length;
   // Index into decorations after the last decoration which ends at or before
   // sourceIndex.
   var decorationIndex = 0;
 
   // Remove all zero-length decorations.
   decorations[nDecorations] = sourceLength;
   var decPos, i;
   for (i = decPos = 0; i < nDecorations;) {
    if (decorations[i] !== decorations[i + 2]) {
     decorations[decPos++] = decorations[i++];
     decorations[decPos++] = decorations[i++];
    } else {
     i += 2;
    }
   }
   nDecorations = decPos;
 
   // Simplify decorations.
   for (i = decPos = 0; i < nDecorations;) {
    var startPos = decorations[i];
    // Conflate all adjacent decorations that use the same style.
    var startDec = decorations[i + 1];
    var end = i + 2;
    while (end + 2 <= nDecorations && decorations[end + 1] === startDec) {
     end += 2;
    }
    decorations[decPos++] = startPos;
    decorations[decPos++] = startDec;
    i = end;
   }
 
   nDecorations = decorations.length = decPos;
 
   var sourceNode = job.sourceNode;
   var oldDisplay = "";
   if (sourceNode) {
    oldDisplay = sourceNode.style.display;
    sourceNode.style.display = 'none';
   }
   try {
    var decoration = null;
    while (spanIndex < nSpans) {
     var spanStart = spans[spanIndex];
     var spanEnd = /** @type{number} */ (spans[spanIndex + 2])
       || sourceLength;
 
     var decEnd = decorations[decorationIndex + 2] || sourceLength;
 
     var end = Math.min(spanEnd, decEnd);
 
     var textNode = /** @type{Node} */ (spans[spanIndex + 1]);
     var styledText;
      if (textNode.nodeType !== 1  // Don't muck with <BR>s or <LI>s
       // Don't introduce spans around empty text nodes.
       && (styledText = source.substring(sourceIndex, end))) {
      // This may seem bizarre, and it is.  Emitting LF on IE causes the
      // code to display with spaces instead of line breaks.
      // Emitting Windows standard issue linebreaks (CRLF) causes a blank
      // space to appear at the beginning of every line but the first.
      // Emitting an old Mac OS 9 line separator makes everything spiffy.
      if (isIE8OrEarlier) {
       styledText = styledText.replace(newlineRe, '\r');
      }
      textNode.nodeValue = styledText;
      var document = textNode.ownerDocument;
      var span = document.createElement('span');
      span.className = decorations[decorationIndex + 1];
      var parentNode = textNode.parentNode;
      parentNode.replaceChild(span, textNode);
      span.appendChild(textNode);
      if (sourceIndex < spanEnd) {  // Split off a text node.
       spans[spanIndex + 1] = textNode
         // TODO: Possibly optimize by using '' if there's no flicker.
         = document.createTextNode(source.substring(end, spanEnd));
       parentNode.insertBefore(textNode, span.nextSibling);
      }
     }
 
     sourceIndex = end;
 
     if (sourceIndex >= spanEnd) {
      spanIndex += 2;
     }
     if (sourceIndex >= decEnd) {
      decorationIndex += 2;
     }
    }
   } finally {
    if (sourceNode) {
     sourceNode.style.display = oldDisplay;
    }
   }
  }
 
 
  /** Maps language-specific file extensions to handlers. */
  var langHandlerRegistry = {};
  /** Register a language handler for the given file extensions.
   * @param {function (JobT)} handler a function from source code to a list
   *    of decorations.  Takes a single argument job which describes the
   *    state of the computation and attaches the decorations to it.
   * @param {Array.<string>} fileExtensions
   */
  function registerLangHandler(handler, fileExtensions) {
   for (var i = fileExtensions.length; --i >= 0;) {
    var ext = fileExtensions[i];
    if (!langHandlerRegistry.hasOwnProperty(ext)) {
     langHandlerRegistry[ext] = handler;
    } else if (win['console']) {
     console['warn']('cannot override language handler %s', ext);
    }
   }
  }
  function langHandlerForExtension(extension, source) {
   if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {
    // Treat it as markup if the first non whitespace character is a < and
    // the last non-whitespace character is a >.
    extension = /^\s*</.test(source)
      ? 'default-markup'
      : 'default-code';
   }
   return langHandlerRegistry[extension];
  }
  registerLangHandler(decorateSource, ['default-code']);
  registerLangHandler(
    createSimpleLexer(
      [],
      [
      [PR_PLAIN,    /^[^<?]+/],
      [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/],
      [PR_COMMENT,   /^<\!--[\s\S]*?(?:-\->|$)/],
      // Unescaped content in an unknown language
      ['lang-',     /^<\?([\s\S]+?)(?:\?>|$)/],
      ['lang-',     /^<%([\s\S]+?)(?:%>|$)/],
      [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],
      ['lang-',     /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],
      // Unescaped content in javascript.  (Or possibly vbscript).
      ['lang-js',    /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],
      // Contains unescaped stylesheet content
      ['lang-css',   /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],
      ['lang-in.tag',  /^(<\/?[a-z][^<>]*>)/i]
      ]),
    ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);
  registerLangHandler(
    createSimpleLexer(
      [
      [PR_PLAIN,     /^[\s]+/, null, ' \t\r\n'],
      [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\'']
      ],
      [
      [PR_TAG,      /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],
      [PR_ATTRIB_NAME,  /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
      ['lang-uq.val',  /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],
      [PR_PUNCTUATION,  /^[=<>\/]+/],
      ['lang-js',    /^on\w+\s*=\s*\"([^\"]+)\"/i],
      ['lang-js',    /^on\w+\s*=\s*\'([^\']+)\'/i],
      ['lang-js',    /^on\w+\s*=\s*([^\"\'>\s]+)/i],
      ['lang-css',    /^style\s*=\s*\"([^\"]+)\"/i],
      ['lang-css',    /^style\s*=\s*\'([^\']+)\'/i],
      ['lang-css',    /^style\s*=\s*([^\"\'>\s]+)/i]
      ]),
    ['in.tag']);
  registerLangHandler(
    createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']);
  registerLangHandler(sourceDecorator({
      'keywords': CPP_KEYWORDS,
      'hashComments': true,
      'cStyleComments': true,
      'types': C_TYPES
     }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);
  registerLangHandler(sourceDecorator({
      'keywords': 'null,true,false'
     }), ['json']);
  registerLangHandler(sourceDecorator({
      'keywords': CSHARP_KEYWORDS,
      'hashComments': true,
      'cStyleComments': true,
      'verbatimStrings': true,
      'types': C_TYPES
     }), ['cs']);
  registerLangHandler(sourceDecorator({
      'keywords': JAVA_KEYWORDS,
      'cStyleComments': true
     }), ['java']);
  registerLangHandler(sourceDecorator({
      'keywords': SH_KEYWORDS,
      'hashComments': true,
      'multiLineStrings': true
     }), ['bash', 'bsh', 'csh', 'sh']);
  registerLangHandler(sourceDecorator({
      'keywords': PYTHON_KEYWORDS,
      'hashComments': true,
      'multiLineStrings': true,
      'tripleQuotedStrings': true
     }), ['cv', 'py', 'python']);
  registerLangHandler(sourceDecorator({
      'keywords': PERL_KEYWORDS,
      'hashComments': true,
      'multiLineStrings': true,
      'regexLiterals': 2  // multiline regex literals
     }), ['perl', 'pl', 'pm']);
  registerLangHandler(sourceDecorator({
      'keywords': RUBY_KEYWORDS,
      'hashComments': true,
      'multiLineStrings': true,
      'regexLiterals': true
     }), ['rb', 'ruby']);
  registerLangHandler(sourceDecorator({
      'keywords': JSCRIPT_KEYWORDS,
      'cStyleComments': true,
      'regexLiterals': true
     }), ['javascript', 'js', 'ts', 'typescript']);
  registerLangHandler(sourceDecorator({
      'keywords': COFFEE_KEYWORDS,
      'hashComments': 3,  // ### style block comments
      'cStyleComments': true,
      'multilineStrings': true,
      'tripleQuotedStrings': true,
      'regexLiterals': true
     }), ['coffee']);
  registerLangHandler(
    createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
 
  /** @param {JobT} job */
  function applyDecorator(job) {
   var opt_langExtension = job.langExtension;
 
   try {
    // Extract tags, and convert the source code to plain text.
    var sourceAndSpans = extractSourceSpans(job.sourceNode, job.pre);
    /** Plain text. @type {string} */
    var source = sourceAndSpans.sourceCode;
    job.sourceCode = source;
    job.spans = sourceAndSpans.spans;
    job.basePos = 0;
 
    // Apply the appropriate language handler
    langHandlerForExtension(opt_langExtension, source)(job);
 
    // Integrate the decorations and tags back into the source code,
    // modifying the sourceNode in place.
    recombineTagsAndDecorations(job);
   } catch (e) {
    if (win['console']) {
     console['log'](e && e['stack'] || e);
    }
   }
  }
 
  /**
  * Pretty print a chunk of code.
  * @param sourceCodeHtml {string} The HTML to pretty print.
  * @param opt_langExtension {string} The language name to use.
  *   Typically, a filename extension like 'cpp' or 'java'.
  * @param opt_numberLines {number|boolean} True to number lines,
  *   or the 1-indexed number of the first line in sourceCodeHtml.
  */
  function $prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) {
   /** @type{number|boolean} */
   var nl = opt_numberLines || false;
   /** @type{string|null} */
   var langExtension = opt_langExtension || null;
   /** @type{!Element} */
   var container = document.createElement('div');
   // This could cause images to load and onload listeners to fire.
   // E.g. <img onerror="alert(1337)" src="nosuchimage.png">.
   // We assume that the inner HTML is from a trusted source.
   // The pre-tag is required for IE8 which strips newlines from innerHTML
   // when it is injected into a <pre> tag.
   // http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie
   // http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript
   container.innerHTML = '<pre>' + sourceCodeHtml + '</pre>';
   container = /** @type{!Element} */(container.firstChild);
   if (nl) {
    numberLines(container, nl, true);
   }
 
   /** @type{JobT} */
   var job = {
    langExtension: langExtension,
    numberLines: nl,
    sourceNode: container,
    pre: 1,
    sourceCode: null,
    basePos: null,
    spans: null,
    decorations: null
   };
   applyDecorator(job);
   return container.innerHTML;
  }
 
  /**
   * Find all the {@code <pre>} and {@code <code>} tags in the DOM with
   * {@code class=prettyprint} and prettify them.
   *
   * @param {Function} opt_whenDone called when prettifying is done.
   * @param {HTMLElement|HTMLDocument} opt_root an element or document
   *  containing all the elements to pretty print.
   *  Defaults to {@code document.body}.
   */
  function $prettyPrint(opt_whenDone, opt_root) {
   var root = opt_root || document.body;
   var doc = root.ownerDocument || document;
   function byTagName(tn) { return root.getElementsByTagName(tn); }
   // fetch a list of nodes to rewrite
   var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];
   var elements = [];
   for (var i = 0; i < codeSegments.length; ++i) {
    for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
     elements.push(codeSegments[i][j]);
    }
   }
   codeSegments = null;
 
   var clock = Date;
   if (!clock['now']) {
    clock = { 'now': function () { return +(new Date); } };
   }
 
   // The loop is broken into a series of continuations to make sure that we
   // don't make the browser unresponsive when rewriting a large page.
   var k = 0;
 
   var langExtensionRe = /\blang(?:uage)?-([\w.]+)(?!\S)/;
   var prettyPrintRe = /\bprettyprint\b/;
   var prettyPrintedRe = /\bprettyprinted\b/;
   var preformattedTagNameRe = /pre|xmp/i;
   var codeRe = /^code$/i;
   var preCodeXmpRe = /^(?:pre|code|xmp)$/i;
   var EMPTY = {};
 
   function doWork() {
    var endTime = (win['PR_SHOULD_USE_CONTINUATION'] ?
           clock['now']() + 250 /* ms */ :
           Infinity);
    for (; k < elements.length && clock['now']() < endTime; k++) {
     var cs = elements[k];
 
     // Look for a preceding comment like
     // <?prettify lang="..." linenums="..."?>
     var attrs = EMPTY;
     {
      for (var preceder = cs; (preceder = preceder.previousSibling);) {
       var nt = preceder.nodeType;
       // <?foo?> is parsed by HTML 5 to a comment node (8)
       // like <!--?foo?-->, but in XML is a processing instruction
       var value = (nt === 7 || nt === 8) && preceder.nodeValue;
       if (value
         ? !/^\??prettify\b/.test(value)
         : (nt !== 3 || /\S/.test(preceder.nodeValue))) {
        // Skip over white-space text nodes but not others.
        break;
       }
       if (value) {
        attrs = {};
        value.replace(
          /\b(\w+)=([\w:.%+-]+)/g,
         function (_, name, value) { attrs[name] = value; });
        break;
       }
      }
     }
 
     var className = cs.className;
     if ((attrs !== EMPTY || prettyPrintRe.test(className))
       // Don't redo this if we've already done it.
       // This allows recalling pretty print to just prettyprint elements
       // that have been added to the page since last call.
       && !prettyPrintedRe.test(className)) {
 
      // make sure this is not nested in an already prettified element
      var nested = false;
      for (var p = cs.parentNode; p; p = p.parentNode) {
       var tn = p.tagName;
       if (preCodeXmpRe.test(tn)
         && p.className && prettyPrintRe.test(p.className)) {
        nested = true;
        break;
       }
      }
      if (!nested) {
       // Mark done.  If we fail to prettyprint for whatever reason,
       // we shouldn't try again.
       cs.className += ' prettyprinted';
 
       // If the classes includes a language extensions, use it.
       // Language extensions can be specified like
       //   <pre class="prettyprint lang-cpp">
       // the language extension "cpp" is used to find a language handler
       // as passed to PR.registerLangHandler.
       // HTML5 recommends that a language be specified using "language-"
       // as the prefix instead.  Google Code Prettify supports both.
       // http://dev.w3.org/html5/spec-author-view/the-code-element.html
       var langExtension = attrs['lang'];
       if (!langExtension) {
        langExtension = className.match(langExtensionRe);
        // Support <pre class="prettyprint"><code class="language-c">
        var wrapper;
        if (!langExtension && (wrapper = childContentWrapper(cs))
          && codeRe.test(wrapper.tagName)) {
         langExtension = wrapper.className.match(langExtensionRe);
        }
 
        if (langExtension) { langExtension = langExtension[1]; }
       }
 
       var preformatted;
       if (preformattedTagNameRe.test(cs.tagName)) {
        preformatted = 1;
       } else {
        var currentStyle = cs['currentStyle'];
        var defaultView = doc.defaultView;
        var whitespace = (
          currentStyle
          ? currentStyle['whiteSpace']
          : (defaultView
           && defaultView.getComputedStyle)
          ? defaultView.getComputedStyle(cs, null)
          .getPropertyValue('white-space')
          : 0);
        preformatted = whitespace
          && 'pre' === whitespace.substring(0, 3);
       }
 
       // Look for a class like linenums or linenums:<n> where <n> is the
       // 1-indexed number of the first line.
       var lineNums = attrs['linenums'];
       if (!(lineNums = lineNums === 'true' || +lineNums)) {
        lineNums = className.match(/\blinenums\b(?::(\d+))?/);
        lineNums =
         lineNums
         ? lineNums[1] && lineNums[1].length
          ? +lineNums[1] : true
         : false;
       }
       if (lineNums) { numberLines(cs, lineNums, preformatted); }
 
       // do the pretty printing
       var prettyPrintingJob = {
        langExtension: langExtension,
        sourceNode: cs,
        numberLines: lineNums,
        pre: preformatted,
        sourceCode: null,
        basePos: null,
        spans: null,
        decorations: null
       };
       applyDecorator(prettyPrintingJob);
      }
     }
    }
    if (k < elements.length) {
     // finish up in a continuation
     win.setTimeout(doWork, 250);
    } else if ('function' === typeof opt_whenDone) {
     opt_whenDone();
    }
   }
 
   doWork();
  }
 
  /**
  * Contains functions for creating and registering new language handlers.
  * @type {Object}
  */
  var PR = win['PR'] = {
     'createSimpleLexer': createSimpleLexer,
     'registerLangHandler': registerLangHandler,
     'sourceDecorator': sourceDecorator,
     'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
     'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
     'PR_COMMENT': PR_COMMENT,
     'PR_DECLARATION': PR_DECLARATION,
     'PR_KEYWORD': PR_KEYWORD,
     'PR_LITERAL': PR_LITERAL,
     'PR_NOCODE': PR_NOCODE,
     'PR_PLAIN': PR_PLAIN,
     'PR_PUNCTUATION': PR_PUNCTUATION,
     'PR_SOURCE': PR_SOURCE,
     'PR_STRING': PR_STRING,
     'PR_TAG': PR_TAG,
     'PR_TYPE': PR_TYPE,
     'prettyPrintOne':
      IN_GLOBAL_SCOPE
       ? (win['prettyPrintOne'] = $prettyPrintOne)
       : (prettyPrintOne = $prettyPrintOne),
     'prettyPrint':
      IN_GLOBAL_SCOPE
       ? (win['prettyPrint'] = $prettyPrint)
       : (prettyPrint = $prettyPrint)
    };
 
  // Make PR available via the Asynchronous Module Definition (AMD) API.
  // Per https://github.com/amdjs/amdjs-api/wiki/AMD:
  // The Asynchronous Module Definition (AMD) API specifies a
  // mechanism for defining modules such that the module and its
  // dependencies can be asynchronously loaded.
  // ...
  // To allow a clear indicator that a global define function (as
  // needed for script src browser loading) conforms to the AMD API,
  // any global define function SHOULD have a property called "amd"
  // whose value is an object. This helps avoid conflict with any
  // other existing JavaScript code that could have defined a define()
  // function that does not conform to the AMD API.
  var define = win['define'];
  if (typeof define === "function" && define['amd']) {
   define("google-code-prettify", [], function () {
    return PR;
    });
    });
  }
   const $block = $content.find( '.hljs:not(.highlighted)' ); // 不重复高亮
})();
   if ($block.length === 0) { return; }
   console.log('Hook: wikipage.content, 开始执行语法高亮');
   const path = '//cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js';
   (window.hljs ? Promise.resolve() : mw.loader.getScript( path )).then(function() { // 不重复下载脚本
     $block.each(function() { hljs.highlightBlock( this ); }).addClass( 'highlighted' ).filter( '.linenums' )
       .html(function() { // 添加行号。这里不使用<table>排版,而是使用<ol>
       const $this = $(this);
       $this.children( ':contains(\n)' ).replaceWith(function() { // 先处理跨行元素
         const $self = $(this);
         return $self.html().split( '\n' ).map(function(ele) {
           return $self.clone().html( ele ).prop( 'outerHTML' );
         }).join('\n');
       });
       var lines = $this.html().replace(/\n$/, '').split('\n');
       if (mw.config.get( 'wgNamespaceNumber' ) == 274) { lines = lines.slice(1, -1); } // 扔掉首尾的Wikitext注释
       return $('<ol>', {html: lines.map(function(ele, i) { return $('<li>', {html: ele, id: 'L' + (i + 1)}); })})
         .css('padding-left', lines.length.toString().length + 2.5 + 'ch');
     });
     mw.hook( 'code.prettify' ).fire( $block );
     const $cssblock = $block.filter( '.css' ); // 对CSS代码添加指示色块
     if ($cssblock.length === 0) { return; }
     const $color = $('<span>', {class: 'hljs-color'});
     $cssblock.find( '.hljs-number:contains(#)' ).before(function() { // 16进制颜色
       const color = this.textContent,
         n = color.length,
         alpha = n == 5 ? color[4].repeat(2) : color.slice(7);
       return $color.clone().css({ color: color.slice(0, n > 5 ? 7 : 4),
         opacity: alpha ? parseInt(alpha, 16) / 255 : undefined });
     });
     $cssblock.find( '.hljs-built_in:contains(rgb), .hljs-built_in:contains(hsl)' ).before(function() { // RGB颜色
       const $siblings = $(this).parent().contents(), // 标点符号都是text节点,所以需要使用contents
         index = $siblings.index( this ),
         n = this.textContent.length == 4 ? 9 : 7, // rgba/hsla或rgb/hsl
         // 右半括号那一项可能有分号
         color = $siblings.slice(index, index + n).map(function() { return this.textContent; }).toArray();
       return $color.clone().css({ color: color[0].slice(0, 3) + color.slice(1, 7).join('') + ')',
         opacity: color[8] });
     });
     // 手动跳转
     const fragment = decodeURIComponent( location.hash.slice(1) ),
       target = document.getElementById( fragment ); // 用户输入内容,禁止使用$()
     if (/^L\d+$/.test( fragment ) && target) { target.scrollIntoView({ behavior: 'smooth' }); }
   }, function(reason) { mw.apiFailure(reason, 'highlight.js'); });
});
//</nowiki>
// [[category:作为模块的小工具]] [[category:阅读工具]] [[category:默认开启的小工具]] [[category:桌面版小工具]] [[category:手机版小工具]]
// {{DEFAULTSORT:code-prettify.js}}

2021年5月7日 (五) 21:13的版本

//<nowiki>
// 由ResourceLoader直接调用,不可使用ES6语法
/**
 * @Function: 高亮JavaScript、CSS、HTML和Lua,按行号跳转,并添加行号和指示色块
 * @Dependencies: ext.gadget.site-lib
 * @Source: [[moegirl:mediawiki:gadget-code-prettify.js]]和[[moegirl:user:机智的小鱼君/gadget/Highlight.js]]
 * @EditedBy: [[User:Bhsd]]
 */
"use strict";
/*global hljs */
const acceptLangs = {js: "javascript", javascript: "javascript", json: "json", css: "css", html: "xml",
    scribunto: "lua", lua: "lua"},
    contentModel = mw.config.get( "wgPageContentModel" ).toLowerCase();

mw.hook( 'wikipage.content' ).add(function($content) {
    if (contentModel in acceptLangs) {
        $content.find( '.mw-code' ).addClass('hljs linenums ' + acceptLangs[contentModel]);
    }
    $content.find('pre[lang], code[lang]').addClass(function() {
        const $self = $(this),
            lang = $self.attr( "lang" ).toLowerCase();
        if (lang in acceptLangs) { return "hljs " + acceptLangs[lang] + ($self.is('pre') ? " linenums" : ""); }
    });
    const $block = $content.find( '.hljs:not(.highlighted)' ); // 不重复高亮
    if ($block.length === 0) { return; }
    console.log('Hook: wikipage.content, 开始执行语法高亮');
    const path = '//cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js';
    (window.hljs ? Promise.resolve() : mw.loader.getScript( path )).then(function() { // 不重复下载脚本
        $block.each(function() { hljs.highlightBlock( this ); }).addClass( 'highlighted' ).filter( '.linenums' )
            .html(function() { // 添加行号。这里不使用<table>排版,而是使用<ol>
            const $this = $(this);
            $this.children( ':contains(\n)' ).replaceWith(function() { // 先处理跨行元素
                const $self = $(this);
                return $self.html().split( '\n' ).map(function(ele) {
                    return $self.clone().html( ele ).prop( 'outerHTML' );
                }).join('\n');
            });
            var lines = $this.html().replace(/\n$/, '').split('\n');
            if (mw.config.get( 'wgNamespaceNumber' ) == 274) { lines = lines.slice(1, -1); } // 扔掉首尾的Wikitext注释
            return $('<ol>', {html: lines.map(function(ele, i) { return $('<li>', {html: ele, id: 'L' + (i + 1)}); })})
                .css('padding-left', lines.length.toString().length + 2.5 + 'ch');
        });
        mw.hook( 'code.prettify' ).fire( $block );
        const $cssblock = $block.filter( '.css' ); // 对CSS代码添加指示色块
        if ($cssblock.length === 0) { return; }
        const $color = $('<span>', {class: 'hljs-color'});
        $cssblock.find( '.hljs-number:contains(#)' ).before(function() { // 16进制颜色
            const color = this.textContent,
                n = color.length,
                alpha = n == 5 ? color[4].repeat(2) : color.slice(7);
            return $color.clone().css({ color: color.slice(0, n > 5 ? 7 : 4),
                opacity: alpha ? parseInt(alpha, 16) / 255 : undefined });
        });
        $cssblock.find( '.hljs-built_in:contains(rgb), .hljs-built_in:contains(hsl)' ).before(function() { // RGB颜色
            const $siblings = $(this).parent().contents(), // 标点符号都是text节点,所以需要使用contents
                index = $siblings.index( this ),
                n = this.textContent.length == 4 ? 9 : 7, // rgba/hsla或rgb/hsl
                // 右半括号那一项可能有分号
                color = $siblings.slice(index, index + n).map(function() { return this.textContent; }).toArray();
            return $color.clone().css({ color: color[0].slice(0, 3) + color.slice(1, 7).join('') + ')',
                opacity: color[8] });
        });
        // 手动跳转
        const fragment = decodeURIComponent( location.hash.slice(1) ),
            target = document.getElementById( fragment ); // 用户输入内容,禁止使用$()
        if (/^L\d+$/.test( fragment ) && target) { target.scrollIntoView({ behavior: 'smooth' }); }
    }, function(reason) { mw.apiFailure(reason, 'highlight.js'); });
});
//</nowiki>
// [[category:作为模块的小工具]] [[category:阅读工具]] [[category:默认开启的小工具]] [[category:桌面版小工具]] [[category:手机版小工具]]
// {{DEFAULTSORT:code-prettify.js}}