'use strict'; /*@ Ox.SyntaxHighlighter Syntax Highlighter options Options file JavaScript file (alternative to `source` option) lineLength If larger than zero, show edge of page offset First line number replace <[[]]|[]> Array of replacements Each array element is an array of two arguments to be passed to the replace function, like [str, str], [regexp, str] or [regexp, fn] showLinebreaks If true, show linebreaks showLineNumbers If true, show line numbers showWhitespace If true, show whitespace showTabs If true, show tabs source JavaScript source, or array of tokens stripComments If true, strip comments tabSize Number of spaces per tab self Shared private variable ([options[, self]]) -> Syntax Highlighter @*/ Ox.SyntaxHighlighter = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ file: '', lineLength: 0, offset: 1, replace: [], showLinebreaks: false, showLineNumbers: false, showTabs: false, showWhitespace: false, source: '', stripComments: false, tabSize: 4 }) .options(options || {}) .update(renderSource) .addClass('OxSyntaxHighlighter'); if (self.options.file) { Ox.get(self.options.file, function(source) { self.options.source = source; renderSource(); }); } else { renderSource(); } function renderSource() { var $lineNumbers, $line, $source, width, lines, source = '', tokens, linebreak = ( self.options.showLinebreaks ? '\u21A9' : '' ) + '
', tab = ( self.options.showTabs ? '\u2192' : '' ) + Ox.repeat(' ', self.options.tabSize - self.options.showTabs), whitespace = self.options.showWhitespace ? '\u00B7' : ' '; tokens = Ox.isArray(self.options.source) ? self.options.source : Ox.tokenize(self.options.source); tokens.forEach(function(token, i) { var classNames, type = token.type == 'identifier' ? Ox.identify(token.value) : token.type; if ( !(self.options.stripComments && type == 'comment') ) { classNames = 'Ox' + Ox.toTitleCase(type); if (self.options.showWhitespace && type == 'whitespace') { if (isAfterLinebreak() && hasIrregularSpaces()) { classNames += ' OxLeading'; } else if (isBeforeLinebreak()) { classNames += ' OxTrailing'; } } source += '' + Ox.encodeHTMLEntities(token.value) .replace(/ /g, whitespace) .replace(/\t/g, tab) .replace(/\n/g, linebreak) + ''; } function isAfterLinebreak() { return i == 0 || tokens[i - 1].type == 'linebreak'; } function isBeforeLinebreak() { return i == tokens.length - 1 || tokens[i + 1].type == 'linebreak'; } function hasIrregularSpaces() { return token.value.split('').reduce(function(prev, curr) { return prev + (curr == ' ' ? 1 : 0); }, 0) % self.options.tabSize; } }); lines = source.split('
'); that.empty(); if (self.options.showLineNumbers) { $lineNumbers = Ox.Element() .addClass('OxLineNumbers') .html( Ox.range(lines.length).map(function(line) { return (line + self.options.offset); }).join('
') ) .appendTo(that); } self.options.replace.forEach(function(replace) { source = source.replace(replace[0], replace[1]) }); $source = Ox.Element() .addClass('OxSourceCode OxSelectable') .html(source) .appendTo(that); if (self.options.lineLength) { $line = Ox.Element() .css({ position: 'absolute', top: '-1000px' }) .html(Ox.repeat(' ', self.options.lineLength)) .appendTo(that); width = $line.width() + 4; // add padding $line.remove(); ['moz', 'webkit'].forEach(function(browser) { $source.css({ background: '-' + browser + '-linear-gradient(left, rgb(255, 255, 255) ' + width + 'px, rgb(192, 192, 192) ' + width + 'px, rgb(255, 255, 255) ' + (width + 1) + 'px)' }); }); } } return that; };