// vim: et:ts=4:sw=4:sts=4:ft=js /*@ Ox.SyntaxHighlighter Syntax Highlighter (options[, self]) -> Syntax Highlighter options Options offset First line number showLineNumbers If true, show line numbers showLinebreaks If true, show linebreaks showWhitespace If true, show whitespace stripComments If true, strip comments self Shared private @*/ Ox.SyntaxHighlighter = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ height: 40, lineLength: 80, //@ number of characters per line offset: 1, //@ first line number showLineNumbers: false, showLinebreaks: false, //@ show linebreak symbols showTabs: false, //@ show tab symbols showWhitespace: false, //@ show irregular leading or trailing whitespace source: '', //@ JavaScript source stripComments: false, //@ strip all comments stripLinebreaks: false, //@ strip multiple linebreaks, NOT IMPLEMENTED stripWhitespace: false, //@ strip all whitespace, NOT IMPLEMENTED tabLength: 4, //@ number of spaces per tab width: 80 }) .options(options || {}) .addClass('OxSyntaxHighlighter'); renderSource(); function renderSource() { var lines, source = '', tokens, linebreak = ( self.options.showLinebreaks ? '\u21A9' : '' ) + '
', tab = ( self.options.showTabs ? '\u2192' : '' ) + Ox.repeat(' ', self.options.tabLength - 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.tabLength; } }); lines = source.split('
'); that.empty(); ///* var $test = new Ox.Element() .css({ position: 'absolute', top: '-1000px' }) .html(Ox.repeat(' ', self.options.lineLength)) .appendTo(that); var width = $test.width() + 4; // add padding $test.removeElement(); //*/ if (self.options.showLineNumbers) { self.$lineNumbers = new Ox.Element() .addClass('OxLineNumbers') .html( Ox.range(lines.length).map(function(line) { return (line + self.options.offset); }).join('
') ) .appendTo(that); } self.$source = new Ox.Element() .addClass('OxSourceCode') .css({ background: '-moz-linear-gradient(left, rgb(255, 255, 255), rgb(255, 255, 255) ' + width + 'px, rgb(248, 248, 248) ' + width + 'px, rgb(248, 248, 248))' }) .css({ background: '-webkit-linear-gradient(left, rgb(255, 255, 255) ' + width + 'px, rgb(192, 192, 192) ' + width + 'px, rgb(255, 255, 255) ' + (width + 1) + 'px)' }) .html(source) .appendTo(that); } self.setOption = function(key, value) { renderSource(); }; return that; };