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'
}, 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'),
$textarea = new Ox.Input({
height: 400,
type: 'textarea',
width: 400
})
.css({
fontFamily: 'Menlo, Monaco, Courier, Courier New'
})
.appendTo($body),
$button = new Ox.Button({
title: 'Run',
width: 40
})
.css({
position: 'absolute',
left: '8px',
top: '416px',
})
.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);
Ox.SplitPanel({
elements: [
{
element: Ox.Element()
.append($textarea)
.append($button),
resizable: true,
resize: [128, 256, 384],
size: 256
},
{
element: Ox.Container()
.append($options)
.append($syntaxHighlighter)
}
],
orientation: 'horizontal'
}).appendTo(Ox.UI.$body)
});

View file

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

View file

@ -7,11 +7,12 @@
Ox.SyntaxHighlighter = function(options, self) {
self = self || {};
var that = new Ox.Element({}, 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
@ -25,71 +26,7 @@ Ox.SyntaxHighlighter = function(options, self) {
.options(options || {})
.addClass('OxSyntaxHighlighter');
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/>');
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);
renderSource();
function encodeToken(source, token) {
var linebreak = '<br/>',
@ -110,8 +47,74 @@ Ox.SyntaxHighlighter = function(options, self) {
.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;

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
any arguments, separated by spaces
arg <*> any value
> Ox.print("foo").substr(-3)
> Ox.print('foo').split(' ').pop()
"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'])
{f: 1, o: 2}
> 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,
objects and strings.
> 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([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo")
{id: "foo", title: "Foo"}
@*/
// fixme: shouldn't this be getElementById() ?
Ox.getObjectById = function(arr, id) {
/***
>>> Ox.getObjectById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo").title
"Foo"
***/
var ret = null;
Ox.forEach(arr, function(v) {
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([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo")
0
@*/
// fixme: shouldn't this be getIndexById() ?
Ox.getPositionById = function(arr, id) {
/***
>>> Ox.getPositionById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "bar")
1
***/
var ret = -1;
Ox.forEach(arr, function(v, i) {
if (v.id == id) {
@ -543,6 +558,8 @@ Ox.isEmpty <f> Returns true if a collection is empty
true
> Ox.isEmpty('')
true
> Ox.isEmpty(function() {})
true
@*/
Ox.isEmpty = function(val) {
return Ox.len(val) == 0;
@ -620,7 +637,7 @@ Ox.len = function(obj) {
/*@
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>Ox.loop</code> doesn't leak its counter variable to the outer scope,
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,
if <code>start</code> is larger than <code>stop</code>,
<code>for (var i = start; i > stop; i--)</code>
(start, stop, step) -> <n> Next value
equivalent to <code>for (var i = start; i < stop; i += step)</code>
(start, stop, step, callback) -> <n> Next value
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
stop <n> Stop value (exclusive)
step <n> Step value
@ -919,8 +938,7 @@ Ox.values <f> Returns the values of a collection
[1, 3]
@*/
Ox.values = function(col) {
// this happens to works for arrays and strings, but still:
// Ox.values(arr) -> arr, Ox.values(str) -> str.split('')
// Ox.values(str) is identical to str.split('')
var values = [];
Ox.forEach(col, function(val) {
values.push(val);
@ -3614,7 +3632,9 @@ Ox.sinh = function(x) {
//@ Constants ------------------------------------------------------------------
//@ Ox.MAX_LATITUDE <n> Maximum latitude of a Mercator projection
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;
//@ 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])?
Ox.char = String.fromCharCode;
Ox.clean = function(str) {
/*
>>> Ox.clean("foo bar")
/*@
Ox.clean <f> Remove leading, trailing and double whitespace from a string
> Ox.clean("foo bar")
"foo bar"
>>> Ox.clean(" foo bar ")
> Ox.clean(" foo bar ")
"foo bar"
>>> Ox.clean(" foo \n bar ")
> Ox.clean(" foo \n bar ")
"foo\nbar"
*/
@*/
Ox.clean = function(str) {
return Ox.map(str.split('\n'), function(str) {
return Ox.trim(str.replace(/\s+/g, ' '));
}).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
While <code>Ox.endsWith('foobar', 'bar')</code> is longer than
<code>/bar$/.test('foobar')</code>, <code>Ox.endsWith('foobar', bar)</code>
is shorter than <code>new RegExp(bar + '$').test('foobar')</code>.
If the substring is a string literal (and not a variable),
<code>/sub$/.test(str)</code> or <code>!!/sub$/(str)</code>
is shorter than <code>Ox.ends(str, sub)</code>.
> Ox.endsWith('foobar', 'bar')
true
@*/
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) {
@ -3928,23 +3936,27 @@ Ox.parsePath = function(str) {
};
}
Ox.repeat = function(obj, num) {
/*
works for arrays, numbers and strings
>>> Ox.repeat(1, 3)
/*@
Ox.repeat <f> Repeat a value multiple times
Works for arrays, numbers and strings
> Ox.repeat(1, 3)
"111"
>>> Ox.repeat("foo", 3)
> Ox.repeat("foo", 3)
"foofoofoo"
>>> Ox.repeat([1, 2], 3)
> Ox.repeat([1, 2], 3)
[1, 2, 1, 2, 1, 2]
*/
@*/
Ox.repeat = function(val, num) {
var ret;
if (Ox.isArray(obj)) {
ret = num >= 1 ? Ox.map(Ox.range(obj.length * num), function(v, i) {
return obj[i % obj.length]
}) : [];
if (Ox.isArray(val)) {
ret = [];
if (num >= 1) {
Ox.loop(num, function() {
ret = Ox.merge(ret, val);
});
}
} else {
ret = num >= 1 ? new Array(num + 1).join(obj.toString()) : '';
ret = num >= 1 ? new Array(num + 1).join(val.toString()) : '';
}
return ret;
};
@ -3959,22 +3971,15 @@ Ox.reverse = function(str) {
/*@
Ox.startsWith <f> Checks if a string starts with a given substring
While <code>Ox.startsWith('foobar', 'foo')</code> is longer than
<code>/^foo/.test('foobar')</code>, <code>Ox.startsWith('foobar', foo)</code>
is shorter than <code>new RegExp('^' + foo).test('foobar')</code>.
> Ox.endsWith('foobar', 'bar')
If the substring is a string literal (and not a variable),
<code>/^sub/.test(str)</code> or <code>!!/^sub/(str)</code>
is shorter than <code>Ox.starts(str, sub)</code>.
> Ox.startsWith('foobar', 'foo')
true
@*/
Ox.startsWith = function(str, sub) {
/*
>>> Ox.startsWith("foobar", "foo")
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);
// fixme: rename to starts
return str.substr(0, sub.length) == sub;
};
/*@
@ -3983,26 +3988,29 @@ Ox.stripTags <f> Strips HTML tags from a string
'foo'
@*/
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) {
/***
// fixme: needed?
Ox.substr behaves like str[start:stop] in Python
(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;
return str.substring(