// vim: et:ts=4:sw=4:sts=4:ft=js /*@ Ox.SyntaxHighlighter Syntax Highlighter (options[, self]) -> Syntax Highlighter options Options lineLength If larger than zero, show edge of page offset First line number 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, 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 = new Ox.Element() .addClass('OxLineNumbers') .html( Ox.range(lines.length).map(function(line) { return (line + self.options.offset); }).join('
') ) .appendTo(that); } $source = new Ox.Element() .addClass('OxSourceCode') .html(source) .appendTo(that); if (self.options.lineLength) { $line = new Ox.Element() .css({ position: 'absolute', top: '-1000px' }) .html(Ox.repeat(' ', self.options.lineLength)) .appendTo(that), width = $line.width() + 4; // add padding $line.removeElement(); ['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; };