comparison vendor/assets/javascripts/prettify.js @ 220:183e8560a337

Update prettify to 2013/03/04.
author Edho Arief <edho@myconan.net>
date Sun, 12 May 2013 12:05:08 +0900
parents 56c4cd16d849
children
comparison
equal deleted inserted replaced
219:56c4cd16d849 220:183e8560a337
51 * </blockquote> 51 * </blockquote>
52 * @requires console 52 * @requires console
53 */ 53 */
54 54
55 // JSLint declarations 55 // JSLint declarations
56 /*global console, document, navigator, setTimeout, window */ 56 /*global console, document, navigator, setTimeout, window, define */
57
58 /** @define {boolean} */
59 var IN_GLOBAL_SCOPE = true;
57 60
58 /** 61 /**
59 * Split {@code prettyPrint} into multiple timeouts so as not to interfere with 62 * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
60 * UI events. 63 * UI events.
61 * If set to {@code false}, {@code prettyPrint()} is synchronous. 64 * If set to {@code false}, {@code prettyPrint()} is synchronous.
62 */ 65 */
63 window['PR_SHOULD_USE_CONTINUATION'] = true; 66 window['PR_SHOULD_USE_CONTINUATION'] = true;
64 67
68 /**
69 * Pretty print a chunk of code.
70 * @param {string} sourceCodeHtml The HTML to pretty print.
71 * @param {string} opt_langExtension The language name to use.
72 * Typically, a filename extension like 'cpp' or 'java'.
73 * @param {number|boolean} opt_numberLines True to number lines,
74 * or the 1-indexed number of the first line in sourceCodeHtml.
75 * @return {string} code as html, but prettier
76 */
77 var prettyPrintOne;
78 /**
79 * Find all the {@code <pre>} and {@code <code>} tags in the DOM with
80 * {@code class=prettyprint} and prettify them.
81 *
82 * @param {Function} opt_whenDone called when prettifying is done.
83 * @param {HTMLElement|HTMLDocument} opt_root an element or document
84 * containing all the elements to pretty print.
85 * Defaults to {@code document.body}.
86 */
87 var prettyPrint;
88
89
65 (function () { 90 (function () {
91 var win = window;
66 // Keyword lists for various languages. 92 // Keyword lists for various languages.
67 // We use things that coerce to strings to make them compact when minified 93 // We use things that coerce to strings to make them compact when minified
68 // and to defeat aggressive optimizers that fold large string constants. 94 // and to defeat aggressive optimizers that fold large string constants.
69 var FLOW_CONTROL_KEYWORDS = ["break,continue,do,else,for,if,return,while"]; 95 var FLOW_CONTROL_KEYWORDS = ["break,continue,do,else,for,if,return,while"];
70 var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,"auto,case,char,const,default," + 96 var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,"auto,case,char,const,default," +
71 "double,enum,extern,float,goto,int,long,register,short,signed,sizeof," + 97 "double,enum,extern,float,goto,inline,int,long,register,short,signed," +
72 "static,struct,switch,typedef,union,unsigned,void,volatile"]; 98 "sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];
73 var COMMON_KEYWORDS = [C_KEYWORDS,"catch,class,delete,false,import," + 99 var COMMON_KEYWORDS = [C_KEYWORDS,"catch,class,delete,false,import," +
74 "new,operator,private,protected,public,this,throw,true,try,typeof"]; 100 "new,operator,private,protected,public,this,throw,true,try,typeof"];
75 var CPP_KEYWORDS = [COMMON_KEYWORDS,"alignof,align_union,asm,axiom,bool," + 101 var CPP_KEYWORDS = [COMMON_KEYWORDS,"alignof,align_union,asm,axiom,bool," +
76 "concept,concept_map,const_cast,constexpr,decltype," + 102 "concept,concept_map,const_cast,constexpr,decltype,delegate," +
77 "dynamic_cast,explicit,export,friend,inline,late_check," + 103 "dynamic_cast,explicit,export,friend,generic,late_check," +
78 "mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast," + 104 "mutable,namespace,nullptr,property,reinterpret_cast,static_assert," +
79 "template,typeid,typename,using,virtual,where"]; 105 "static_cast,template,typeid,typename,using,virtual,where"];
80 var JAVA_KEYWORDS = [COMMON_KEYWORDS, 106 var JAVA_KEYWORDS = [COMMON_KEYWORDS,
81 "abstract,boolean,byte,extends,final,finally,implements,import," + 107 "abstract,assert,boolean,byte,extends,final,finally,implements,import," +
82 "instanceof,null,native,package,strictfp,super,synchronized,throws," + 108 "instanceof,interface,null,native,package,strictfp,super,synchronized," +
83 "transient"]; 109 "throws,transient"];
84 var CSHARP_KEYWORDS = [JAVA_KEYWORDS, 110 var CSHARP_KEYWORDS = [JAVA_KEYWORDS,
85 "as,base,by,checked,decimal,delegate,descending,dynamic,event," + 111 "as,base,by,checked,decimal,delegate,descending,dynamic,event," +
86 "fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock," + 112 "fixed,foreach,from,group,implicit,in,internal,into,is,let," +
87 "object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed," + 113 "lock,object,out,override,orderby,params,partial,readonly,ref,sbyte," +
88 "stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"]; 114 "sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort," +
115 "var,virtual,where"];
89 var COFFEE_KEYWORDS = "all,and,by,catch,class,else,extends,false,finally," + 116 var COFFEE_KEYWORDS = "all,and,by,catch,class,else,extends,false,finally," +
90 "for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then," + 117 "for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then," +
91 "true,try,unless,until,when,while,yes"; 118 "throw,true,try,unless,until,when,while,yes";
92 var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS, 119 var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS,
93 "debugger,eval,export,function,get,null,set,undefined,var,with," + 120 "debugger,eval,export,function,get,null,set,undefined,var,with," +
94 "Infinity,NaN"]; 121 "Infinity,NaN"];
95 var PERL_KEYWORDS = "caller,delete,die,do,dump,elsif,eval,exit,foreach,for," + 122 var PERL_KEYWORDS = "caller,delete,die,do,dump,elsif,eval,exit,foreach,for," +
96 "goto,if,import,last,local,my,next,no,our,print,package,redo,require," + 123 "goto,if,import,last,local,my,next,no,our,print,package,redo,require," +
101 "False,True,None"]; 128 "False,True,None"];
102 var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "alias,and,begin,case,class," + 129 var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "alias,and,begin,case,class," +
103 "def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo," + 130 "def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo," +
104 "rescue,retry,self,super,then,true,undef,unless,until,when,yield," + 131 "rescue,retry,self,super,then,true,undef,unless,until,when,yield," +
105 "BEGIN,END"]; 132 "BEGIN,END"];
133 var RUST_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "as,assert,const,copy,drop," +
134 "enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv," +
135 "pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"];
106 var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "case,done,elif,esac,eval,fi," + 136 var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "case,done,elif,esac,eval,fi," +
107 "function,in,local,set,then,until"]; 137 "function,in,local,set,then,until"];
108 var ALL_KEYWORDS = [ 138 var ALL_KEYWORDS = [
109 CPP_KEYWORDS, CSHARP_KEYWORDS, JSCRIPT_KEYWORDS, PERL_KEYWORDS + 139 CPP_KEYWORDS, CSHARP_KEYWORDS, JSCRIPT_KEYWORDS, PERL_KEYWORDS,
110 PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS]; 140 PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS];
111 var C_TYPES = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/; 141 var C_TYPES = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/;
112 142
113 // token style names. correspond to css classes 143 // token style names. correspond to css classes
114 /** 144 /**
115 * token style for a string literal 145 * token style for a string literal
116 * @const 146 * @const
140 * token style for a punctuation string. 170 * token style for a punctuation string.
141 * @const 171 * @const
142 */ 172 */
143 var PR_PUNCTUATION = 'pun'; 173 var PR_PUNCTUATION = 'pun';
144 /** 174 /**
145 * token style for a punctuation string. 175 * token style for plain text.
146 * @const 176 * @const
147 */ 177 */
148 var PR_PLAIN = 'pln'; 178 var PR_PLAIN = 'pln';
149 179
150 /** 180 /**
178 * embedding of line numbers within code listings. 208 * embedding of line numbers within code listings.
179 * @const 209 * @const
180 */ 210 */
181 var PR_NOCODE = 'nocode'; 211 var PR_NOCODE = 'nocode';
182 212
183 213
184 214
185 /** 215 /**
186 * A set of tokens that can precede a regular expression literal in 216 * A set of tokens that can precede a regular expression literal in
187 * javascript 217 * javascript
188 * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html 218 * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html
189 * has the full list, but I've removed ones that might be problematic when 219 * has the full list, but I've removed ones that might be problematic when
190 * seen in languages that don't support regular expression literals. 220 * seen in languages that don't support regular expression literals.
191 * 221 *
192 * <p>Specifically, I've removed any keywords that can't precede a regexp 222 * <p>Specifically, I've removed any keywords that can't precede a regexp
193 * literal in a syntactically legal javascript program, and I've removed the 223 * literal in a syntactically legal javascript program, and I've removed the
194 * "in" keyword since it's not a keyword in many languages, and might be used 224 * "in" keyword since it's not a keyword in many languages, and might be used
195 * as a count of inches. 225 * as a count of inches.
196 * 226 *
197 * <p>The link a above does not accurately describe EcmaScript rules since 227 * <p>The link above does not accurately describe EcmaScript rules since
198 * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works 228 * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
199 * very well in practice. 229 * very well in practice.
200 * 230 *
201 * @private 231 * @private
202 * @const 232 * @const
203 */ 233 */
204 var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*'; 234 var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*';
205 235
206 // CAVEAT: this does not properly handle the case where a regular 236 // CAVEAT: this does not properly handle the case where a regular
207 // expression immediately follows another since a regular expression may 237 // expression immediately follows another since a regular expression may
208 // have flags for case-sensitivity and the like. Having regexp tokens 238 // have flags for case-sensitivity and the like. Having regexp tokens
209 // adjacent is not valid in any language I'm aware of, so I'm punting. 239 // adjacent is not valid in any language I'm aware of, so I'm punting.
210 // TODO: maybe style special characters inside a regexp as punctuation. 240 // TODO: maybe style special characters inside a regexp as punctuation.
211
212 241
213 /** 242 /**
214 * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally 243 * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
215 * matches the union of the sets of strings matched by the input RegExp. 244 * matches the union of the sets of strings matched by the input RegExp.
216 * Since it matches globally, if the input strings have a start-of-input 245 * Since it matches globally, if the input strings have a start-of-input
265 function encodeEscape(charCode) { 294 function encodeEscape(charCode) {
266 if (charCode < 0x20) { 295 if (charCode < 0x20) {
267 return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16); 296 return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
268 } 297 }
269 var ch = String.fromCharCode(charCode); 298 var ch = String.fromCharCode(charCode);
270 if (ch === '\\' || ch === '-' || ch === '[' || ch === ']') { 299 return (ch === '\\' || ch === '-' || ch === ']' || ch === '^')
271 ch = '\\' + ch; 300 ? "\\" + ch : ch;
272 }
273 return ch;
274 } 301 }
275 302
276 function caseFoldCharset(charSet) { 303 function caseFoldCharset(charSet) {
277 var charsetParts = charSet.substring(1, charSet.length - 1).match( 304 var charsetParts = charSet.substring(1, charSet.length - 1).match(
278 new RegExp( 305 new RegExp(
282 + '|\\\\[0-7]{1,2}' 309 + '|\\\\[0-7]{1,2}'
283 + '|\\\\[\\s\\S]' 310 + '|\\\\[\\s\\S]'
284 + '|-' 311 + '|-'
285 + '|[^-\\\\]', 312 + '|[^-\\\\]',
286 'g')); 313 'g'));
287 var groups = [];
288 var ranges = []; 314 var ranges = [];
289 var inverse = charsetParts[0] === '^'; 315 var inverse = charsetParts[0] === '^';
316
317 var out = ['['];
318 if (inverse) { out.push('^'); }
319
290 for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) { 320 for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
291 var p = charsetParts[i]; 321 var p = charsetParts[i];
292 if (/\\[bdsw]/i.test(p)) { // Don't muck with named groups. 322 if (/\\[bdsw]/i.test(p)) { // Don't muck with named groups.
293 groups.push(p); 323 out.push(p);
294 } else { 324 } else {
295 var start = decodeEscape(p); 325 var start = decodeEscape(p);
296 var end; 326 var end;
297 if (i + 2 < n && '-' === charsetParts[i + 1]) { 327 if (i + 2 < n && '-' === charsetParts[i + 1]) {
298 end = decodeEscape(charsetParts[i + 2]); 328 end = decodeEscape(charsetParts[i + 2]);
318 348
319 // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]] 349 // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
320 // -> [[1, 12], [14, 14], [16, 17]] 350 // -> [[1, 12], [14, 14], [16, 17]]
321 ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1] - a[1]); }); 351 ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1] - a[1]); });
322 var consolidatedRanges = []; 352 var consolidatedRanges = [];
323 var lastRange = [NaN, NaN]; 353 var lastRange = [];
324 for (var i = 0; i < ranges.length; ++i) { 354 for (var i = 0; i < ranges.length; ++i) {
325 var range = ranges[i]; 355 var range = ranges[i];
326 if (range[0] <= lastRange[1] + 1) { 356 if (range[0] <= lastRange[1] + 1) {
327 lastRange[1] = Math.max(lastRange[1], range[1]); 357 lastRange[1] = Math.max(lastRange[1], range[1]);
328 } else { 358 } else {
329 consolidatedRanges.push(lastRange = range); 359 consolidatedRanges.push(lastRange = range);
330 } 360 }
331 } 361 }
332 362
333 var out = ['['];
334 if (inverse) { out.push('^'); }
335 out.push.apply(out, groups);
336 for (var i = 0; i < consolidatedRanges.length; ++i) { 363 for (var i = 0; i < consolidatedRanges.length; ++i) {
337 var range = consolidatedRanges[i]; 364 var range = consolidatedRanges[i];
338 out.push(encodeEscape(range[0])); 365 out.push(encodeEscape(range[0]));
339 if (range[1] > range[0]) { 366 if (range[1] > range[0]) {
340 if (range[1] + 1 > range[0]) { out.push('-'); } 367 if (range[1] + 1 > range[0]) { out.push('-'); }
356 + '|\\\\u[A-Fa-f0-9]{4}' // a unicode escape 383 + '|\\\\u[A-Fa-f0-9]{4}' // a unicode escape
357 + '|\\\\x[A-Fa-f0-9]{2}' // a hex escape 384 + '|\\\\x[A-Fa-f0-9]{2}' // a hex escape
358 + '|\\\\[0-9]+' // a back-reference or octal escape 385 + '|\\\\[0-9]+' // a back-reference or octal escape
359 + '|\\\\[^ux0-9]' // other escape sequence 386 + '|\\\\[^ux0-9]' // other escape sequence
360 + '|\\(\\?[:!=]' // start of a non-capturing group 387 + '|\\(\\?[:!=]' // start of a non-capturing group
361 + '|[\\(\\)\\^]' // start/emd of a group, or line start 388 + '|[\\(\\)\\^]' // start/end of a group, or line start
362 + '|[^\\x5B\\x5C\\(\\)\\^]+' // run of other characters 389 + '|[^\\x5B\\x5C\\(\\)\\^]+' // run of other characters
363 + ')', 390 + ')',
364 'g')); 391 'g'));
365 var n = parts.length; 392 var n = parts.length;
366 393
376 if (p === '(') { 403 if (p === '(') {
377 // groups are 1-indexed, so max group index is count of '(' 404 // groups are 1-indexed, so max group index is count of '('
378 ++groupIndex; 405 ++groupIndex;
379 } else if ('\\' === p.charAt(0)) { 406 } else if ('\\' === p.charAt(0)) {
380 var decimalValue = +p.substring(1); 407 var decimalValue = +p.substring(1);
381 if (decimalValue && decimalValue <= groupIndex) { 408 if (decimalValue) {
382 capturedGroups[decimalValue] = -1; 409 if (decimalValue <= groupIndex) {
410 capturedGroups[decimalValue] = -1;
411 } else {
412 // Replace with an unambiguous escape sequence so that
413 // an octal escape sequence does not turn into a backreference
414 // to a capturing group from an earlier regex.
415 parts[i] = encodeEscape(decimalValue);
416 }
383 } 417 }
384 } 418 }
385 } 419 }
386 420
387 // Renumber groups and reduce capturing groups to non-capturing groups 421 // Renumber groups and reduce capturing groups to non-capturing groups
393 } 427 }
394 for (var i = 0, groupIndex = 0; i < n; ++i) { 428 for (var i = 0, groupIndex = 0; i < n; ++i) {
395 var p = parts[i]; 429 var p = parts[i];
396 if (p === '(') { 430 if (p === '(') {
397 ++groupIndex; 431 ++groupIndex;
398 if (capturedGroups[groupIndex] === undefined) { 432 if (!capturedGroups[groupIndex]) {
399 parts[i] = '(?:'; 433 parts[i] = '(?:';
400 } 434 }
401 } else if ('\\' === p.charAt(0)) { 435 } else if ('\\' === p.charAt(0)) {
402 var decimalValue = +p.substring(1); 436 var decimalValue = +p.substring(1);
403 if (decimalValue && decimalValue <= groupIndex) { 437 if (decimalValue && decimalValue <= groupIndex) {
404 parts[i] = '\\' + capturedGroups[groupIndex]; 438 parts[i] = '\\' + capturedGroups[decimalValue];
405 } 439 }
406 } 440 }
407 } 441 }
408 442
409 // Remove any prefix anchors so that the output will match anywhere. 443 // Remove any prefix anchors so that the output will match anywhere.
410 // ^^ really does mean an anchored match though. 444 // ^^ really does mean an anchored match though.
411 for (var i = 0, groupIndex = 0; i < n; ++i) { 445 for (var i = 0; i < n; ++i) {
412 if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; } 446 if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
413 } 447 }
414 448
415 // Expand letters to groups to handle mixing of case-sensitive and 449 // Expand letters to groups to handle mixing of case-sensitive and
416 // case-insensitive patterns if necessary. 450 // case-insensitive patterns if necessary.
443 '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')'); 477 '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
444 } 478 }
445 479
446 return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g'); 480 return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
447 } 481 }
448
449 482
450 /** 483 /**
451 * Split markup into a string of source code and an array mapping ranges in 484 * Split markup into a string of source code and an array mapping ranges in
452 * that string to the text nodes in which they appear. 485 * that string to the text nodes in which they appear.
453 * 486 *
468 * <p> 501 * <p>
469 * It will produce the output:</p> 502 * It will produce the output:</p>
470 * <pre> 503 * <pre>
471 * { 504 * {
472 * sourceCode: "print 'Hello '\n + 'World';", 505 * sourceCode: "print 'Hello '\n + 'World';",
473 * // 1 2 506 * // 1 2
474 * // 012345678901234 5678901234567 507 * // 012345678901234 5678901234567
475 * spans: [0, #1, 6, #2, 14, #3, 15, #4] 508 * spans: [0, #1, 6, #2, 14, #3, 15, #4]
476 * } 509 * }
477 * </pre> 510 * </pre>
478 * <p> 511 * <p>
479 * where #1 is a reference to the {@code "print "} text node above, and so 512 * where #1 is a reference to the {@code "print "} text node above, and so
486 * that contain the text for those substrings. 519 * that contain the text for those substrings.
487 * Substrings continue until the next index or the end of the source. 520 * Substrings continue until the next index or the end of the source.
488 * </p> 521 * </p>
489 * 522 *
490 * @param {Node} node an HTML DOM subtree containing source-code. 523 * @param {Node} node an HTML DOM subtree containing source-code.
524 * @param {boolean} isPreformatted true if white-space in text nodes should
525 * be considered significant.
491 * @return {Object} source code and the text nodes in which they occur. 526 * @return {Object} source code and the text nodes in which they occur.
492 */ 527 */
493 function extractSourceSpans(node) { 528 function extractSourceSpans(node, isPreformatted) {
494 var nocode = /(?:^|\s)nocode(?:\s|$)/; 529 var nocode = /(?:^|\s)nocode(?:\s|$)/;
495 530
496 var chunks = []; 531 var chunks = [];
497 var length = 0; 532 var length = 0;
498 var spans = []; 533 var spans = [];
499 var k = 0; 534 var k = 0;
500 535
501 var whitespace;
502 if (node.currentStyle) {
503 whitespace = node.currentStyle.whiteSpace;
504 } else if (window.getComputedStyle) {
505 whitespace = document.defaultView.getComputedStyle(node, null)
506 .getPropertyValue('white-space');
507 }
508 var isPreformatted = whitespace && 'pre' === whitespace.substring(0, 3);
509
510 function walk(node) { 536 function walk(node) {
511 switch (node.nodeType) { 537 var type = node.nodeType;
512 case 1: // Element 538 if (type == 1) { // Element
513 if (nocode.test(node.className)) { return; } 539 if (nocode.test(node.className)) { return; }
514 for (var child = node.firstChild; child; child = child.nextSibling) { 540 for (var child = node.firstChild; child; child = child.nextSibling) {
515 walk(child); 541 walk(child);
516 } 542 }
517 var nodeName = node.nodeName; 543 var nodeName = node.nodeName.toLowerCase();
518 if ('BR' === nodeName || 'LI' === nodeName) { 544 if ('br' === nodeName || 'li' === nodeName) {
519 chunks[k] = '\n'; 545 chunks[k] = '\n';
520 spans[k << 1] = length++; 546 spans[k << 1] = length++;
521 spans[(k++ << 1) | 1] = node; 547 spans[(k++ << 1) | 1] = node;
522 } 548 }
523 break; 549 } else if (type == 3 || type == 4) { // Text
524 case 3: case 4: // Text 550 var text = node.nodeValue;
525 var text = node.nodeValue; 551 if (text.length) {
526 if (text.length) { 552 if (!isPreformatted) {
527 if (!isPreformatted) { 553 text = text.replace(/[ \t\r\n]+/g, ' ');
528 text = text.replace(/[ \t\r\n]+/g, ' '); 554 } else {
529 } else { 555 text = text.replace(/\r\n?/g, '\n'); // Normalize newlines.
530 text = text.replace(/\r\n?/g, '\n'); // Normalize newlines. 556 }
531 } 557 // TODO: handle tabs here?
532 // TODO: handle tabs here? 558 chunks[k] = text;
533 chunks[k] = text; 559 spans[k << 1] = length;
534 spans[k << 1] = length; 560 length += text.length;
535 length += text.length; 561 spans[(k++ << 1) | 1] = node;
536 spans[(k++ << 1) | 1] = node; 562 }
537 }
538 break;
539 } 563 }
540 } 564 }
541 565
542 walk(node); 566 walk(node);
543 567
544 return { 568 return {
545 sourceCode: chunks.join('').replace(/\n$/, ''), 569 sourceCode: chunks.join('').replace(/\n$/, ''),
546 spans: spans 570 spans: spans
547 }; 571 };
548 } 572 }
549
550 573
551 /** 574 /**
552 * Apply the given language handler to sourceCode and add the resulting 575 * Apply the given language handler to sourceCode and add the resulting
553 * decorations to out. 576 * decorations to out.
554 * @param {number} basePos the index of sourceCode within the chunk of source 577 * @param {number} basePos the index of sourceCode within the chunk of source
810 shortcutStylePatterns.push( 833 shortcutStylePatterns.push(
811 [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']); 834 [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']);
812 } else { 835 } else {
813 // Stop C preprocessor declarations at an unclosed open comment 836 // Stop C preprocessor declarations at an unclosed open comment
814 shortcutStylePatterns.push( 837 shortcutStylePatterns.push(
815 [PR_COMMENT, /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/, 838 [PR_COMMENT, /^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\r\n]*)/,
816 null, '#']); 839 null, '#']);
817 } 840 }
841 // #include <stdio.h>
818 fallthroughStylePatterns.push( 842 fallthroughStylePatterns.push(
819 [PR_STRING, 843 [PR_STRING,
820 /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/, 844 /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,
821 null]); 845 null]);
822 } else { 846 } else {
823 shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']); 847 shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
824 } 848 }
825 } 849 }
826 if (options['cStyleComments']) { 850 if (options['cStyleComments']) {
827 fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]); 851 fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
828 fallthroughStylePatterns.push( 852 fallthroughStylePatterns.push(
829 [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]); 853 [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
830 } 854 }
831 if (options['regexLiterals']) { 855 var regexLiterals = options['regexLiterals'];
856 if (regexLiterals) {
857 /**
858 * @const
859 */
860 var regexExcls = regexLiterals > 1
861 ? '' // Multiline regex literals
862 : '\n\r';
863 /**
864 * @const
865 */
866 var regexAny = regexExcls ? '.' : '[\\S\\s]';
832 /** 867 /**
833 * @const 868 * @const
834 */ 869 */
835 var REGEX_LITERAL = ( 870 var REGEX_LITERAL = (
836 // A regular expression literal starts with a slash that is 871 // A regular expression literal starts with a slash that is
837 // not followed by * or / so that it is not confused with 872 // not followed by * or / so that it is not confused with
838 // comments. 873 // comments.
839 '/(?=[^/*])' 874 '/(?=[^/*' + regexExcls + '])'
840 // and then contains any number of raw characters, 875 // and then contains any number of raw characters,
841 + '(?:[^/\\x5B\\x5C]' 876 + '(?:[^/\\x5B\\x5C' + regexExcls + ']'
842 // escape sequences (\x5C), 877 // escape sequences (\x5C),
843 + '|\\x5C[\\s\\S]' 878 + '|\\x5C' + regexAny
844 // or non-nesting character sets (\x5B\x5D); 879 // or non-nesting character sets (\x5B\x5D);
845 + '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+' 880 + '|\\x5B(?:[^\\x5C\\x5D' + regexExcls + ']'
881 + '|\\x5C' + regexAny + ')*(?:\\x5D|$))+'
846 // finally closed by a /. 882 // finally closed by a /.
847 + '/'); 883 + '/');
848 fallthroughStylePatterns.push( 884 fallthroughStylePatterns.push(
849 ['lang-regex', 885 ['lang-regex',
850 new RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')') 886 RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
851 ]); 887 ]);
852 } 888 }
853 889
854 var types = options['types']; 890 var types = options['types'];
855 if (types) { 891 if (types) {
863 new RegExp('^(?:' + keywords.replace(/[\s,]+/g, '|') + ')\\b'), 899 new RegExp('^(?:' + keywords.replace(/[\s,]+/g, '|') + ')\\b'),
864 null]); 900 null]);
865 } 901 }
866 902
867 shortcutStylePatterns.push([PR_PLAIN, /^\s+/, null, ' \r\n\t\xA0']); 903 shortcutStylePatterns.push([PR_PLAIN, /^\s+/, null, ' \r\n\t\xA0']);
904
905 var punctuation =
906 // The Bash man page says
907
908 // A word is a sequence of characters considered as a single
909 // unit by GRUB. Words are separated by metacharacters,
910 // which are the following plus space, tab, and newline: { }
911 // | & $ ; < >
912 // ...
913
914 // A word beginning with # causes that word and all remaining
915 // characters on that line to be ignored.
916
917 // which means that only a '#' after /(?:^|[{}|&$;<>\s])/ starts a
918 // comment but empirically
919 // $ echo {#}
920 // {#}
921 // $ echo \$#
922 // $#
923 // $ echo }#
924 // }#
925
926 // so /(?:^|[|&;<>\s])/ is more appropriate.
927
928 // http://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC3
929 // suggests that this definition is compatible with a
930 // default mode that tries to use a single token definition
931 // to recognize both bash/python style comments and C
932 // preprocessor directives.
933
934 // This definition of punctuation does not include # in the list of
935 // follow-on exclusions, so # will not be broken before if preceeded
936 // by a punctuation character. We could try to exclude # after
937 // [|&;<>] but that doesn't seem to cause many major problems.
938 // If that does turn out to be a problem, we should change the below
939 // when hc is truthy to include # in the run of punctuation characters
940 // only when not followint [|&;<>].
941 '^.[^\\s\\w.$@\'"`/\\\\]*';
942 if (options['regexLiterals']) {
943 punctuation += '(?!\s*\/)';
944 }
945
868 fallthroughStylePatterns.push( 946 fallthroughStylePatterns.push(
869 // TODO(mikesamuel): recognize non-latin letters and numerals in idents 947 // TODO(mikesamuel): recognize non-latin letters and numerals in idents
870 [PR_LITERAL, /^@[a-z_$][a-z_$@0-9]*/i, null], 948 [PR_LITERAL, /^@[a-z_$][a-z_$@0-9]*/i, null],
871 [PR_TYPE, /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null], 949 [PR_TYPE, /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null],
872 [PR_PLAIN, /^[a-z_$][a-z_$@0-9]*/i, null], 950 [PR_PLAIN, /^[a-z_$][a-z_$@0-9]*/i, null],
881 + '(?:e[+\\-]?\\d+)?' 959 + '(?:e[+\\-]?\\d+)?'
882 + ')' 960 + ')'
883 // with an optional modifier like UL for unsigned long 961 // with an optional modifier like UL for unsigned long
884 + '[a-z]*', 'i'), 962 + '[a-z]*', 'i'),
885 null, '0123456789'], 963 null, '0123456789'],
886 // Don't treat escaped quotes in bash as starting strings. See issue 144. 964 // Don't treat escaped quotes in bash as starting strings.
965 // See issue 144.
887 [PR_PLAIN, /^\\[\s\S]?/, null], 966 [PR_PLAIN, /^\\[\s\S]?/, null],
888 [PR_PUNCTUATION, /^.[^\s\w\.$@\'\"\`\/\#\\]*/, null]); 967 [PR_PUNCTUATION, new RegExp(punctuation), null]);
889 968
890 return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns); 969 return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
891 } 970 }
892 971
893 var decorateSource = sourceDecorator({ 972 var decorateSource = sourceDecorator({
904 * 983 *
905 * @param {Node} node modified in place. Its content is pulled into an 984 * @param {Node} node modified in place. Its content is pulled into an
906 * HTMLOListElement, and each line is moved into a separate list item. 985 * HTMLOListElement, and each line is moved into a separate list item.
907 * This requires cloning elements, so the input might not have unique 986 * This requires cloning elements, so the input might not have unique
908 * IDs after numbering. 987 * IDs after numbering.
909 */ 988 * @param {boolean} isPreformatted true iff white-space in text nodes should
910 function numberLines(node, opt_startLineNum) { 989 * be treated as significant.
990 */
991 function numberLines(node, opt_startLineNum, isPreformatted) {
911 var nocode = /(?:^|\s)nocode(?:\s|$)/; 992 var nocode = /(?:^|\s)nocode(?:\s|$)/;
912 var lineBreak = /\r\n?|\n/; 993 var lineBreak = /\r\n?|\n/;
913 994
914 var document = node.ownerDocument; 995 var document = node.ownerDocument;
915 996
916 var whitespace; 997 var li = document.createElement('li');
917 if (node.currentStyle) {
918 whitespace = node.currentStyle.whiteSpace;
919 } else if (window.getComputedStyle) {
920 whitespace = document.defaultView.getComputedStyle(node, null)
921 .getPropertyValue('white-space');
922 }
923 // If it's preformatted, then we need to split lines on line breaks
924 // in addition to <BR>s.
925 var isPreformatted = whitespace && 'pre' === whitespace.substring(0, 3);
926
927 var li = document.createElement('LI');
928 while (node.firstChild) { 998 while (node.firstChild) {
929 li.appendChild(node.firstChild); 999 li.appendChild(node.firstChild);
930 } 1000 }
931 // An array of lines. We split below, so this is initialized to one 1001 // An array of lines. We split below, so this is initialized to one
932 // un-split line. 1002 // un-split line.
933 var listItems = [li]; 1003 var listItems = [li];
934 1004
935 function walk(node) { 1005 function walk(node) {
936 switch (node.nodeType) { 1006 var type = node.nodeType;
937 case 1: // Element 1007 if (type == 1 && !nocode.test(node.className)) { // Element
938 if (nocode.test(node.className)) { break; } 1008 if ('br' === node.nodeName) {
939 if ('BR' === node.nodeName) { 1009 breakAfter(node);
940 breakAfter(node); 1010 // Discard the <BR> since it is now flush against a </LI>.
941 // Discard the <BR> since it is now flush against a </LI>. 1011 if (node.parentNode) {
942 if (node.parentNode) { 1012 node.parentNode.removeChild(node);
943 node.parentNode.removeChild(node); 1013 }
944 } 1014 } else {
945 } else { 1015 for (var child = node.firstChild; child; child = child.nextSibling) {
946 for (var child = node.firstChild; child; child = child.nextSibling) { 1016 walk(child);
947 walk(child); 1017 }
948 } 1018 }
949 } 1019 } else if ((type == 3 || type == 4) && isPreformatted) { // Text
950 break; 1020 var text = node.nodeValue;
951 case 3: case 4: // Text 1021 var match = text.match(lineBreak);
952 if (isPreformatted) { 1022 if (match) {
953 var text = node.nodeValue; 1023 var firstLine = text.substring(0, match.index);
954 var match = text.match(lineBreak); 1024 node.nodeValue = firstLine;
955 if (match) { 1025 var tail = text.substring(match.index + match[0].length);
956 var firstLine = text.substring(0, match.index); 1026 if (tail) {
957 node.nodeValue = firstLine; 1027 var parent = node.parentNode;
958 var tail = text.substring(match.index + match[0].length); 1028 parent.insertBefore(
959 if (tail) { 1029 document.createTextNode(tail), node.nextSibling);
960 var parent = node.parentNode; 1030 }
961 parent.insertBefore( 1031 breakAfter(node);
962 document.createTextNode(tail), node.nextSibling); 1032 if (!firstLine) {
963 } 1033 // Don't leave blank text nodes in the DOM.
964 breakAfter(node); 1034 node.parentNode.removeChild(node);
965 if (!firstLine) { 1035 }
966 // Don't leave blank text nodes in the DOM. 1036 }
967 node.parentNode.removeChild(node);
968 }
969 }
970 }
971 break;
972 } 1037 }
973 } 1038 }
974 1039
975 // Split a line after the given node. 1040 // Split a line after the given node.
976 function breakAfter(lineEndNode) { 1041 function breakAfter(lineEndNode) {
1026 // Make sure numeric indices show correctly. 1091 // Make sure numeric indices show correctly.
1027 if (opt_startLineNum === (opt_startLineNum|0)) { 1092 if (opt_startLineNum === (opt_startLineNum|0)) {
1028 listItems[0].setAttribute('value', opt_startLineNum); 1093 listItems[0].setAttribute('value', opt_startLineNum);
1029 } 1094 }
1030 1095
1031 var ol = document.createElement('OL'); 1096 var ol = document.createElement('ol');
1032 ol.className = 'linenums'; 1097 ol.className = 'linenums';
1033 var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0; 1098 var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0;
1034 for (var i = 0, n = listItems.length; i < n; ++i) { 1099 for (var i = 0, n = listItems.length; i < n; ++i) {
1035 li = listItems[i]; 1100 li = listItems[i];
1036 // Stick a class on the LIs so that stylesheets can 1101 // Stick a class on the LIs so that stylesheets can
1043 ol.appendChild(li); 1108 ol.appendChild(li);
1044 } 1109 }
1045 1110
1046 node.appendChild(ol); 1111 node.appendChild(ol);
1047 } 1112 }
1048
1049 /** 1113 /**
1050 * Breaks {@code job.sourceCode} around style boundaries in 1114 * Breaks {@code job.sourceCode} around style boundaries in
1051 * {@code job.decorations} and modifies {@code job.sourceNode} in place. 1115 * {@code job.decorations} and modifies {@code job.sourceNode} in place.
1052 * @param {Object} job like <pre>{ 1116 * @param {Object} job like <pre>{
1053 * sourceCode: {string} source as plain text, 1117 * sourceCode: {string} source as plain text,
1118 * sourceNode: {HTMLElement} the element containing the source,
1054 * spans: {Array.<number|Node>} alternating span start indices into source 1119 * spans: {Array.<number|Node>} alternating span start indices into source
1055 * and the text node or element (e.g. {@code <BR>}) corresponding to that 1120 * and the text node or element (e.g. {@code <BR>}) corresponding to that
1056 * span. 1121 * span.
1057 * decorations: {Array.<number|string} an array of style classes preceded 1122 * decorations: {Array.<number|string} an array of style classes preceded
1058 * by the position at which they start in job.sourceCode in order 1123 * by the position at which they start in job.sourceCode in order
1059 * }</pre> 1124 * }</pre>
1060 * @private 1125 * @private
1061 */ 1126 */
1062 function recombineTagsAndDecorations(job) { 1127 function recombineTagsAndDecorations(job) {
1063 var isIE = /\bMSIE\b/.test(navigator.userAgent); 1128 var isIE8OrEarlier = /\bMSIE\s(\d+)/.exec(navigator.userAgent);
1129 isIE8OrEarlier = isIE8OrEarlier && +isIE8OrEarlier[1] <= 8;
1064 var newlineRe = /\n/g; 1130 var newlineRe = /\n/g;
1065 1131
1066 var source = job.sourceCode; 1132 var source = job.sourceCode;
1067 var sourceLength = source.length; 1133 var sourceLength = source.length;
1068 // Index into source after the last code-unit recombined. 1134 // Index into source after the last code-unit recombined.
1106 i = end; 1172 i = end;
1107 } 1173 }
1108 1174
1109 nDecorations = decorations.length = decPos; 1175 nDecorations = decorations.length = decPos;
1110 1176
1111 var decoration = null; 1177 var sourceNode = job.sourceNode;
1112 while (spanIndex < nSpans) { 1178 var oldDisplay;
1113 var spanStart = spans[spanIndex]; 1179 if (sourceNode) {
1114 var spanEnd = spans[spanIndex + 2] || sourceLength; 1180 oldDisplay = sourceNode.style.display;
1115 1181 sourceNode.style.display = 'none';
1116 var decStart = decorations[decorationIndex]; 1182 }
1117 var decEnd = decorations[decorationIndex + 2] || sourceLength; 1183 try {
1118 1184 var decoration = null;
1119 var end = Math.min(spanEnd, decEnd); 1185 while (spanIndex < nSpans) {
1120 1186 var spanStart = spans[spanIndex];
1121 var textNode = spans[spanIndex + 1]; 1187 var spanEnd = spans[spanIndex + 2] || sourceLength;
1122 var styledText; 1188
1123 if (textNode.nodeType !== 1 // Don't muck with <BR>s or <LI>s 1189 var decEnd = decorations[decorationIndex + 2] || sourceLength;
1124 // Don't introduce spans around empty text nodes. 1190
1125 && (styledText = source.substring(sourceIndex, end))) { 1191 var end = Math.min(spanEnd, decEnd);
1126 // This may seem bizarre, and it is. Emitting LF on IE causes the 1192
1127 // code to display with spaces instead of line breaks. 1193 var textNode = spans[spanIndex + 1];
1128 // Emitting Windows standard issue linebreaks (CRLF) causes a blank 1194 var styledText;
1129 // space to appear at the beginning of every line but the first. 1195 if (textNode.nodeType !== 1 // Don't muck with <BR>s or <LI>s
1130 // Emitting an old Mac OS 9 line separator makes everything spiffy. 1196 // Don't introduce spans around empty text nodes.
1131 if (isIE) { styledText = styledText.replace(newlineRe, '\r'); } 1197 && (styledText = source.substring(sourceIndex, end))) {
1132 textNode.nodeValue = styledText; 1198 // This may seem bizarre, and it is. Emitting LF on IE causes the
1133 var document = textNode.ownerDocument; 1199 // code to display with spaces instead of line breaks.
1134 var span = document.createElement('SPAN'); 1200 // Emitting Windows standard issue linebreaks (CRLF) causes a blank
1135 span.className = decorations[decorationIndex + 1]; 1201 // space to appear at the beginning of every line but the first.
1136 var parentNode = textNode.parentNode; 1202 // Emitting an old Mac OS 9 line separator makes everything spiffy.
1137 parentNode.replaceChild(span, textNode); 1203 if (isIE8OrEarlier) {
1138 span.appendChild(textNode); 1204 styledText = styledText.replace(newlineRe, '\r');
1139 if (sourceIndex < spanEnd) { // Split off a text node. 1205 }
1140 spans[spanIndex + 1] = textNode 1206 textNode.nodeValue = styledText;
1141 // TODO: Possibly optimize by using '' if there's no flicker. 1207 var document = textNode.ownerDocument;
1142 = document.createTextNode(source.substring(end, spanEnd)); 1208 var span = document.createElement('span');
1143 parentNode.insertBefore(textNode, span.nextSibling); 1209 span.className = decorations[decorationIndex + 1];
1144 } 1210 var parentNode = textNode.parentNode;
1145 } 1211 parentNode.replaceChild(span, textNode);
1146 1212 span.appendChild(textNode);
1147 sourceIndex = end; 1213 if (sourceIndex < spanEnd) { // Split off a text node.
1148 1214 spans[spanIndex + 1] = textNode
1149 if (sourceIndex >= spanEnd) { 1215 // TODO: Possibly optimize by using '' if there's no flicker.
1150 spanIndex += 2; 1216 = document.createTextNode(source.substring(end, spanEnd));
1151 } 1217 parentNode.insertBefore(textNode, span.nextSibling);
1152 if (sourceIndex >= decEnd) { 1218 }
1153 decorationIndex += 2; 1219 }
1220
1221 sourceIndex = end;
1222
1223 if (sourceIndex >= spanEnd) {
1224 spanIndex += 2;
1225 }
1226 if (sourceIndex >= decEnd) {
1227 decorationIndex += 2;
1228 }
1229 }
1230 } finally {
1231 if (sourceNode) {
1232 sourceNode.style.display = oldDisplay;
1154 } 1233 }
1155 } 1234 }
1156 } 1235 }
1157
1158 1236
1159 /** Maps language-specific file extensions to handlers. */ 1237 /** Maps language-specific file extensions to handlers. */
1160 var langHandlerRegistry = {}; 1238 var langHandlerRegistry = {};
1161 /** Register a language handler for the given file extensions. 1239 /** Register a language handler for the given file extensions.
1162 * @param {function (Object)} handler a function from source code to a list 1240 * @param {function (Object)} handler a function from source code to a list
1177 function registerLangHandler(handler, fileExtensions) { 1255 function registerLangHandler(handler, fileExtensions) {
1178 for (var i = fileExtensions.length; --i >= 0;) { 1256 for (var i = fileExtensions.length; --i >= 0;) {
1179 var ext = fileExtensions[i]; 1257 var ext = fileExtensions[i];
1180 if (!langHandlerRegistry.hasOwnProperty(ext)) { 1258 if (!langHandlerRegistry.hasOwnProperty(ext)) {
1181 langHandlerRegistry[ext] = handler; 1259 langHandlerRegistry[ext] = handler;
1182 } else if (window['console']) { 1260 } else if (win['console']) {
1183 console['warn']('cannot override language handler %s', ext); 1261 console['warn']('cannot override language handler %s', ext);
1184 } 1262 }
1185 } 1263 }
1186 } 1264 }
1187 function langHandlerForExtension(extension, source) { 1265 function langHandlerForExtension(extension, source) {
1257 }), ['java']); 1335 }), ['java']);
1258 registerLangHandler(sourceDecorator({ 1336 registerLangHandler(sourceDecorator({
1259 'keywords': SH_KEYWORDS, 1337 'keywords': SH_KEYWORDS,
1260 'hashComments': true, 1338 'hashComments': true,
1261 'multiLineStrings': true 1339 'multiLineStrings': true
1262 }), ['bsh', 'csh', 'sh']); 1340 }), ['bash', 'bsh', 'csh', 'sh']);
1263 registerLangHandler(sourceDecorator({ 1341 registerLangHandler(sourceDecorator({
1264 'keywords': PYTHON_KEYWORDS, 1342 'keywords': PYTHON_KEYWORDS,
1265 'hashComments': true, 1343 'hashComments': true,
1266 'multiLineStrings': true, 1344 'multiLineStrings': true,
1267 'tripleQuotedStrings': true 1345 'tripleQuotedStrings': true
1268 }), ['cv', 'py']); 1346 }), ['cv', 'py', 'python']);
1269 registerLangHandler(sourceDecorator({ 1347 registerLangHandler(sourceDecorator({
1270 'keywords': PERL_KEYWORDS, 1348 'keywords': PERL_KEYWORDS,
1271 'hashComments': true, 1349 'hashComments': true,
1272 'multiLineStrings': true, 1350 'multiLineStrings': true,
1273 'regexLiterals': true 1351 'regexLiterals': 2 // multiline regex literals
1274 }), ['perl', 'pl', 'pm']); 1352 }), ['perl', 'pl', 'pm']);
1275 registerLangHandler(sourceDecorator({ 1353 registerLangHandler(sourceDecorator({
1276 'keywords': RUBY_KEYWORDS, 1354 'keywords': RUBY_KEYWORDS,
1277 'hashComments': true, 1355 'hashComments': true,
1278 'multiLineStrings': true, 1356 'multiLineStrings': true,
1279 'regexLiterals': true 1357 'regexLiterals': true
1280 }), ['rb']); 1358 }), ['rb', 'ruby']);
1281 registerLangHandler(sourceDecorator({ 1359 registerLangHandler(sourceDecorator({
1282 'keywords': JSCRIPT_KEYWORDS, 1360 'keywords': JSCRIPT_KEYWORDS,
1283 'cStyleComments': true, 1361 'cStyleComments': true,
1284 'regexLiterals': true 1362 'regexLiterals': true
1285 }), ['js']); 1363 }), ['javascript', 'js']);
1286 registerLangHandler(sourceDecorator({ 1364 registerLangHandler(sourceDecorator({
1287 'keywords': COFFEE_KEYWORDS, 1365 'keywords': COFFEE_KEYWORDS,
1288 'hashComments': 3, // ### style block comments 1366 'hashComments': 3, // ### style block comments
1289 'cStyleComments': true, 1367 'cStyleComments': true,
1290 'multilineStrings': true, 1368 'multilineStrings': true,
1291 'tripleQuotedStrings': true, 1369 'tripleQuotedStrings': true,
1292 'regexLiterals': true 1370 'regexLiterals': true
1293 }), ['coffee']); 1371 }), ['coffee']);
1294 registerLangHandler(createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']); 1372 registerLangHandler(sourceDecorator({
1373 'keywords': RUST_KEYWORDS,
1374 'cStyleComments': true,
1375 'multilineStrings': true
1376 }), ['rc', 'rs', 'rust']);
1377 registerLangHandler(
1378 createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
1295 1379
1296 function applyDecorator(job) { 1380 function applyDecorator(job) {
1297 var opt_langExtension = job.langExtension; 1381 var opt_langExtension = job.langExtension;
1298 1382
1299 try { 1383 try {
1300 // Extract tags, and convert the source code to plain text. 1384 // Extract tags, and convert the source code to plain text.
1301 var sourceAndSpans = extractSourceSpans(job.sourceNode); 1385 var sourceAndSpans = extractSourceSpans(job.sourceNode, job.pre);
1302 /** Plain text. @type {string} */ 1386 /** Plain text. @type {string} */
1303 var source = sourceAndSpans.sourceCode; 1387 var source = sourceAndSpans.sourceCode;
1304 job.sourceCode = source; 1388 job.sourceCode = source;
1305 job.spans = sourceAndSpans.spans; 1389 job.spans = sourceAndSpans.spans;
1306 job.basePos = 0; 1390 job.basePos = 0;
1310 1394
1311 // Integrate the decorations and tags back into the source code, 1395 // Integrate the decorations and tags back into the source code,
1312 // modifying the sourceNode in place. 1396 // modifying the sourceNode in place.
1313 recombineTagsAndDecorations(job); 1397 recombineTagsAndDecorations(job);
1314 } catch (e) { 1398 } catch (e) {
1315 if ('console' in window) { 1399 if (win['console']) {
1316 console['log'](e && e['stack'] ? e['stack'] : e); 1400 console['log'](e && e['stack'] || e);
1317 } 1401 }
1318 } 1402 }
1319 } 1403 }
1320 1404
1321 /** 1405 /**
1406 * Pretty print a chunk of code.
1322 * @param sourceCodeHtml {string} The HTML to pretty print. 1407 * @param sourceCodeHtml {string} The HTML to pretty print.
1323 * @param opt_langExtension {string} The language name to use. 1408 * @param opt_langExtension {string} The language name to use.
1324 * Typically, a filename extension like 'cpp' or 'java'. 1409 * Typically, a filename extension like 'cpp' or 'java'.
1325 * @param opt_numberLines {number|boolean} True to number lines, 1410 * @param opt_numberLines {number|boolean} True to number lines,
1326 * or the 1-indexed number of the first line in sourceCodeHtml. 1411 * or the 1-indexed number of the first line in sourceCodeHtml.
1327 */ 1412 */
1328 function prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) { 1413 function $prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) {
1329 var container = document.createElement('PRE'); 1414 var container = document.createElement('div');
1330 // This could cause images to load and onload listeners to fire. 1415 // This could cause images to load and onload listeners to fire.
1331 // E.g. <img onerror="alert(1337)" src="nosuchimage.png">. 1416 // E.g. <img onerror="alert(1337)" src="nosuchimage.png">.
1332 // We assume that the inner HTML is from a trusted source. 1417 // We assume that the inner HTML is from a trusted source.
1333 container.innerHTML = sourceCodeHtml; 1418 // The pre-tag is required for IE8 which strips newlines from innerHTML
1419 // when it is injected into a <pre> tag.
1420 // http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie
1421 // http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript
1422 container.innerHTML = '<pre>' + sourceCodeHtml + '</pre>';
1423 container = container.firstChild;
1334 if (opt_numberLines) { 1424 if (opt_numberLines) {
1335 numberLines(container, opt_numberLines); 1425 numberLines(container, opt_numberLines, true);
1336 } 1426 }
1337 1427
1338 var job = { 1428 var job = {
1339 langExtension: opt_langExtension, 1429 langExtension: opt_langExtension,
1340 numberLines: opt_numberLines, 1430 numberLines: opt_numberLines,
1341 sourceNode: container 1431 sourceNode: container,
1432 pre: 1
1342 }; 1433 };
1343 applyDecorator(job); 1434 applyDecorator(job);
1344 return container.innerHTML; 1435 return container.innerHTML;
1345 } 1436 }
1346 1437
1347 function prettyPrint(opt_whenDone) { 1438 /**
1348 function byTagName(tn) { return document.getElementsByTagName(tn); } 1439 * Find all the {@code <pre>} and {@code <code>} tags in the DOM with
1440 * {@code class=prettyprint} and prettify them.
1441 *
1442 * @param {Function} opt_whenDone called when prettifying is done.
1443 * @param {HTMLElement|HTMLDocument} opt_root an element or document
1444 * containing all the elements to pretty print.
1445 * Defaults to {@code document.body}.
1446 */
1447 function $prettyPrint(opt_whenDone, opt_root) {
1448 var root = opt_root || document.body;
1449 var doc = root.ownerDocument || document;
1450 function byTagName(tn) { return root.getElementsByTagName(tn); }
1349 // fetch a list of nodes to rewrite 1451 // fetch a list of nodes to rewrite
1350 var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')]; 1452 var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];
1351 var elements = []; 1453 var elements = [];
1352 for (var i = 0; i < codeSegments.length; ++i) { 1454 for (var i = 0; i < codeSegments.length; ++i) {
1353 for (var j = 0, n = codeSegments[i].length; j < n; ++j) { 1455 for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
1366 var k = 0; 1468 var k = 0;
1367 var prettyPrintingJob; 1469 var prettyPrintingJob;
1368 1470
1369 var langExtensionRe = /\blang(?:uage)?-([\w.]+)(?!\S)/; 1471 var langExtensionRe = /\blang(?:uage)?-([\w.]+)(?!\S)/;
1370 var prettyPrintRe = /\bprettyprint\b/; 1472 var prettyPrintRe = /\bprettyprint\b/;
1473 var prettyPrintedRe = /\bprettyprinted\b/;
1474 var preformattedTagNameRe = /pre|xmp/i;
1475 var codeRe = /^code$/i;
1476 var preCodeXmpRe = /^(?:pre|code|xmp)$/i;
1477 var EMPTY = {};
1371 1478
1372 function doWork() { 1479 function doWork() {
1373 var endTime = (window['PR_SHOULD_USE_CONTINUATION'] ? 1480 var endTime = (win['PR_SHOULD_USE_CONTINUATION'] ?
1374 clock['now']() + 250 /* ms */ : 1481 clock['now']() + 250 /* ms */ :
1375 Infinity); 1482 Infinity);
1376 for (; k < elements.length && clock['now']() < endTime; k++) { 1483 for (; k < elements.length && clock['now']() < endTime; k++) {
1377 var cs = elements[k]; 1484 var cs = elements[k];
1485
1486 // Look for a preceding comment like
1487 // <?prettify lang="..." linenums="..."?>
1488 var attrs = EMPTY;
1489 {
1490 for (var preceder = cs; (preceder = preceder.previousSibling);) {
1491 var nt = preceder.nodeType;
1492 // <?foo?> is parsed by HTML 5 to a comment node (8)
1493 // like <!--?foo?-->, but in XML is a processing instruction
1494 var value = (nt === 7 || nt === 8) && preceder.nodeValue;
1495 if (value
1496 ? !/^\??prettify\b/.test(value)
1497 : (nt !== 3 || /\S/.test(preceder.nodeValue))) {
1498 // Skip over white-space text nodes but not others.
1499 break;
1500 }
1501 if (value) {
1502 attrs = {};
1503 value.replace(
1504 /\b(\w+)=([\w:.%+-]+)/g,
1505 function (_, name, value) { attrs[name] = value; });
1506 break;
1507 }
1508 }
1509 }
1510
1378 var className = cs.className; 1511 var className = cs.className;
1379 if (className.indexOf('prettyprint') >= 0) { 1512 if ((attrs !== EMPTY || prettyPrintRe.test(className))
1380 // If the classes includes a language extensions, use it. 1513 // Don't redo this if we've already done it.
1381 // Language extensions can be specified like 1514 // This allows recalling pretty print to just prettyprint elements
1382 // <pre class="prettyprint lang-cpp"> 1515 // that have been added to the page since last call.
1383 // the language extension "cpp" is used to find a language handler as 1516 && !prettyPrintedRe.test(className)) {
1384 // passed to PR.registerLangHandler.
1385 // HTML5 recommends that a language be specified using "language-"
1386 // as the prefix instead. Google Code Prettify supports both.
1387 // http://dev.w3.org/html5/spec-author-view/the-code-element.html
1388 var langExtension = className.match(langExtensionRe);
1389 // Support <pre class="prettyprint"><code class="language-c">
1390 var wrapper;
1391 if (!langExtension && (wrapper = childContentWrapper(cs))
1392 && "CODE" === wrapper.tagName) {
1393 langExtension = wrapper.className.match(langExtensionRe);
1394 }
1395
1396 if (langExtension) {
1397 langExtension = langExtension[1];
1398 }
1399 1517
1400 // make sure this is not nested in an already prettified element 1518 // make sure this is not nested in an already prettified element
1401 var nested = false; 1519 var nested = false;
1402 for (var p = cs.parentNode; p; p = p.parentNode) { 1520 for (var p = cs.parentNode; p; p = p.parentNode) {
1403 if ((p.tagName === 'pre' || p.tagName === 'code' || 1521 var tn = p.tagName;
1404 p.tagName === 'xmp') && 1522 if (preCodeXmpRe.test(tn)
1405 p.className && p.className.indexOf('prettyprint') >= 0) { 1523 && p.className && prettyPrintRe.test(p.className)) {
1406 nested = true; 1524 nested = true;
1407 break; 1525 break;
1408 } 1526 }
1409 } 1527 }
1410 if (!nested) { 1528 if (!nested) {
1529 // Mark done. If we fail to prettyprint for whatever reason,
1530 // we shouldn't try again.
1531 cs.className += ' prettyprinted';
1532
1533 // If the classes includes a language extensions, use it.
1534 // Language extensions can be specified like
1535 // <pre class="prettyprint lang-cpp">
1536 // the language extension "cpp" is used to find a language handler
1537 // as passed to PR.registerLangHandler.
1538 // HTML5 recommends that a language be specified using "language-"
1539 // as the prefix instead. Google Code Prettify supports both.
1540 // http://dev.w3.org/html5/spec-author-view/the-code-element.html
1541 var langExtension = attrs['lang'];
1542 if (!langExtension) {
1543 langExtension = className.match(langExtensionRe);
1544 // Support <pre class="prettyprint"><code class="language-c">
1545 var wrapper;
1546 if (!langExtension && (wrapper = childContentWrapper(cs))
1547 && codeRe.test(wrapper.tagName)) {
1548 langExtension = wrapper.className.match(langExtensionRe);
1549 }
1550
1551 if (langExtension) { langExtension = langExtension[1]; }
1552 }
1553
1554 var preformatted;
1555 if (preformattedTagNameRe.test(cs.tagName)) {
1556 preformatted = 1;
1557 } else {
1558 var currentStyle = cs['currentStyle'];
1559 var defaultView = doc.defaultView;
1560 var whitespace = (
1561 currentStyle
1562 ? currentStyle['whiteSpace']
1563 : (defaultView
1564 && defaultView.getComputedStyle)
1565 ? defaultView.getComputedStyle(cs, null)
1566 .getPropertyValue('white-space')
1567 : 0);
1568 preformatted = whitespace
1569 && 'pre' === whitespace.substring(0, 3);
1570 }
1571
1411 // Look for a class like linenums or linenums:<n> where <n> is the 1572 // Look for a class like linenums or linenums:<n> where <n> is the
1412 // 1-indexed number of the first line. 1573 // 1-indexed number of the first line.
1413 var lineNums = cs.className.match(/\blinenums\b(?::(\d+))?/); 1574 var lineNums = attrs['linenums'];
1414 lineNums = lineNums 1575 if (!(lineNums = lineNums === 'true' || +lineNums)) {
1415 ? lineNums[1] && lineNums[1].length ? +lineNums[1] : true 1576 lineNums = className.match(/\blinenums\b(?::(\d+))?/);
1416 : false; 1577 lineNums =
1417 if (lineNums) { numberLines(cs, lineNums); } 1578 lineNums
1579 ? lineNums[1] && lineNums[1].length
1580 ? +lineNums[1] : true
1581 : false;
1582 }
1583 if (lineNums) { numberLines(cs, lineNums, preformatted); }
1418 1584
1419 // do the pretty printing 1585 // do the pretty printing
1420 prettyPrintingJob = { 1586 prettyPrintingJob = {
1421 langExtension: langExtension, 1587 langExtension: langExtension,
1422 sourceNode: cs, 1588 sourceNode: cs,
1423 numberLines: lineNums 1589 numberLines: lineNums,
1590 pre: preformatted
1424 }; 1591 };
1425 applyDecorator(prettyPrintingJob); 1592 applyDecorator(prettyPrintingJob);
1426 } 1593 }
1427 } 1594 }
1428 } 1595 }
1429 if (k < elements.length) { 1596 if (k < elements.length) {
1430 // finish up in a continuation 1597 // finish up in a continuation
1431 setTimeout(doWork, 250); 1598 setTimeout(doWork, 250);
1432 } else if (opt_whenDone) { 1599 } else if ('function' === typeof opt_whenDone) {
1433 opt_whenDone(); 1600 opt_whenDone();
1434 } 1601 }
1435 } 1602 }
1436 1603
1437 doWork(); 1604 doWork();
1438 } 1605 }
1439 1606
1440 /** 1607 /**
1441 * Find all the {@code <pre>} and {@code <code>} tags in the DOM with 1608 * Contains functions for creating and registering new language handlers.
1442 * {@code class=prettyprint} and prettify them. 1609 * @type {Object}
1443 * 1610 */
1444 * @param {Function?} opt_whenDone if specified, called when the last entry 1611 var PR = win['PR'] = {
1445 * has been finished.
1446 */
1447 window['prettyPrintOne'] = prettyPrintOne;
1448 /**
1449 * Pretty print a chunk of code.
1450 *
1451 * @param {string} sourceCodeHtml code as html
1452 * @return {string} code as html, but prettier
1453 */
1454 window['prettyPrint'] = prettyPrint;
1455 /**
1456 * Contains functions for creating and registering new language handlers.
1457 * @type {Object}
1458 */
1459 window['PR'] = {
1460 'createSimpleLexer': createSimpleLexer, 1612 'createSimpleLexer': createSimpleLexer,
1461 'registerLangHandler': registerLangHandler, 1613 'registerLangHandler': registerLangHandler,
1462 'sourceDecorator': sourceDecorator, 1614 'sourceDecorator': sourceDecorator,
1463 'PR_ATTRIB_NAME': PR_ATTRIB_NAME, 1615 'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
1464 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE, 1616 'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
1470 'PR_PLAIN': PR_PLAIN, 1622 'PR_PLAIN': PR_PLAIN,
1471 'PR_PUNCTUATION': PR_PUNCTUATION, 1623 'PR_PUNCTUATION': PR_PUNCTUATION,
1472 'PR_SOURCE': PR_SOURCE, 1624 'PR_SOURCE': PR_SOURCE,
1473 'PR_STRING': PR_STRING, 1625 'PR_STRING': PR_STRING,
1474 'PR_TAG': PR_TAG, 1626 'PR_TAG': PR_TAG,
1475 'PR_TYPE': PR_TYPE 1627 'PR_TYPE': PR_TYPE,
1628 'prettyPrintOne':
1629 IN_GLOBAL_SCOPE
1630 ? (win['prettyPrintOne'] = $prettyPrintOne)
1631 : (prettyPrintOne = $prettyPrintOne),
1632 'prettyPrint': prettyPrint =
1633 IN_GLOBAL_SCOPE
1634 ? (win['prettyPrint'] = $prettyPrint)
1635 : (prettyPrint = $prettyPrint)
1476 }; 1636 };
1637
1638 // Make PR available via the Asynchronous Module Definition (AMD) API.
1639 // Per https://github.com/amdjs/amdjs-api/wiki/AMD:
1640 // The Asynchronous Module Definition (AMD) API specifies a
1641 // mechanism for defining modules such that the module and its
1642 // dependencies can be asynchronously loaded.
1643 // ...
1644 // To allow a clear indicator that a global define function (as
1645 // needed for script src browser loading) conforms to the AMD API,
1646 // any global define function SHOULD have a property called "amd"
1647 // whose value is an object. This helps avoid conflict with any
1648 // other existing JavaScript code that could have defined a define()
1649 // function that does not conform to the AMD API.
1650 if (typeof define === "function" && define['amd']) {
1651 define("google-code-prettify", [], function () {
1652 return PR;
1653 });
1654 }
1477 })(); 1655 })();