better syntax highlighter demo, some bugfixes

This commit is contained in:
rolux 2011-05-08 20:22:43 +02:00
parent 37219bfbe9
commit 0b629a1b40
4 changed files with 214 additions and 189 deletions

View file

@ -3,48 +3,61 @@ Ox.load('UI', {
theme: 'classic' theme: 'classic'
}, function() { }, function() {
Ox.Theme('classic'); var $button = Ox.Button({
title: 'Run',
width: 256
})
.css({
marginTop: '256px'
})
.click(function() {
$syntaxHighlighter.options({
source: $textarea.value()
});
}),
$options = Ox.Element()
.append(
Ox.FormElementGroup({
elements: ['showLineNumbers', 'showLinebreaks', 'showTabs', 'showWhitespace'].map(function(v, i) {
return Ox.Checkbox({
overlap: 'right',
title: Ox.toDashes(v).split('-').map(function(v) { return Ox.toTitleCase(v); }).join(' '),
width: 160
}).bindEvent({
change: function(event) {
$syntaxHighlighter.options(v, event.checked);
}
})
})
})
),
$syntaxHighlighter = Ox.SyntaxHighlighter(),
$textarea = Ox.Input({
height: 256,
type: 'textarea',
width: 256
})
.css({
fontFamily: 'Menlo, Monaco, Courier, Courier New'
});
var $body = $('body'), Ox.SplitPanel({
$textarea = new Ox.Input({ elements: [
height: 400, {
type: 'textarea', element: Ox.Element()
width: 400 .append($textarea)
}) .append($button),
.css({ resizable: true,
fontFamily: 'Menlo, Monaco, Courier, Courier New' resize: [128, 256, 384],
}) size: 256
.appendTo($body), },
$button = new Ox.Button({ {
title: 'Run', element: Ox.Container()
width: 40 .append($options)
}) .append($syntaxHighlighter)
.css({ }
position: 'absolute', ],
left: '8px', orientation: 'horizontal'
top: '416px', }).appendTo(Ox.UI.$body)
})
.bindEvent({
click: function() {
$div.empty();
new Ox.SyntaxHighlighter({
showLinebreaks: true,
showTabs: true,
showWhitespace: true,
source: $textarea.value(),
//stripComments: true,
//stripLinebreaks: true,
//stripWhitespace: true,
}).appendTo($div);
}
})
.appendTo($body),
$div = $('<div>')
.css({
position: 'absolute',
left: '416px',
top: '8px'
})
.appendTo($body);
}); });

View file

