diff --git a/demos/syntax/js/syntax.js b/demos/syntax/js/syntax.js index 4a702d89..b9e98c4e 100644 --- a/demos/syntax/js/syntax.js +++ b/demos/syntax/js/syntax.js @@ -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 = $('
') - .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) }); \ No newline at end of file diff --git a/source/Ox.UI/js/Core/Ox.DocPage.js b/source/Ox.UI/js/Core/Ox.DocPage.js index 5bd64f34..8730aa02 100644 --- a/source/Ox.UI/js/Core/Ox.DocPage.js +++ b/source/Ox.UI/js/Core/Ox.DocPage.js @@ -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; diff --git a/source/Ox.UI/js/Core/Ox.SyntaxHighlighter.js b/source/Ox.UI/js/Core/Ox.SyntaxHighlighter.js index 25ce2cf6..ce7b6152 100644 --- a/source/Ox.UI/js/Core/Ox.SyntaxHighlighter.js +++ b/source/Ox.UI/js/Core/Ox.SyntaxHighlighter.js @@ -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 += '' + - encodeToken(source, token.type) + ''; - } - 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('
'); - 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('
') - ) - .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 = '
', @@ -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 += '' + + encodeToken(source, token.type) + ''; + } + 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('
'); + + 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('
') + ) + .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; diff --git a/source/Ox.js b/source/Ox.js index cbc106ce..3b408792 100644 --- a/source/Ox.js +++ b/source/Ox.js @@ -73,7 +73,7 @@ Ox.print 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 Counts the occurences of values in an array, object or string +Ox.contains 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 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 Returns true if a condition holds for every element of a collection +Ox.every Tests if every element of a collection satisfies a given condition Unlike [].every(), Ox.every() 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 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 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 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 For-loop, functional-style - Returning false from the iterater function acts like a + Returning false from the iterator function acts like a break statement. Unlike a for loop, Ox.loop doesn't leak its counter variable to the outer scope, but returns it. @@ -630,8 +647,10 @@ Ox.loop For-loop, functional-style equivalent to for (var i = start; i < stop; i++) or, if start is larger than stop, for (var i = start; i > stop; i--) - (start, stop, step) -> Next value - equivalent to for (var i = start; i < stop; i += step) + (start, stop, step, callback) -> Next value + equivalent to for (var i = start; i < stop; i += step) or, + if step is negative, + for (var i = start; i > stop; i += step) start Start value stop Stop value (exclusive) step Step value @@ -919,8 +938,7 @@ Ox.values 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 Maximum latitude of a Mercator projection Ox.MAX_LATITUDE = Ox.deg(Math.atan(Ox.sinh(Math.PI))); +//@ Ox.MIN_LATITUDE Minimum latitude of a Mercator projection Ox.MIN_LATITUDE = -Ox.MAX_LATITUDE; //@ Object --------------------------------------------------------------------- @@ -3811,44 +3831,32 @@ Ox.char 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 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 Checks if a string ends with a given substring - While Ox.endsWith('foobar', 'bar') is longer than - /bar$/.test('foobar'), Ox.endsWith('foobar', bar) - is shorter than new RegExp(bar + '$').test('foobar'). + If the substring is a string literal (and not a variable), + /sub$/.test(str) or !!/sub$/(str) + is shorter than Ox.ends(str, sub). > 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 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 Checks if a string starts with a given substring - While Ox.startsWith('foobar', 'foo') is longer than - /^foo/.test('foobar'), Ox.startsWith('foobar', foo) - is shorter than new RegExp('^' + foo).test('foobar'). - > Ox.endsWith('foobar', 'bar') + If the substring is a string literal (and not a variable), + /^sub/.test(str) or !!/^sub/(str) + is shorter than Ox.starts(str, sub). + > 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 Strips HTML tags from a string 'foo' @*/ Ox.stripTags = function(str) { - return str.replace(/(<.*?>)/g, ''); + return str.replace(/<.*?>/g, ''); }; +/*@ +Ox.substr A better substr + > 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(