// vim: et:ts=4:sw=4:sts=4:ft=javascript 'use strict'; /*@ Ox.SyntaxHighlighter Syntax Highlighter (options[, self]) -> Syntax Highlighter options Options 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 stripComments If true, strip comments tabSize Number of spaces per tab self Shared private variable @*/ Ox.SyntaxHighlighter = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ lineLength: 0, offset: 1, replace: [], showLinebreaks: false, showLineNumbers: false, showTabs: false, showWhitespace: false, source: '', stripComments: false, tabSize: 4, }) .options(options || {}) .addClass('OxSyntaxHighlighter'); 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' : ' '; self.options.source = self.options.source .replace(/\r\n/g, '\n') .replace(/\r/g, '\n'); tokens = Ox.tokenize(self.options.source); tokens.forEach(function(token, i) { var classNames, substr = self.options.source.substr(token.offset, token.length); if ( !(self.options.stripComments && token.type == 'comment') ) { classNames = 'Ox' + Ox.toTitleCase(token.type); if (self.options.showWhitespace && token.type == 'whitespace') { if (isAfterLinebreak() && hasIrregularSpaces()) { classNames += ' OxLeading'; } else if (isBeforeLinebreak()) { classNames += ' OxTrailing'; } } source += '' + Ox.encodeHTML(substr) .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 substr.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') .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)' }); }); } } self.setOption = function(key, value) { renderSource(); }; return that; };