oxjs/source/Ox.UI/js/Core/Ox.SyntaxHighlighter.js

132 lines
5.1 KiB
JavaScript
Raw Normal View History

// vim: et:ts=4:sw=4:sts=4:ft=js
/*@
Ox.SyntaxHighlighter <function> Syntax Highlighter
(options[, self]) -> <o> Syntax Highlighter
options <o> Options
offset <n|1> First line number
showLineNumbers <b|false> If true, show line numbers
showLinebreaks <b|false> If true, show linebreaks
showWhitespace <b|false> If true, show whitespace
stripComments <b|false> If true, strip comments
self <o> 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 ?
'<span class="OxLinebreak">\u21A9</span>' : ''
) + '<br/>',
tab = (
self.options.showTabs ?
'<span class="OxTab">\u2192</span>' : ''
) + Ox.repeat('&nbsp;', self.options.tabLength - self.options.showTabs),
whitespace = self.options.showWhitespace ? '\u00B7' : '&nbsp;';
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 += '<span class="' + classNames + '">' +
Ox.encodeHTML(substr)
.replace(/ /g, whitespace)
.replace(/\t/g, tab)
.replace(/\n/g, linebreak) + '</span>';
}
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('<br/>');
that.empty();
///*
var $test = new Ox.Element()
.css({
position: 'absolute',
top: '-1000px'
})
.html(Ox.repeat('&nbsp;', 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('<br/>')
)
.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;
};