UNPKG

13.4 kBJavaScriptView Raw
1var _self = (typeof window !== 'undefined')
2 ? window // if in browser
3 : (
4 (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
5 ? self // if in worker
6 : {} // if in node js
7 );
8
9/**
10 * Prism: Lightweight, robust, elegant syntax highlighting
11 * MIT license http://www.opensource.org/licenses/mit-license.php/
12 * @author Lea Verou http://lea.verou.me
13 */
14
15var Prism = (function(){
16
17// Private helper vars
18var lang = /\blang(?:uage)?-([\w-]+)\b/i;
19var uniqueId = 0;
20
21var _ = _self.Prism = {
22 manual: _self.Prism && _self.Prism.manual,
23 disableWorkerMessageHandler: _self.Prism && _self.Prism.disableWorkerMessageHandler,
24 util: {
25 encode: function (tokens) {
26 if (tokens instanceof Token) {
27 return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias);
28 } else if (_.util.type(tokens) === 'Array') {
29 return tokens.map(_.util.encode);
30 } else {
31 return tokens.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
32 }
33 },
34
35 type: function (o) {
36 return Object.prototype.toString.call(o).match(/\[object (\w+)\]/)[1];
37 },
38
39 objId: function (obj) {
40 if (!obj['__id']) {
41 Object.defineProperty(obj, '__id', { value: ++uniqueId });
42 }
43 return obj['__id'];
44 },
45
46 // Deep clone a language definition (e.g. to extend it)
47 clone: function (o, visited) {
48 var type = _.util.type(o);
49 visited = visited || {};
50
51 switch (type) {
52 case 'Object':
53 if (visited[_.util.objId(o)]) {
54 return visited[_.util.objId(o)];
55 }
56 var clone = {};
57 visited[_.util.objId(o)] = clone;
58
59 for (var key in o) {
60 if (o.hasOwnProperty(key)) {
61 clone[key] = _.util.clone(o[key], visited);
62 }
63 }
64
65 return clone;
66
67 case 'Array':
68 if (visited[_.util.objId(o)]) {
69 return visited[_.util.objId(o)];
70 }
71 var clone = [];
72 visited[_.util.objId(o)] = clone;
73
74 o.forEach(function (v, i) {
75 clone[i] = _.util.clone(v, visited);
76 });
77
78 return clone;
79 }
80
81 return o;
82 }
83 },
84
85 languages: {
86 extend: function (id, redef) {
87 var lang = _.util.clone(_.languages[id]);
88
89 for (var key in redef) {
90 lang[key] = redef[key];
91 }
92
93 return lang;
94 },
95
96 /**
97 * Insert a token before another token in a language literal
98 * As this needs to recreate the object (we cannot actually insert before keys in object literals),
99 * we cannot just provide an object, we need anobject and a key.
100 * @param inside The key (or language id) of the parent
101 * @param before The key to insert before. If not provided, the function appends instead.
102 * @param insert Object with the key/value pairs to insert
103 * @param root The object that contains `inside`. If equal to Prism.languages, it can be omitted.
104 */
105 insertBefore: function (inside, before, insert, root) {
106 root = root || _.languages;
107 var grammar = root[inside];
108
109 if (arguments.length == 2) {
110 insert = arguments[1];
111
112 for (var newToken in insert) {
113 if (insert.hasOwnProperty(newToken)) {
114 grammar[newToken] = insert[newToken];
115 }
116 }
117
118 return grammar;
119 }
120
121 var ret = {};
122
123 for (var token in grammar) {
124
125 if (grammar.hasOwnProperty(token)) {
126
127 if (token == before) {
128
129 for (var newToken in insert) {
130
131 if (insert.hasOwnProperty(newToken)) {
132 ret[newToken] = insert[newToken];
133 }
134 }
135 }
136
137 ret[token] = grammar[token];
138 }
139 }
140
141 // Update references in other language definitions
142 _.languages.DFS(_.languages, function(key, value) {
143 if (value === root[inside] && key != inside) {
144 this[key] = ret;
145 }
146 });
147
148 return root[inside] = ret;
149 },
150
151 // Traverse a language definition with Depth First Search
152 DFS: function(o, callback, type, visited) {
153 visited = visited || {};
154 for (var i in o) {
155 if (o.hasOwnProperty(i)) {
156 callback.call(o, i, o[i], type || i);
157
158 if (_.util.type(o[i]) === 'Object' && !visited[_.util.objId(o[i])]) {
159 visited[_.util.objId(o[i])] = true;
160 _.languages.DFS(o[i], callback, null, visited);
161 }
162 else if (_.util.type(o[i]) === 'Array' && !visited[_.util.objId(o[i])]) {
163 visited[_.util.objId(o[i])] = true;
164 _.languages.DFS(o[i], callback, i, visited);
165 }
166 }
167 }
168 }
169 },
170 plugins: {},
171
172 highlightAll: function(async, callback) {
173 _.highlightAllUnder(document, async, callback);
174 },
175
176 highlightAllUnder: function(container, async, callback) {
177 var env = {
178 callback: callback,
179 selector: 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'
180 };
181
182 _.hooks.run("before-highlightall", env);
183
184 var elements = env.elements || container.querySelectorAll(env.selector);
185
186 for (var i=0, element; element = elements[i++];) {
187 _.highlightElement(element, async === true, env.callback);
188 }
189 },
190
191 highlightElement: function(element, async, callback) {
192 // Find language
193 var language, grammar, parent = element;
194
195 while (parent && !lang.test(parent.className)) {
196 parent = parent.parentNode;
197 }
198
199 if (parent) {
200 language = (parent.className.match(lang) || [,''])[1].toLowerCase();
201 grammar = _.languages[language];
202 }
203
204 // Set language on the element, if not present
205 element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
206
207 if (element.parentNode) {
208 // Set language on the parent, for styling
209 parent = element.parentNode;
210
211 if (/pre/i.test(parent.nodeName)) {
212 parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
213 }
214 }
215
216 var code = element.textContent;
217
218 var env = {
219 element: element,
220 language: language,
221 grammar: grammar,
222 code: code
223 };
224
225 _.hooks.run('before-sanity-check', env);
226
227 if (!env.code || !env.grammar) {
228 if (env.code) {
229 _.hooks.run('before-highlight', env);
230 env.element.textContent = env.code;
231 _.hooks.run('after-highlight', env);
232 }
233 _.hooks.run('complete', env);
234 return;
235 }
236
237 _.hooks.run('before-highlight', env);
238
239 if (async && _self.Worker) {
240 var worker = new Worker(_.filename);
241
242 worker.onmessage = function(evt) {
243 env.highlightedCode = evt.data;
244
245 _.hooks.run('before-insert', env);
246
247 env.element.innerHTML = env.highlightedCode;
248
249 callback && callback.call(env.element);
250 _.hooks.run('after-highlight', env);
251 _.hooks.run('complete', env);
252 };
253
254 worker.postMessage(JSON.stringify({
255 language: env.language,
256 code: env.code,
257 immediateClose: true
258 }));
259 }
260 else {
261 env.highlightedCode = _.highlight(env.code, env.grammar, env.language);
262
263 _.hooks.run('before-insert', env);
264
265 env.element.innerHTML = env.highlightedCode;
266
267 callback && callback.call(element);
268
269 _.hooks.run('after-highlight', env);
270 _.hooks.run('complete', env);
271 }
272 },
273
274 highlight: function (text, grammar, language) {
275 var env = {
276 code: text,
277 grammar: grammar,
278 language: language
279 };
280 _.hooks.run('before-tokenize', env);
281 env.tokens = _.tokenize(env.code, env.grammar);
282 _.hooks.run('after-tokenize', env);
283 return Token.stringify(_.util.encode(env.tokens), env.language);
284 },
285
286 matchGrammar: function (text, strarr, grammar, index, startPos, oneshot, target) {
287 var Token = _.Token;
288
289 for (var token in grammar) {
290 if(!grammar.hasOwnProperty(token) || !grammar[token]) {
291 continue;
292 }
293
294 if (token == target) {
295 return;
296 }
297
298 var patterns = grammar[token];
299 patterns = (_.util.type(patterns) === "Array") ? patterns : [patterns];
300
301 for (var j = 0; j < patterns.length; ++j) {
302 var pattern = patterns[j],
303 inside = pattern.inside,
304 lookbehind = !!pattern.lookbehind,
305 greedy = !!pattern.greedy,
306 lookbehindLength = 0,
307 alias = pattern.alias;
308
309 if (greedy && !pattern.pattern.global) {
310 // Without the global flag, lastIndex won't work
311 var flags = pattern.pattern.toString().match(/[imuy]*$/)[0];
312 pattern.pattern = RegExp(pattern.pattern.source, flags + "g");
313 }
314
315 pattern = pattern.pattern || pattern;
316
317 // Don’t cache length as it changes during the loop
318 for (var i = index, pos = startPos; i < strarr.length; pos += strarr[i].length, ++i) {
319
320 var str = strarr[i];
321
322 if (strarr.length > text.length) {
323 // Something went terribly wrong, ABORT, ABORT!
324 return;
325 }
326
327 if (str instanceof Token) {
328 continue;
329 }
330
331 if (greedy && i != strarr.length - 1) {
332 pattern.lastIndex = pos;
333 var match = pattern.exec(text);
334 if (!match) {
335 break;
336 }
337
338 var from = match.index + (lookbehind ? match[1].length : 0),
339 to = match.index + match[0].length,
340 k = i,
341 p = pos;
342
343 for (var len = strarr.length; k < len && (p < to || (!strarr[k].type && !strarr[k - 1].greedy)); ++k) {
344 p += strarr[k].length;
345 // Move the index i to the element in strarr that is closest to from
346 if (from >= p) {
347 ++i;
348 pos = p;
349 }
350 }
351
352 // If strarr[i] is a Token, then the match starts inside another Token, which is invalid
353 if (strarr[i] instanceof Token) {
354 continue;
355 }
356
357 // Number of tokens to delete and replace with the new match
358 delNum = k - i;
359 str = text.slice(pos, p);
360 match.index -= pos;
361 } else {
362 pattern.lastIndex = 0;
363
364 var match = pattern.exec(str),
365 delNum = 1;
366 }
367
368 if (!match) {
369 if (oneshot) {
370 break;
371 }
372
373 continue;
374 }
375
376 if(lookbehind) {
377 lookbehindLength = match[1] ? match[1].length : 0;
378 }
379
380 var from = match.index + lookbehindLength,
381 match = match[0].slice(lookbehindLength),
382 to = from + match.length,
383 before = str.slice(0, from),
384 after = str.slice(to);
385
386 var args = [i, delNum];
387
388 if (before) {
389 ++i;
390 pos += before.length;
391 args.push(before);
392 }
393
394 var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias, match, greedy);
395
396 args.push(wrapped);
397
398 if (after) {
399 args.push(after);
400 }
401
402 Array.prototype.splice.apply(strarr, args);
403
404 if (delNum != 1)
405 _.matchGrammar(text, strarr, grammar, i, pos, true, token);
406
407 if (oneshot)
408 break;
409 }
410 }
411 }
412 },
413
414 tokenize: function(text, grammar, language) {
415 var strarr = [text];
416
417 var rest = grammar.rest;
418
419 if (rest) {
420 for (var token in rest) {
421 grammar[token] = rest[token];
422 }
423
424 delete grammar.rest;
425 }
426
427 _.matchGrammar(text, strarr, grammar, 0, 0, false);
428
429 return strarr;
430 },
431
432 hooks: {
433 all: {},
434
435 add: function (name, callback) {
436 var hooks = _.hooks.all;
437
438 hooks[name] = hooks[name] || [];
439
440 hooks[name].push(callback);
441 },
442
443 run: function (name, env) {
444 var callbacks = _.hooks.all[name];
445
446 if (!callbacks || !callbacks.length) {
447 return;
448 }
449
450 for (var i=0, callback; callback = callbacks[i++];) {
451 callback(env);
452 }
453 }
454 }
455};
456
457var Token = _.Token = function(type, content, alias, matchedStr, greedy) {
458 this.type = type;
459 this.content = content;
460 this.alias = alias;
461 // Copy of the full string this token was created from
462 this.length = (matchedStr || "").length|0;
463 this.greedy = !!greedy;
464};
465
466Token.stringify = function(o, language, parent) {
467 if (typeof o == 'string') {
468 return o;
469 }
470
471 if (_.util.type(o) === 'Array') {
472 return o.map(function(element) {
473 return Token.stringify(element, language, o);
474 }).join('');
475 }
476
477 var env = {
478 type: o.type,
479 content: Token.stringify(o.content, language, parent),
480 tag: 'span',
481 classes: ['token', o.type],
482 attributes: {},
483 language: language,
484 parent: parent
485 };
486
487 if (o.alias) {
488 var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias];
489 Array.prototype.push.apply(env.classes, aliases);
490 }
491
492 _.hooks.run('wrap', env);
493
494 var attributes = Object.keys(env.attributes).map(function(name) {
495 return name + '="' + (env.attributes[name] || '').replace(/"/g, '&quot;') + '"';
496 }).join(' ');
497
498 return '<' + env.tag + ' class="' + env.classes.join(' ') + '"' + (attributes ? ' ' + attributes : '') + '>' + env.content + '</' + env.tag + '>';
499
500};
501
502if (!_self.document) {
503 if (!_self.addEventListener) {
504 // in Node.js
505 return _self.Prism;
506 }
507
508 if (!_.disableWorkerMessageHandler) {
509 // In worker
510 _self.addEventListener('message', function (evt) {
511 var message = JSON.parse(evt.data),
512 lang = message.language,
513 code = message.code,
514 immediateClose = message.immediateClose;
515
516 _self.postMessage(_.highlight(code, _.languages[lang], lang));
517 if (immediateClose) {
518 _self.close();
519 }
520 }, false);
521 }
522
523 return _self.Prism;
524}
525
526//Get current script and highlight
527var script = document.currentScript || [].slice.call(document.getElementsByTagName("script")).pop();
528
529if (script) {
530 _.filename = script.src;
531
532 if (!_.manual && !script.hasAttribute('data-manual')) {
533 if(document.readyState !== "loading") {
534 if (window.requestAnimationFrame) {
535 window.requestAnimationFrame(_.highlightAll);
536 } else {
537 window.setTimeout(_.highlightAll, 16);
538 }
539 }
540 else {
541 document.addEventListener('DOMContentLoaded', _.highlightAll);
542 }
543 }
544}
545
546return _self.Prism;
547
548})();
549
550if (typeof module !== 'undefined' && module.exports) {
551 module.exports = Prism;
552}
553
554// hack for components to work correctly in node.js
555if (typeof global !== 'undefined') {
556 global.Prism = Prism;
557}