@ -129,6 +129,7 @@ Ox.DocPage = function(options, self) {
); );
$elements.push( $elements.push(
Ox.SyntaxHighlighter({ Ox.SyntaxHighlighter({
showLineNumbers: true,
// fixme: silly // fixme: silly
source: item.source.map(function(token) { source: item.source.map(function(token) {
return token.source; return token.source;

View file

@ -7,11 +7,12 @@
Ox.SyntaxHighlighter = function(options, self) { Ox.SyntaxHighlighter = function(options, self) {
self = self || {}; self = self || {};
var that = new Ox.Element({}, self) var that = Ox.Element({}, self)
.defaults({ .defaults({
height: 40, height: 40,
lineLength: 80, //@ number of characters per line lineLength: 80, //@ number of characters per line
offset: 1, //@ first line number offset: 1, //@ first line number
showLineNumbers: false,
showLinebreaks: false, //@ show linebreak symbols showLinebreaks: false, //@ show linebreak symbols
showTabs: false, //@ show tab symbols showTabs: false, //@ show tab symbols
showWhitespace: false, //@ show irregular leading or trailing whitespace showWhitespace: false, //@ show irregular leading or trailing whitespace
@ -25,71 +26,7 @@ Ox.SyntaxHighlighter = function(options, self) {
.options(options || {}) .options(options || {})
.addClass('OxSyntaxHighlighter'); .addClass('OxSyntaxHighlighter');
self.options.source = self.options.source renderSource();
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n');
self.cursor = 0;
self.source = '';
self.tokens = Ox.tokenize(self.options.source);
self.tokens.forEach(function(token, i) {
var classNames,
source = 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'
}
}
self.source += '<span class="' + classNames + '">' +
encodeToken(source, token.type) + '</span>';
}
self.cursor += token.length;
function isAfterLinebreak() {
return i == 0 ||
self.tokens[i - 1].type == 'linebreak';
}
function isBeforeLinebreak() {
return i == self.tokens.length - 1 ||
self.tokens[i + 1].type == 'linebreak';
}
function hasIrregularSpaces() {
return source.split('').reduce(function(prev, curr) {
return prev + (curr == ' ' ? 1 : 0);
}, 0) % self.options.tabLength;
}
});
self.lines = self.source.split('<br/>');
self.lineNumbersWidth = (
self.lines.length + self.options.offset - 1
).toString().length * 7;
self.$lineNumbers = new Ox.Element()
.addClass('OxLineNumbers')
.css({
display: 'table-cell',
width: self.lineNumbersWidth + 'px',
padding: '4px',
})
.html(
Ox.range(self.lines.length).map(function(line) {
return (line + self.options.offset);
}).join('<br/>')
)
.appendTo(that);
self.$source = new Ox.Element()
.addClass('OxSourceCode')
.css({
display: 'table-cell',
padding: '4px'
})
.html(self.source)
.appendTo(that);
function encodeToken(source, token) { function encodeToken(source, token) {
var linebreak = '<br/>', var linebreak = '<br/>',
@ -110,8 +47,74 @@ Ox.SyntaxHighlighter = function(options, self) {
.replace(/\n/g, linebreak); .replace(/\n/g, linebreak);
} }
self.setOption = function() { function renderSource() {
self.options.source = self.options.source
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n');
self.cursor = 0;
self.source = '';
self.tokens = Ox.tokenize(self.options.source);
self.tokens.forEach(function(token, i) {
var classNames,
source = 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'
}
}
self.source += '<span class="' + classNames + '">' +
encodeToken(source, token.type) + '</span>';
}
self.cursor += token.length;
function isAfterLinebreak() {
return i == 0 ||
self.tokens[i - 1].type == 'linebreak';
}
function isBeforeLinebreak() {
return i == self.tokens.length - 1 ||
self.tokens[i + 1].type == 'linebreak';
}
function hasIrregularSpaces() {
return source.split('').reduce(function(prev, curr) {
return prev + (curr == ' ' ? 1 : 0);
}, 0) % self.options.tabLength;
}
});
self.lines = self.source.split('<br/>');
that.empty();
if (self.options.showLineNumbers) {
self.$lineNumbers = new Ox.Element()
.addClass('OxLineNumbers')
.css({
display: 'table-cell',
padding: '4px',
})
.html(
Ox.range(self.lines.length).map(function(line) {
return (line + self.options.offset);
}).join('<br/>')
)
.appendTo(that);
}
self.$source = new Ox.Element()
.addClass('OxSourceCode')
.css({
display: 'table-cell',
padding: '4px'
})
.html(self.source)
.appendTo(that);
}
self.setOption = function(key, value) {
renderSource();
}; };
return that; return that;

View file

@ -73,7 +73,7 @@ Ox.print <f> Prints its arguments to the console
The string contains the timestamp, the name of the caller function, and The string contains the timestamp, the name of the caller function, and
any arguments, separated by spaces any arguments, separated by spaces
arg <*> any value arg <*> any value
> Ox.print("foo").substr(-3) > Ox.print('foo').split(' ').pop()
"foo" "foo"
@*/ @*/
@ -289,7 +289,26 @@ Ox.clone = function(obj) {
}; };
/*@ /*@
Ox.count <f> Counts the occurences of values in an array, object or string Ox.contains <f> Tests if a collection contains a value
> Ox.contains(['foo', 'bar'], 'foo')
true
> Ox.contains({foo: 'bar'}, 'bar')
true
> Ox.contains({foo: 'bar'}, 'foo')
false
> Ox.contains("foobar", "bar")
true
@*/
Ox.contains = function(col, val) {
/*
// fixme: rename to Ox.has or Ox.isIn?
// then it'd become convenient for arrays
*/
return (Ox.isObject(col) ? Ox.values(col) : col).indexOf(val) > -1;
};
/*@
Ox.count <f> Counts the occurences of values in a collection
> Ox.count(['f', 'o', 'o']) > Ox.count(['f', 'o', 'o'])
{f: 1, o: 2} {f: 1, o: 2}
> Ox.count({a: 'f', b: 'o', c: 'o'}) > Ox.count({a: 'f', b: 'o', c: 'o'})
@ -330,7 +349,7 @@ Ox.each = function(obj, fn) {
}; };
/*@ /*@
Ox.every <f> Returns true if a condition holds for every element of a collection Ox.every <f> Tests if every element of a collection satisfies a given condition
Unlike <code>[].every()</code>, <code>Ox.every()</code> works for arrays, Unlike <code>[].every()</code>, <code>Ox.every()</code> works for arrays,
objects and strings. objects and strings.
> Ox.every([0, 1, 2], function(v, i) { return i == v; }) > Ox.every([0, 1, 2], function(v, i) { return i == v; })
@ -436,13 +455,11 @@ Ox.forEach = function(obj, fn) {
/*@ /*@
Ox.getObjectById <f> Returns an array element with a given id Ox.getObjectById <f> Returns an array element with a given id
> Ox.getObjectById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo")
{id: "foo", title: "Foo"}
@*/ @*/
// fixme: shouldn't this be getElementById() ? // fixme: shouldn't this be getElementById() ?
Ox.getObjectById = function(arr, id) { Ox.getObjectById = function(arr, id) {
/***
>>> Ox.getObjectById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo").title
"Foo"
***/
var ret = null; var ret = null;
Ox.forEach(arr, function(v) { Ox.forEach(arr, function(v) {
if (v.id == id) { if (v.id == id) {
@ -455,13 +472,11 @@ Ox.getObjectById = function(arr, id) {
/*@ /*@
Ox.getPositionById <f> Returns the index of an array element with a given id Ox.getPositionById <f> Returns the index of an array element with a given id
> Ox.getPositionById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo")
0
@*/ @*/
// fixme: shouldn't this be getIndexById() ? // fixme: shouldn't this be getIndexById() ?
Ox.getPositionById = function(arr, id) { Ox.getPositionById = function(arr, id) {
/***
>>> Ox.getPositionById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "bar")
1
***/
var ret = -1; var ret = -1;
Ox.forEach(arr, function(v, i) { Ox.forEach(arr, function(v, i) {
if (v.id == id) { if (v.id == id) {
@ -543,6 +558,8 @@ Ox.isEmpty <f> Returns true if a collection is empty
true true
> Ox.isEmpty('') > Ox.isEmpty('')
true true
> Ox.isEmpty(function() {})
true
@*/ @*/
Ox.isEmpty = function(val) { Ox.isEmpty = function(val) {
return Ox.len(val) == 0; return Ox.len(val) == 0;
@ -620,7 +637,7 @@ Ox.len = function(obj) {
/*@ /*@
Ox.loop <f> For-loop, functional-style Ox.loop <f> For-loop, functional-style
Returning <code>false</code> from the iterater function acts like a Returning <code>false</code> from the iterator function acts like a
<code>break</code> statement. Unlike a <code>for</code> loop, <code>break</code> statement. Unlike a <code>for</code> loop,
<code>Ox.loop</code> doesn't leak its counter variable to the outer scope, <code>Ox.loop</code> doesn't leak its counter variable to the outer scope,
but returns it. but returns it.
@ -630,8 +647,10 @@ Ox.loop <f> For-loop, functional-style
equivalent to <code>for (var i = start; i < stop; i++)</code> or, equivalent to <code>for (var i = start; i < stop; i++)</code> or,
if <code>start</code> is larger than <code>stop</code>, if <code>start</code> is larger than <code>stop</code>,
<code>for (var i = start; i > stop; i--)</code> <code>for (var i = start; i > stop; i--)</code>
(start, stop, step) -> <n> Next value (start, stop, step, callback) -> <n> Next value
equivalent to <code>for (var i = start; i < stop; i += step)</code> equivalent to <code>for (var i = start; i < stop; i += step)</code> or,
if <code>step</code> is negative,
<code>for (var i = start; i > stop; i += step)</code>
start <n> Start value start <n> Start value
stop <n> Stop value (exclusive) stop <n> Stop value (exclusive)
step <n> Step value step <n> Step value
@ -919,8 +938,7 @@ Ox.values <f> Returns the values of a collection
[1, 3] [1, 3]
@*/ @*/
Ox.values = function(col) { Ox.values = function(col) {
// this happens to works for arrays and strings, but still: // Ox.values(str) is identical to str.split('')
// Ox.values(arr) -> arr, Ox.values(str) -> str.split('')
var values = []; var values = [];
Ox.forEach(col, function(val) { Ox.forEach(col, function(val) {
values.push(val); values.push(val);
@ -3614,7 +3632,9 @@ Ox.sinh = function(x) {
//@ Constants ------------------------------------------------------------------ //@ Constants ------------------------------------------------------------------
//@ Ox.MAX_LATITUDE <n> Maximum latitude of a Mercator projection
Ox.MAX_LATITUDE = Ox.deg(Math.atan(Ox.sinh(Math.PI))); Ox.MAX_LATITUDE = Ox.deg(Math.atan(Ox.sinh(Math.PI)));
//@ Ox.MIN_LATITUDE <n> Minimum latitude of a Mercator projection
Ox.MIN_LATITUDE = -Ox.MAX_LATITUDE; Ox.MIN_LATITUDE = -Ox.MAX_LATITUDE;
//@ Object --------------------------------------------------------------------- //@ Object ---------------------------------------------------------------------
@ -3811,44 +3831,32 @@ Ox.char <f> Alias for String.fromCharCode
// fixme: add some mapping? like Ox.char(9, 13) or Ox.char([9, 13])? // fixme: add some mapping? like Ox.char(9, 13) or Ox.char([9, 13])?
Ox.char = String.fromCharCode; Ox.char = String.fromCharCode;
Ox.clean = function(str) { /*@
/* Ox.clean <f> Remove leading, trailing and double whitespace from a string
>>> Ox.clean("foo bar") > Ox.clean("foo bar")
"foo bar" "foo bar"
>>> Ox.clean(" foo bar ") > Ox.clean(" foo bar ")
"foo bar" "foo bar"
>>> Ox.clean(" foo \n bar ") > Ox.clean(" foo \n bar ")
"foo\nbar" "foo\nbar"
*/ @*/
Ox.clean = function(str) {
return Ox.map(str.split('\n'), function(str) { return Ox.map(str.split('\n'), function(str) {
return Ox.trim(str.replace(/\s+/g, ' ')); return Ox.trim(str.replace(/\s+/g, ' '));
}).join('\n'); }).join('\n');
}; };
Ox.contains = function(str, chr) {
/*
>>> Ox.contains("foo", "bar")
false
>>> Ox.contains("foobar", "bar")
true
>>> Ox.contains(['foo', 'bar'], 'foo')
true
// fixme: rename to Ox.has or Ox.isIn?
// then it'd become convenient for arrays
*/
return str.indexOf(chr) > -1;
};
/*@ /*@
Ox.endsWith <f> Checks if a string ends with a given substring Ox.endsWith <f> Checks if a string ends with a given substring
While <code>Ox.endsWith('foobar', 'bar')</code> is longer than If the substring is a string literal (and not a variable),
<code>/bar$/.test('foobar')</code>, <code>Ox.endsWith('foobar', bar)</code> <code>/sub$/.test(str)</code> or <code>!!/sub$/(str)</code>
is shorter than <code>new RegExp(bar + '$').test('foobar')</code>. is shorter than <code>Ox.ends(str, sub)</code>.
> Ox.endsWith('foobar', 'bar') > Ox.endsWith('foobar', 'bar')
true true
@*/ @*/
Ox.endsWith = function(str, sub) { Ox.endsWith = function(str, sub) {
return new RegExp(sub + '$').test(str); // fixme: rename to ends
return str.substr(str.length - sub.length) == sub;
}; };
Ox.highlight = function(txt, str) { Ox.highlight = function(txt, str) {
@ -3928,23 +3936,27 @@ Ox.parsePath = function(str) {
}; };
} }
Ox.repeat = function(obj, num) { /*@
/* Ox.repeat <f> Repeat a value multiple times
works for arrays, numbers and strings Works for arrays, numbers and strings
>>> Ox.repeat(1, 3) > Ox.repeat(1, 3)
"111" "111"
>>> Ox.repeat("foo", 3) > Ox.repeat("foo", 3)
"foofoofoo" "foofoofoo"
>>> Ox.repeat([1, 2], 3) > Ox.repeat([1, 2], 3)
[1, 2, 1, 2, 1, 2] [1, 2, 1, 2, 1, 2]
*/ @*/
Ox.repeat = function(val, num) {
var ret; var ret;
if (Ox.isArray(obj)) { if (Ox.isArray(val)) {
ret = num >= 1 ? Ox.map(Ox.range(obj.length * num), function(v, i) { ret = [];
return obj[i % obj.length] if (num >= 1) {
}) : []; Ox.loop(num, function() {
ret = Ox.merge(ret, val);
});
}
} else { } else {
ret = num >= 1 ? new Array(num + 1).join(obj.toString()) : ''; ret = num >= 1 ? new Array(num + 1).join(val.toString()) : '';
} }
return ret; return ret;
}; };
@ -3959,22 +3971,15 @@ Ox.reverse = function(str) {
/*@ /*@
Ox.startsWith <f> Checks if a string starts with a given substring Ox.startsWith <f> Checks if a string starts with a given substring
While <code>Ox.startsWith('foobar', 'foo')</code> is longer than If the substring is a string literal (and not a variable),
<code>/^foo/.test('foobar')</code>, <code>Ox.startsWith('foobar', foo)</code> <code>/^sub/.test(str)</code> or <code>!!/^sub/(str)</code>
is shorter than <code>new RegExp('^' + foo).test('foobar')</code>. is shorter than <code>Ox.starts(str, sub)</code>.
> Ox.endsWith('foobar', 'bar') > Ox.startsWith('foobar', 'foo')
true true
@*/ @*/
Ox.startsWith = function(str, sub) { Ox.startsWith = function(str, sub) {
/* // fixme: rename to starts
>>> Ox.startsWith("foobar", "foo") return str.substr(0, sub.length) == sub;
true
// fixme:
// !!(/^sub/(str)) is shorter than
// Ox.startsWith(str, sub) anyway
// new RegExp('^' + sub).test(str) is longer though...
*/
return new RegExp('^' + sub).test(str);
}; };
/*@ /*@
@ -3983,26 +3988,29 @@ Ox.stripTags <f> Strips HTML tags from a string
'foo' 'foo'
@*/ @*/
Ox.stripTags = function(str) { Ox.stripTags = function(str) {
return str.replace(/(<.*?>)/g, ''); return str.replace(/<.*?>/g, '');
}; };
/*@
Ox.substr <f> A better <code>substr</code>
> Ox.substr('foobar', 1)
"oobar"
> Ox.substr('foobar', -1)
"r"
> Ox.substr('foobar', 1, 5)
"ooba"
> Ox.substr('foobar', 1, -1)
"ooba"
> Ox.substr('foobar', -5, 5)
"ooba"
> Ox.substr('foobar', -5, -1)
"ooba"
@*/
Ox.substr = function(str, start, stop) { Ox.substr = function(str, start, stop) {
/*** /***
// fixme: needed?
Ox.substr behaves like str[start:stop] in Python Ox.substr behaves like str[start:stop] in Python
(or like str.substring() with negative values for stop) (or like str.substring() with negative values for stop)
// fixme: needed?
>>> Ox.substr('foobar', 1)
"oobar"
>>> Ox.substr('foobar', -1)
"r"
>>> Ox.substr('foobar', 1, 3)
"oo"
>>> Ox.substr('foobar', -3, 5)
"ba"
>>> Ox.substr('foobar', 1, -2)
"oob"
>>> Ox.substr('foobar', -4, -1)
"oba"
***/ ***/
stop = Ox.isUndefined(stop) ? str.length : stop; stop = Ox.isUndefined(stop) ? str.length : stop;
return str.substring( return str.substring(