diff --git a/codemirror/addon/dialog/dialog.css b/codemirror/addon/dialog/dialog.css
new file mode 100644
index 00000000..2e7c0fc9
--- /dev/null
+++ b/codemirror/addon/dialog/dialog.css
@@ -0,0 +1,32 @@
+.CodeMirror-dialog {
+ position: absolute;
+ left: 0; right: 0;
+ background: white;
+ z-index: 15;
+ padding: .1em .8em;
+ overflow: hidden;
+ color: #333;
+}
+
+.CodeMirror-dialog-top {
+ border-bottom: 1px solid #eee;
+ top: 0;
+}
+
+.CodeMirror-dialog-bottom {
+ border-top: 1px solid #eee;
+ bottom: 0;
+}
+
+.CodeMirror-dialog input {
+ border: none;
+ outline: none;
+ background: transparent;
+ width: 20em;
+ color: inherit;
+ font-family: monospace;
+}
+
+.CodeMirror-dialog button {
+ font-size: 70%;
+}
diff --git a/codemirror/addon/dialog/dialog.js b/codemirror/addon/dialog/dialog.js
new file mode 100644
index 00000000..e0e8ad4e
--- /dev/null
+++ b/codemirror/addon/dialog/dialog.js
@@ -0,0 +1,155 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+// Open simple dialogs on top of an editor. Relies on dialog.css.
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ function dialogDiv(cm, template, bottom) {
+ var wrap = cm.getWrapperElement();
+ var dialog;
+ dialog = wrap.appendChild(document.createElement("div"));
+ if (bottom)
+ dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
+ else
+ dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
+
+ if (typeof template == "string") {
+ dialog.innerHTML = template;
+ } else { // Assuming it's a detached DOM element.
+ dialog.appendChild(template);
+ }
+ return dialog;
+ }
+
+ function closeNotification(cm, newVal) {
+ if (cm.state.currentNotificationClose)
+ cm.state.currentNotificationClose();
+ cm.state.currentNotificationClose = newVal;
+ }
+
+ CodeMirror.defineExtension("openDialog", function(template, callback, options) {
+ if (!options) options = {};
+
+ closeNotification(this, null);
+
+ var dialog = dialogDiv(this, template, options.bottom);
+ var closed = false, me = this;
+ function close(newVal) {
+ if (typeof newVal == 'string') {
+ inp.value = newVal;
+ } else {
+ if (closed) return;
+ closed = true;
+ dialog.parentNode.removeChild(dialog);
+ me.focus();
+
+ if (options.onClose) options.onClose(dialog);
+ }
+ }
+
+ var inp = dialog.getElementsByTagName("input")[0], button;
+ if (inp) {
+ if (options.value) {
+ inp.value = options.value;
+ inp.select();
+ }
+
+ if (options.onInput)
+ CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
+ if (options.onKeyUp)
+ CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
+
+ CodeMirror.on(inp, "keydown", function(e) {
+ if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
+ if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
+ inp.blur();
+ CodeMirror.e_stop(e);
+ close();
+ }
+ if (e.keyCode == 13) callback(inp.value, e);
+ });
+
+ if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
+
+ inp.focus();
+ } else if (button = dialog.getElementsByTagName("button")[0]) {
+ CodeMirror.on(button, "click", function() {
+ close();
+ me.focus();
+ });
+
+ if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
+
+ button.focus();
+ }
+ return close;
+ });
+
+ CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
+ closeNotification(this, null);
+ var dialog = dialogDiv(this, template, options && options.bottom);
+ var buttons = dialog.getElementsByTagName("button");
+ var closed = false, me = this, blurring = 1;
+ function close() {
+ if (closed) return;
+ closed = true;
+ dialog.parentNode.removeChild(dialog);
+ me.focus();
+ }
+ buttons[0].focus();
+ for (var i = 0; i < buttons.length; ++i) {
+ var b = buttons[i];
+ (function(callback) {
+ CodeMirror.on(b, "click", function(e) {
+ CodeMirror.e_preventDefault(e);
+ close();
+ if (callback) callback(me);
+ });
+ })(callbacks[i]);
+ CodeMirror.on(b, "blur", function() {
+ --blurring;
+ setTimeout(function() { if (blurring <= 0) close(); }, 200);
+ });
+ CodeMirror.on(b, "focus", function() { ++blurring; });
+ }
+ });
+
+ /*
+ * openNotification
+ * Opens a notification, that can be closed with an optional timer
+ * (default 5000ms timer) and always closes on click.
+ *
+ * If a notification is opened while another is opened, it will close the
+ * currently opened one and open the new one immediately.
+ */
+ CodeMirror.defineExtension("openNotification", function(template, options) {
+ closeNotification(this, close);
+ var dialog = dialogDiv(this, template, options && options.bottom);
+ var closed = false, doneTimer;
+ var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
+
+ function close() {
+ if (closed) return;
+ closed = true;
+ clearTimeout(doneTimer);
+ dialog.parentNode.removeChild(dialog);
+ }
+
+ CodeMirror.on(dialog, 'click', function(e) {
+ CodeMirror.e_preventDefault(e);
+ close();
+ });
+
+ if (duration)
+ doneTimer = setTimeout(close, duration);
+
+ return close;
+ });
+});
diff --git a/codemirror/addon/edit/matchbrackets.js b/codemirror/addon/edit/matchbrackets.js
new file mode 100644
index 00000000..70e1ae18
--- /dev/null
+++ b/codemirror/addon/edit/matchbrackets.js
@@ -0,0 +1,120 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
+ (document.documentMode == null || document.documentMode < 8);
+
+ var Pos = CodeMirror.Pos;
+
+ var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
+
+ function findMatchingBracket(cm, where, strict, config) {
+ var line = cm.getLineHandle(where.line), pos = where.ch - 1;
+ var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
+ if (!match) return null;
+ var dir = match.charAt(1) == ">" ? 1 : -1;
+ if (strict && (dir > 0) != (pos == where.ch)) return null;
+ var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
+
+ var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
+ if (found == null) return null;
+ return {from: Pos(where.line, pos), to: found && found.pos,
+ match: found && found.ch == match.charAt(0), forward: dir > 0};
+ }
+
+ // bracketRegex is used to specify which type of bracket to scan
+ // should be a regexp, e.g. /[[\]]/
+ //
+ // Note: If "where" is on an open bracket, then this bracket is ignored.
+ //
+ // Returns false when no bracket was found, null when it reached
+ // maxScanLines and gave up
+ function scanForBracket(cm, where, dir, style, config) {
+ var maxScanLen = (config && config.maxScanLineLength) || 10000;
+ var maxScanLines = (config && config.maxScanLines) || 1000;
+
+ var stack = [];
+ var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/;
+ var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
+ : Math.max(cm.firstLine() - 1, where.line - maxScanLines);
+ for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
+ var line = cm.getLine(lineNo);
+ if (!line) continue;
+ var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;
+ if (line.length > maxScanLen) continue;
+ if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
+ for (; pos != end; pos += dir) {
+ var ch = line.charAt(pos);
+ if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
+ var match = matching[ch];
+ if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
+ else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
+ else stack.pop();
+ }
+ }
+ }
+ return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;
+ }
+
+ function matchBrackets(cm, autoclear, config) {
+ // Disable brace matching in long lines, since it'll cause hugely slow updates
+ var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
+ var marks = [], ranges = cm.listSelections();
+ for (var i = 0; i < ranges.length; i++) {
+ var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config);
+ if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {
+ var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
+ marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
+ if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
+ marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
+ }
+ }
+
+ if (marks.length) {
+ // Kludge to work around the IE bug from issue #1193, where text
+ // input stops going to the textare whever this fires.
+ if (ie_lt8 && cm.state.focused) cm.focus();
+
+ var clear = function() {
+ cm.operation(function() {
+ for (var i = 0; i < marks.length; i++) marks[i].clear();
+ });
+ };
+ if (autoclear) setTimeout(clear, 800);
+ else return clear;
+ }
+ }
+
+ var currentlyHighlighted = null;
+ function doMatchBrackets(cm) {
+ cm.operation(function() {
+ if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
+ currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
+ });
+ }
+
+ CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
+ if (old && old != CodeMirror.Init)
+ cm.off("cursorActivity", doMatchBrackets);
+ if (val) {
+ cm.state.matchBrackets = typeof val == "object" ? val : {};
+ cm.on("cursorActivity", doMatchBrackets);
+ }
+ });
+
+ CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
+ CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){
+ return findMatchingBracket(this, pos, strict, config);
+ });
+ CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
+ return scanForBracket(this, pos, dir, style, config);
+ });
+});
diff --git a/codemirror/addon/fold/brace-fold.js b/codemirror/addon/fold/brace-fold.js
new file mode 100644
index 00000000..1605f6c2
--- /dev/null
+++ b/codemirror/addon/fold/brace-fold.js
@@ -0,0 +1,105 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+CodeMirror.registerHelper("fold", "brace", function(cm, start) {
+ var line = start.line, lineText = cm.getLine(line);
+ var startCh, tokenType;
+
+ function findOpening(openCh) {
+ for (var at = start.ch, pass = 0;;) {
+ var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1);
+ if (found == -1) {
+ if (pass == 1) break;
+ pass = 1;
+ at = lineText.length;
+ continue;
+ }
+ if (pass == 1 && found < start.ch) break;
+ tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
+ if (!/^(comment|string)/.test(tokenType)) return found + 1;
+ at = found - 1;
+ }
+ }
+
+ var startToken = "{", endToken = "}", startCh = findOpening("{");
+ if (startCh == null) {
+ startToken = "[", endToken = "]";
+ startCh = findOpening("[");
+ }
+
+ if (startCh == null) return;
+ var count = 1, lastLine = cm.lastLine(), end, endCh;
+ outer: for (var i = line; i <= lastLine; ++i) {
+ var text = cm.getLine(i), pos = i == line ? startCh : 0;
+ for (;;) {
+ var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
+ if (nextOpen < 0) nextOpen = text.length;
+ if (nextClose < 0) nextClose = text.length;
+ pos = Math.min(nextOpen, nextClose);
+ if (pos == text.length) break;
+ if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) {
+ if (pos == nextOpen) ++count;
+ else if (!--count) { end = i; endCh = pos; break outer; }
+ }
+ ++pos;
+ }
+ }
+ if (end == null || line == end && endCh == startCh) return;
+ return {from: CodeMirror.Pos(line, startCh),
+ to: CodeMirror.Pos(end, endCh)};
+});
+
+CodeMirror.registerHelper("fold", "import", function(cm, start) {
+ function hasImport(line) {
+ if (line < cm.firstLine() || line > cm.lastLine()) return null;
+ var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
+ if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
+ if (start.type != "keyword" || start.string != "import") return null;
+ // Now find closing semicolon, return its position
+ for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {
+ var text = cm.getLine(i), semi = text.indexOf(";");
+ if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};
+ }
+ }
+
+ var start = start.line, has = hasImport(start), prev;
+ if (!has || hasImport(start - 1) || ((prev = hasImport(start - 2)) && prev.end.line == start - 1))
+ return null;
+ for (var end = has.end;;) {
+ var next = hasImport(end.line + 1);
+ if (next == null) break;
+ end = next.end;
+ }
+ return {from: cm.clipPos(CodeMirror.Pos(start, has.startCh + 1)), to: end};
+});
+
+CodeMirror.registerHelper("fold", "include", function(cm, start) {
+ function hasInclude(line) {
+ if (line < cm.firstLine() || line > cm.lastLine()) return null;
+ var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
+ if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
+ if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
+ }
+
+ var start = start.line, has = hasInclude(start);
+ if (has == null || hasInclude(start - 1) != null) return null;
+ for (var end = start;;) {
+ var next = hasInclude(end + 1);
+ if (next == null) break;
+ ++end;
+ }
+ return {from: CodeMirror.Pos(start, has + 1),
+ to: cm.clipPos(CodeMirror.Pos(end))};
+});
+
+});
diff --git a/codemirror/addon/fold/comment-fold.js b/codemirror/addon/fold/comment-fold.js
new file mode 100644
index 00000000..b75db7ea
--- /dev/null
+++ b/codemirror/addon/fold/comment-fold.js
@@ -0,0 +1,57 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
+ return mode.blockCommentStart && mode.blockCommentEnd;
+}, function(cm, start) {
+ var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd;
+ if (!startToken || !endToken) return;
+ var line = start.line, lineText = cm.getLine(line);
+
+ var startCh;
+ for (var at = start.ch, pass = 0;;) {
+ var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1);
+ if (found == -1) {
+ if (pass == 1) return;
+ pass = 1;
+ at = lineText.length;
+ continue;
+ }
+ if (pass == 1 && found < start.ch) return;
+ if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)))) {
+ startCh = found + startToken.length;
+ break;
+ }
+ at = found - 1;
+ }
+
+ var depth = 1, lastLine = cm.lastLine(), end, endCh;
+ outer: for (var i = line; i <= lastLine; ++i) {
+ var text = cm.getLine(i), pos = i == line ? startCh : 0;
+ for (;;) {
+ var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
+ if (nextOpen < 0) nextOpen = text.length;
+ if (nextClose < 0) nextClose = text.length;
+ pos = Math.min(nextOpen, nextClose);
+ if (pos == text.length) break;
+ if (pos == nextOpen) ++depth;
+ else if (!--depth) { end = i; endCh = pos; break outer; }
+ ++pos;
+ }
+ }
+ if (end == null || line == end && endCh == startCh) return;
+ return {from: CodeMirror.Pos(line, startCh),
+ to: CodeMirror.Pos(end, endCh)};
+});
+
+});
diff --git a/codemirror/addon/fold/foldcode.js b/codemirror/addon/fold/foldcode.js
new file mode 100644
index 00000000..62911f93
--- /dev/null
+++ b/codemirror/addon/fold/foldcode.js
@@ -0,0 +1,149 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ function doFold(cm, pos, options, force) {
+ if (options && options.call) {
+ var finder = options;
+ options = null;
+ } else {
+ var finder = getOption(cm, options, "rangeFinder");
+ }
+ if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
+ var minSize = getOption(cm, options, "minFoldSize");
+
+ function getRange(allowFolded) {
+ var range = finder(cm, pos);
+ if (!range || range.to.line - range.from.line < minSize) return null;
+ var marks = cm.findMarksAt(range.from);
+ for (var i = 0; i < marks.length; ++i) {
+ if (marks[i].__isFold && force !== "fold") {
+ if (!allowFolded) return null;
+ range.cleared = true;
+ marks[i].clear();
+ }
+ }
+ return range;
+ }
+
+ var range = getRange(true);
+ if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
+ pos = CodeMirror.Pos(pos.line - 1, 0);
+ range = getRange(false);
+ }
+ if (!range || range.cleared || force === "unfold") return;
+
+ var myWidget = makeWidget(cm, options);
+ CodeMirror.on(myWidget, "mousedown", function(e) {
+ myRange.clear();
+ CodeMirror.e_preventDefault(e);
+ });
+ var myRange = cm.markText(range.from, range.to, {
+ replacedWith: myWidget,
+ clearOnEnter: true,
+ __isFold: true
+ });
+ myRange.on("clear", function(from, to) {
+ CodeMirror.signal(cm, "unfold", cm, from, to);
+ });
+ CodeMirror.signal(cm, "fold", cm, range.from, range.to);
+ }
+
+ function makeWidget(cm, options) {
+ var widget = getOption(cm, options, "widget");
+ if (typeof widget == "string") {
+ var text = document.createTextNode(widget);
+ widget = document.createElement("span");
+ widget.appendChild(text);
+ widget.className = "CodeMirror-foldmarker";
+ }
+ return widget;
+ }
+
+ // Clumsy backwards-compatible interface
+ CodeMirror.newFoldFunction = function(rangeFinder, widget) {
+ return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
+ };
+
+ // New-style interface
+ CodeMirror.defineExtension("foldCode", function(pos, options, force) {
+ doFold(this, pos, options, force);
+ });
+
+ CodeMirror.defineExtension("isFolded", function(pos) {
+ var marks = this.findMarksAt(pos);
+ for (var i = 0; i < marks.length; ++i)
+ if (marks[i].__isFold) return true;
+ });
+
+ CodeMirror.commands.toggleFold = function(cm) {
+ cm.foldCode(cm.getCursor());
+ };
+ CodeMirror.commands.fold = function(cm) {
+ cm.foldCode(cm.getCursor(), null, "fold");
+ };
+ CodeMirror.commands.unfold = function(cm) {
+ cm.foldCode(cm.getCursor(), null, "unfold");
+ };
+ CodeMirror.commands.foldAll = function(cm) {
+ cm.operation(function() {
+ for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
+ cm.foldCode(CodeMirror.Pos(i, 0), null, "fold");
+ });
+ };
+ CodeMirror.commands.unfoldAll = function(cm) {
+ cm.operation(function() {
+ for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
+ cm.foldCode(CodeMirror.Pos(i, 0), null, "unfold");
+ });
+ };
+
+ CodeMirror.registerHelper("fold", "combine", function() {
+ var funcs = Array.prototype.slice.call(arguments, 0);
+ return function(cm, start) {
+ for (var i = 0; i < funcs.length; ++i) {
+ var found = funcs[i](cm, start);
+ if (found) return found;
+ }
+ };
+ });
+
+ CodeMirror.registerHelper("fold", "auto", function(cm, start) {
+ var helpers = cm.getHelpers(start, "fold");
+ for (var i = 0; i < helpers.length; i++) {
+ var cur = helpers[i](cm, start);
+ if (cur) return cur;
+ }
+ });
+
+ var defaultOptions = {
+ rangeFinder: CodeMirror.fold.auto,
+ widget: "\u2194",
+ minFoldSize: 0,
+ scanUp: false
+ };
+
+ CodeMirror.defineOption("foldOptions", null);
+
+ function getOption(cm, options, name) {
+ if (options && options[name] !== undefined)
+ return options[name];
+ var editorOptions = cm.options.foldOptions;
+ if (editorOptions && editorOptions[name] !== undefined)
+ return editorOptions[name];
+ return defaultOptions[name];
+ }
+
+ CodeMirror.defineExtension("foldOption", function(options, name) {
+ return getOption(this, options, name);
+ });
+});
diff --git a/codemirror/addon/fold/foldgutter.css b/codemirror/addon/fold/foldgutter.css
new file mode 100644
index 00000000..ad19ae2d
--- /dev/null
+++ b/codemirror/addon/fold/foldgutter.css
@@ -0,0 +1,20 @@
+.CodeMirror-foldmarker {
+ color: blue;
+ text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
+ font-family: arial;
+ line-height: .3;
+ cursor: pointer;
+}
+.CodeMirror-foldgutter {
+ width: .7em;
+}
+.CodeMirror-foldgutter-open,
+.CodeMirror-foldgutter-folded {
+ cursor: pointer;
+}
+.CodeMirror-foldgutter-open:after {
+ content: "\25BE";
+}
+.CodeMirror-foldgutter-folded:after {
+ content: "\25B8";
+}
diff --git a/codemirror/addon/fold/foldgutter.js b/codemirror/addon/fold/foldgutter.js
new file mode 100644
index 00000000..199120c7
--- /dev/null
+++ b/codemirror/addon/fold/foldgutter.js
@@ -0,0 +1,144 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"), require("./foldcode"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror", "./foldcode"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
+ if (old && old != CodeMirror.Init) {
+ cm.clearGutter(cm.state.foldGutter.options.gutter);
+ cm.state.foldGutter = null;
+ cm.off("gutterClick", onGutterClick);
+ cm.off("change", onChange);
+ cm.off("viewportChange", onViewportChange);
+ cm.off("fold", onFold);
+ cm.off("unfold", onFold);
+ cm.off("swapDoc", updateInViewport);
+ }
+ if (val) {
+ cm.state.foldGutter = new State(parseOptions(val));
+ updateInViewport(cm);
+ cm.on("gutterClick", onGutterClick);
+ cm.on("change", onChange);
+ cm.on("viewportChange", onViewportChange);
+ cm.on("fold", onFold);
+ cm.on("unfold", onFold);
+ cm.on("swapDoc", updateInViewport);
+ }
+ });
+
+ var Pos = CodeMirror.Pos;
+
+ function State(options) {
+ this.options = options;
+ this.from = this.to = 0;
+ }
+
+ function parseOptions(opts) {
+ if (opts === true) opts = {};
+ if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
+ if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
+ if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
+ return opts;
+ }
+
+ function isFolded(cm, line) {
+ var marks = cm.findMarksAt(Pos(line));
+ for (var i = 0; i < marks.length; ++i)
+ if (marks[i].__isFold && marks[i].find().from.line == line) return true;
+ }
+
+ function marker(spec) {
+ if (typeof spec == "string") {
+ var elt = document.createElement("div");
+ elt.className = spec + " CodeMirror-guttermarker-subtle";
+ return elt;
+ } else {
+ return spec.cloneNode(true);
+ }
+ }
+
+ function updateFoldInfo(cm, from, to) {
+ var opts = cm.state.foldGutter.options, cur = from;
+ var minSize = cm.foldOption(opts, "minFoldSize");
+ var func = cm.foldOption(opts, "rangeFinder");
+ cm.eachLine(from, to, function(line) {
+ var mark = null;
+ if (isFolded(cm, cur)) {
+ mark = marker(opts.indicatorFolded);
+ } else {
+ var pos = Pos(cur, 0);
+ var range = func && func(cm, pos);
+ if (range && range.to.line - range.from.line >= minSize)
+ mark = marker(opts.indicatorOpen);
+ }
+ cm.setGutterMarker(line, opts.gutter, mark);
+ ++cur;
+ });
+ }
+
+ function updateInViewport(cm) {
+ var vp = cm.getViewport(), state = cm.state.foldGutter;
+ if (!state) return;
+ cm.operation(function() {
+ updateFoldInfo(cm, vp.from, vp.to);
+ });
+ state.from = vp.from; state.to = vp.to;
+ }
+
+ function onGutterClick(cm, line, gutter) {
+ var state = cm.state.foldGutter;
+ if (!state) return;
+ var opts = state.options;
+ if (gutter != opts.gutter) return;
+ cm.foldCode(Pos(line, 0), opts.rangeFinder);
+ }
+
+ function onChange(cm) {
+ var state = cm.state.foldGutter;
+ if (!state) return;
+ var opts = state.options;
+ state.from = state.to = 0;
+ clearTimeout(state.changeUpdate);
+ state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
+ }
+
+ function onViewportChange(cm) {
+ var state = cm.state.foldGutter;
+ if (!state) return;
+ var opts = state.options;
+ clearTimeout(state.changeUpdate);
+ state.changeUpdate = setTimeout(function() {
+ var vp = cm.getViewport();
+ if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
+ updateInViewport(cm);
+ } else {
+ cm.operation(function() {
+ if (vp.from < state.from) {
+ updateFoldInfo(cm, vp.from, state.from);
+ state.from = vp.from;
+ }
+ if (vp.to > state.to) {
+ updateFoldInfo(cm, state.to, vp.to);
+ state.to = vp.to;
+ }
+ });
+ }
+ }, opts.updateViewportTimeSpan || 400);
+ }
+
+ function onFold(cm, from) {
+ var state = cm.state.foldGutter;
+ if (!state) return;
+ var line = from.line;
+ if (line >= state.from && line < state.to)
+ updateFoldInfo(cm, line, line + 1);
+ }
+});
diff --git a/codemirror/addon/lint/css-lint.js b/codemirror/addon/lint/css-lint.js
new file mode 100644
index 00000000..1f61b479
--- /dev/null
+++ b/codemirror/addon/lint/css-lint.js
@@ -0,0 +1,35 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+// Depends on csslint.js from https://github.com/stubbornella/csslint
+
+// declare global: CSSLint
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+"use strict";
+
+CodeMirror.registerHelper("lint", "css", function(text) {
+ var found = [];
+ if (!window.CSSLint) return found;
+ var results = CSSLint.verify(text), messages = results.messages, message = null;
+ for ( var i = 0; i < messages.length; i++) {
+ message = messages[i];
+ var startLine = message.line -1, endLine = message.line -1, startCol = message.col -1, endCol = message.col;
+ found.push({
+ from: CodeMirror.Pos(startLine, startCol),
+ to: CodeMirror.Pos(endLine, endCol),
+ message: message.message,
+ severity : message.type
+ });
+ }
+ return found;
+});
+
+});
diff --git a/codemirror/addon/lint/lint.css b/codemirror/addon/lint/lint.css
new file mode 100644
index 00000000..414a9a0e
--- /dev/null
+++ b/codemirror/addon/lint/lint.css
@@ -0,0 +1,73 @@
+/* The lint marker gutter */
+.CodeMirror-lint-markers {
+ width: 16px;
+}
+
+.CodeMirror-lint-tooltip {
+ background-color: infobackground;
+ border: 1px solid black;
+ border-radius: 4px 4px 4px 4px;
+ color: infotext;
+ font-family: monospace;
+ font-size: 10pt;
+ overflow: hidden;
+ padding: 2px 5px;
+ position: fixed;
+ white-space: pre;
+ white-space: pre-wrap;
+ z-index: 100;
+ max-width: 600px;
+ opacity: 0;
+ transition: opacity .4s;
+ -moz-transition: opacity .4s;
+ -webkit-transition: opacity .4s;
+ -o-transition: opacity .4s;
+ -ms-transition: opacity .4s;
+}
+
+.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning {
+ background-position: left bottom;
+ background-repeat: repeat-x;
+}
+
+.CodeMirror-lint-mark-error {
+ background-image:
+ url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==")
+ ;
+}
+
+.CodeMirror-lint-mark-warning {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII=");
+}
+
+.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning {
+ background-position: center center;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ display: inline-block;
+ height: 16px;
+ width: 16px;
+ vertical-align: middle;
+ position: relative;
+}
+
+.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning {
+ padding-left: 18px;
+ background-position: top left;
+ background-repeat: no-repeat;
+}
+
+.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII=");
+}
+
+.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII=");
+}
+
+.CodeMirror-lint-marker-multiple {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC");
+ background-repeat: no-repeat;
+ background-position: right bottom;
+ width: 100%; height: 100%;
+}
diff --git a/codemirror/addon/lint/lint.js b/codemirror/addon/lint/lint.js
new file mode 100644
index 00000000..18eb7090
--- /dev/null
+++ b/codemirror/addon/lint/lint.js
@@ -0,0 +1,205 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+ var GUTTER_ID = "CodeMirror-lint-markers";
+
+ function showTooltip(e, content) {
+ var tt = document.createElement("div");
+ tt.className = "CodeMirror-lint-tooltip";
+ tt.appendChild(content.cloneNode(true));
+ document.body.appendChild(tt);
+
+ function position(e) {
+ if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
+ tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
+ tt.style.left = (e.clientX + 5) + "px";
+ }
+ CodeMirror.on(document, "mousemove", position);
+ position(e);
+ if (tt.style.opacity != null) tt.style.opacity = 1;
+ return tt;
+ }
+ function rm(elt) {
+ if (elt.parentNode) elt.parentNode.removeChild(elt);
+ }
+ function hideTooltip(tt) {
+ if (!tt.parentNode) return;
+ if (tt.style.opacity == null) rm(tt);
+ tt.style.opacity = 0;
+ setTimeout(function() { rm(tt); }, 600);
+ }
+
+ function showTooltipFor(e, content, node) {
+ var tooltip = showTooltip(e, content);
+ function hide() {
+ CodeMirror.off(node, "mouseout", hide);
+ if (tooltip) { hideTooltip(tooltip); tooltip = null; }
+ }
+ var poll = setInterval(function() {
+ if (tooltip) for (var n = node;; n = n.parentNode) {
+ if (n && n.nodeType == 11) n = n.host;
+ if (n == document.body) return;
+ if (!n) { hide(); break; }
+ }
+ if (!tooltip) return clearInterval(poll);
+ }, 400);
+ CodeMirror.on(node, "mouseout", hide);
+ }
+
+ function LintState(cm, options, hasGutter) {
+ this.marked = [];
+ this.options = options;
+ this.timeout = null;
+ this.hasGutter = hasGutter;
+ this.onMouseOver = function(e) { onMouseOver(cm, e); };
+ }
+
+ function parseOptions(cm, options) {
+ if (options instanceof Function) return {getAnnotations: options};
+ if (!options || options === true) options = {};
+ if (!options.getAnnotations) options.getAnnotations = cm.getHelper(CodeMirror.Pos(0, 0), "lint");
+ if (!options.getAnnotations) throw new Error("Required option 'getAnnotations' missing (lint addon)");
+ return options;
+ }
+
+ function clearMarks(cm) {
+ var state = cm.state.lint;
+ if (state.hasGutter) cm.clearGutter(GUTTER_ID);
+ for (var i = 0; i < state.marked.length; ++i)
+ state.marked[i].clear();
+ state.marked.length = 0;
+ }
+
+ function makeMarker(labels, severity, multiple, tooltips) {
+ var marker = document.createElement("div"), inner = marker;
+ marker.className = "CodeMirror-lint-marker-" + severity;
+ if (multiple) {
+ inner = marker.appendChild(document.createElement("div"));
+ inner.className = "CodeMirror-lint-marker-multiple";
+ }
+
+ if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) {
+ showTooltipFor(e, labels, inner);
+ });
+
+ return marker;
+ }
+
+ function getMaxSeverity(a, b) {
+ if (a == "error") return a;
+ else return b;
+ }
+
+ function groupByLine(annotations) {
+ var lines = [];
+ for (var i = 0; i < annotations.length; ++i) {
+ var ann = annotations[i], line = ann.from.line;
+ (lines[line] || (lines[line] = [])).push(ann);
+ }
+ return lines;
+ }
+
+ function annotationTooltip(ann) {
+ var severity = ann.severity;
+ if (!severity) severity = "error";
+ var tip = document.createElement("div");
+ tip.className = "CodeMirror-lint-message-" + severity;
+ tip.appendChild(document.createTextNode(ann.message));
+ return tip;
+ }
+
+ function startLinting(cm) {
+ var state = cm.state.lint, options = state.options;
+ var passOptions = options.options || options; // Support deprecated passing of `options` property in options
+ if (options.async || options.getAnnotations.async)
+ options.getAnnotations(cm.getValue(), updateLinting, passOptions, cm);
+ else
+ updateLinting(cm, options.getAnnotations(cm.getValue(), passOptions, cm));
+ }
+
+ function updateLinting(cm, annotationsNotSorted) {
+ clearMarks(cm);
+ var state = cm.state.lint, options = state.options;
+
+ var annotations = groupByLine(annotationsNotSorted);
+
+ for (var line = 0; line < annotations.length; ++line) {
+ var anns = annotations[line];
+ if (!anns) continue;
+
+ var maxSeverity = null;
+ var tipLabel = state.hasGutter && document.createDocumentFragment();
+
+ for (var i = 0; i < anns.length; ++i) {
+ var ann = anns[i];
+ var severity = ann.severity;
+ if (!severity) severity = "error";
+ maxSeverity = getMaxSeverity(maxSeverity, severity);
+
+ if (options.formatAnnotation) ann = options.formatAnnotation(ann);
+ if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann));
+
+ if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, {
+ className: "CodeMirror-lint-mark-" + severity,
+ __annotation: ann
+ }));
+ }
+
+ if (state.hasGutter)
+ cm.setGutterMarker(line, GUTTER_ID, makeMarker(tipLabel, maxSeverity, anns.length > 1,
+ state.options.tooltips));
+ }
+ if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm);
+ }
+
+ function onChange(cm) {
+ var state = cm.state.lint;
+ clearTimeout(state.timeout);
+ state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
+ }
+
+ function popupSpanTooltip(ann, e) {
+ var target = e.target || e.srcElement;
+ showTooltipFor(e, annotationTooltip(ann), target);
+ }
+
+ function onMouseOver(cm, e) {
+ var target = e.target || e.srcElement;
+ if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
+ var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
+ var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
+ for (var i = 0; i < spans.length; ++i) {
+ var ann = spans[i].__annotation;
+ if (ann) return popupSpanTooltip(ann, e);
+ }
+ }
+
+ CodeMirror.defineOption("lint", false, function(cm, val, old) {
+ if (old && old != CodeMirror.Init) {
+ clearMarks(cm);
+ cm.off("change", onChange);
+ CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver);
+ delete cm.state.lint;
+ }
+
+ if (val) {
+ var gutters = cm.getOption("gutters"), hasLintGutter = false;
+ for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true;
+ var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter);
+ cm.on("change", onChange);
+ if (state.options.tooltips != false)
+ CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver);
+
+ startLinting(cm);
+ }
+ });
+});
diff --git a/codemirror/addon/scroll/annotatescrollbar.js b/codemirror/addon/scroll/annotatescrollbar.js
new file mode 100644
index 00000000..54aeacf2
--- /dev/null
+++ b/codemirror/addon/scroll/annotatescrollbar.js
@@ -0,0 +1,100 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ CodeMirror.defineExtension("annotateScrollbar", function(options) {
+ if (typeof options == "string") options = {className: options};
+ return new Annotation(this, options);
+ });
+
+ CodeMirror.defineOption("scrollButtonHeight", 0);
+
+ function Annotation(cm, options) {
+ this.cm = cm;
+ this.options = options;
+ this.buttonHeight = options.scrollButtonHeight || cm.getOption("scrollButtonHeight");
+ this.annotations = [];
+ this.doRedraw = this.doUpdate = null;
+ this.div = cm.getWrapperElement().appendChild(document.createElement("div"));
+ this.div.style.cssText = "position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none";
+ this.computeScale();
+
+ function scheduleRedraw(delay) {
+ clearTimeout(self.doRedraw);
+ self.doRedraw = setTimeout(function() { self.redraw(); }, delay);
+ }
+
+ var self = this;
+ cm.on("refresh", this.resizeHandler = function() {
+ clearTimeout(self.doUpdate);
+ self.doUpdate = setTimeout(function() {
+ if (self.computeScale()) scheduleRedraw(20);
+ }, 100);
+ });
+ cm.on("markerAdded", this.resizeHandler);
+ cm.on("markerCleared", this.resizeHandler);
+ if (options.listenForChanges !== false)
+ cm.on("change", this.changeHandler = function() {
+ scheduleRedraw(250);
+ });
+ }
+
+ Annotation.prototype.computeScale = function() {
+ var cm = this.cm;
+ var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
+ cm.heightAtLine(cm.lastLine() + 1, "local");
+ if (hScale != this.hScale) {
+ this.hScale = hScale;
+ return true;
+ }
+ };
+
+ Annotation.prototype.update = function(annotations) {
+ this.annotations = annotations;
+ this.redraw();
+ };
+
+ Annotation.prototype.redraw = function(compute) {
+ if (compute !== false) this.computeScale();
+ var cm = this.cm, hScale = this.hScale;
+
+ var frag = document.createDocumentFragment(), anns = this.annotations;
+ if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
+ var ann = anns[i];
+ var top = nextTop || cm.charCoords(ann.from, "local").top * hScale;
+ var bottom = cm.charCoords(ann.to, "local").bottom * hScale;
+ while (i < anns.length - 1) {
+ nextTop = cm.charCoords(anns[i + 1].from, "local").top * hScale;
+ if (nextTop > bottom + .9) break;
+ ann = anns[++i];
+ bottom = cm.charCoords(ann.to, "local").bottom * hScale;
+ }
+ if (bottom == top) continue;
+ var height = Math.max(bottom - top, 3);
+
+ var elt = frag.appendChild(document.createElement("div"));
+ elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
+ + (top + this.buttonHeight) + "px; height: " + height + "px";
+ elt.className = this.options.className;
+ }
+ this.div.textContent = "";
+ this.div.appendChild(frag);
+ };
+
+ Annotation.prototype.clear = function() {
+ this.cm.off("refresh", this.resizeHandler);
+ this.cm.off("markerAdded", this.resizeHandler);
+ this.cm.off("markerCleared", this.resizeHandler);
+ if (this.changeHandler) this.cm.off("change", this.changeHandler);
+ this.div.parentNode.removeChild(this.div);
+ };
+});
diff --git a/codemirror/addon/search/matchesonscrollbar.css b/codemirror/addon/search/matchesonscrollbar.css
new file mode 100644
index 00000000..77932cc9
--- /dev/null
+++ b/codemirror/addon/search/matchesonscrollbar.css
@@ -0,0 +1,8 @@
+.CodeMirror-search-match {
+ background: gold;
+ border-top: 1px solid orange;
+ border-bottom: 1px solid orange;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ opacity: .5;
+}
diff --git a/codemirror/addon/search/matchesonscrollbar.js b/codemirror/addon/search/matchesonscrollbar.js
new file mode 100644
index 00000000..dbd67a4a
--- /dev/null
+++ b/codemirror/addon/search/matchesonscrollbar.js
@@ -0,0 +1,95 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"), require("./searchcursor"), require("../scroll/annotatescrollbar"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror", "./searchcursor", "../scroll/annotatescrollbar"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+
+ CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) {
+ if (typeof options == "string") options = {className: options};
+ if (!options) options = {};
+ return new SearchAnnotation(this, query, caseFold, options);
+ });
+
+ function SearchAnnotation(cm, query, caseFold, options) {
+ this.cm = cm;
+ var annotateOptions = {listenForChanges: false};
+ for (var prop in options) annotateOptions[prop] = options[prop];
+ if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match";
+ this.annotation = cm.annotateScrollbar(annotateOptions);
+ this.query = query;
+ this.caseFold = caseFold;
+ this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1};
+ this.matches = [];
+ this.update = null;
+
+ this.findMatches();
+ this.annotation.update(this.matches);
+
+ var self = this;
+ cm.on("change", this.changeHandler = function(_cm, change) { self.onChange(change); });
+ }
+
+ var MAX_MATCHES = 1000;
+
+ SearchAnnotation.prototype.findMatches = function() {
+ if (!this.gap) return;
+ for (var i = 0; i < this.matches.length; i++) {
+ var match = this.matches[i];
+ if (match.from.line >= this.gap.to) break;
+ if (match.to.line >= this.gap.from) this.matches.splice(i--, 1);
+ }
+ var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold);
+ while (cursor.findNext()) {
+ var match = {from: cursor.from(), to: cursor.to()};
+ if (match.from.line >= this.gap.to) break;
+ this.matches.splice(i++, 0, match);
+ if (this.matches.length > MAX_MATCHES) break;
+ }
+ this.gap = null;
+ };
+
+ function offsetLine(line, changeStart, sizeChange) {
+ if (line <= changeStart) return line;
+ return Math.max(changeStart, line + sizeChange);
+ }
+
+ SearchAnnotation.prototype.onChange = function(change) {
+ var startLine = change.from.line;
+ var endLine = CodeMirror.changeEnd(change).line;
+ var sizeChange = endLine - change.to.line;
+ if (this.gap) {
+ this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line);
+ this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line);
+ } else {
+ this.gap = {from: change.from.line, to: endLine + 1};
+ }
+
+ if (sizeChange) for (var i = 0; i < this.matches.length; i++) {
+ var match = this.matches[i];
+ var newFrom = offsetLine(match.from.line, startLine, sizeChange);
+ if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch);
+ var newTo = offsetLine(match.to.line, startLine, sizeChange);
+ if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch);
+ }
+ clearTimeout(this.update);
+ var self = this;
+ this.update = setTimeout(function() { self.updateAfterChange(); }, 250);
+ };
+
+ SearchAnnotation.prototype.updateAfterChange = function() {
+ this.findMatches();
+ this.annotation.update(this.matches);
+ };
+
+ SearchAnnotation.prototype.clear = function() {
+ this.cm.off("change", this.changeHandler);
+ this.annotation.clear();
+ };
+});
diff --git a/codemirror/addon/search/search.js b/codemirror/addon/search/search.js
new file mode 100644
index 00000000..0251067a
--- /dev/null
+++ b/codemirror/addon/search/search.js
@@ -0,0 +1,164 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+// Define search commands. Depends on dialog.js or another
+// implementation of the openDialog method.
+
+// Replace works a little oddly -- it will do the replace on the next
+// Ctrl-G (or whatever is bound to findNext) press. You prevent a
+// replace by making sure the match is no longer selected when hitting
+// Ctrl-G.
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+ function searchOverlay(query, caseInsensitive) {
+ if (typeof query == "string")
+ query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g");
+ else if (!query.global)
+ query = new RegExp(query.source, query.ignoreCase ? "gi" : "g");
+
+ return {token: function(stream) {
+ query.lastIndex = stream.pos;
+ var match = query.exec(stream.string);
+ if (match && match.index == stream.pos) {
+ stream.pos += match[0].length;
+ return "searching";
+ } else if (match) {
+ stream.pos = match.index;
+ } else {
+ stream.skipToEnd();
+ }
+ }};
+ }
+
+ function SearchState() {
+ this.posFrom = this.posTo = this.query = null;
+ this.overlay = null;
+ }
+ function getSearchState(cm) {
+ return cm.state.search || (cm.state.search = new SearchState());
+ }
+ function queryCaseInsensitive(query) {
+ return typeof query == "string" && query == query.toLowerCase();
+ }
+ function getSearchCursor(cm, query, pos) {
+ // Heuristic: if the query string is all lowercase, do a case insensitive search.
+ return cm.getSearchCursor(query, pos, queryCaseInsensitive(query));
+ }
+ function dialog(cm, text, shortText, deflt, f) {
+ if (cm.openDialog) cm.openDialog(text, f, {value: deflt});
+ else f(prompt(shortText, deflt));
+ }
+ function confirmDialog(cm, text, shortText, fs) {
+ if (cm.openConfirm) cm.openConfirm(text, fs);
+ else if (confirm(shortText)) fs[0]();
+ }
+ function parseQuery(query) {
+ var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
+ if (isRE) {
+ try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); }
+ catch(e) {} // Not a regular expression after all, do a string search
+ }
+ if (typeof query == "string" ? query == "" : query.test(""))
+ query = /x^/;
+ return query;
+ }
+ var queryDialog =
+ 'Search: (Use /re/ syntax for regexp search)';
+ function doSearch(cm, rev) {
+ var state = getSearchState(cm);
+ if (state.query) return findNext(cm, rev);
+ dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) {
+ cm.operation(function() {
+ if (!query || state.query) return;
+ state.query = parseQuery(query);
+ cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query));
+ state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query));
+ cm.addOverlay(state.overlay);
+ if (cm.showMatchesOnScrollbar) {
+ if (state.annotate) { state.annotate.clear(); state.annotate = null; }
+ state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query));
+ }
+ state.posFrom = state.posTo = cm.getCursor();
+ findNext(cm, rev);
+ });
+ });
+ }
+ function findNext(cm, rev) {cm.operation(function() {
+ var state = getSearchState(cm);
+ var cursor = getSearchCursor(cm, state.query, rev ? state.posFrom : state.posTo);
+ if (!cursor.find(rev)) {
+ cursor = getSearchCursor(cm, state.query, rev ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0));
+ if (!cursor.find(rev)) return;
+ }
+ cm.setSelection(cursor.from(), cursor.to());
+ cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
+ state.posFrom = cursor.from(); state.posTo = cursor.to();
+ });}
+ function clearSearch(cm) {cm.operation(function() {
+ var state = getSearchState(cm);
+ if (!state.query) return;
+ state.query = null;
+ cm.removeOverlay(state.overlay);
+ if (state.annotate) { state.annotate.clear(); state.annotate = null; }
+ });}
+
+ var replaceQueryDialog =
+ 'Replace: (Use /re/ syntax for regexp search)';
+ var replacementQueryDialog = 'With: ';
+ var doReplaceConfirm = "Replace? ";
+ function replace(cm, all) {
+ if (cm.getOption("readOnly")) return;
+ dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) {
+ if (!query) return;
+ query = parseQuery(query);
+ dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) {
+ if (all) {
+ cm.operation(function() {
+ for (var cursor = getSearchCursor(cm, query); cursor.findNext();) {
+ if (typeof query != "string") {
+ var match = cm.getRange(cursor.from(), cursor.to()).match(query);
+ cursor.replace(text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
+ } else cursor.replace(text);
+ }
+ });
+ } else {
+ clearSearch(cm);
+ var cursor = getSearchCursor(cm, query, cm.getCursor());
+ var advance = function() {
+ var start = cursor.from(), match;
+ if (!(match = cursor.findNext())) {
+ cursor = getSearchCursor(cm, query);
+ if (!(match = cursor.findNext()) ||
+ (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return;
+ }
+ cm.setSelection(cursor.from(), cursor.to());
+ cm.scrollIntoView({from: cursor.from(), to: cursor.to()});
+ confirmDialog(cm, doReplaceConfirm, "Replace?",
+ [function() {doReplace(match);}, advance]);
+ };
+ var doReplace = function(match) {
+ cursor.replace(typeof query == "string" ? text :
+ text.replace(/\$(\d)/g, function(_, i) {return match[i];}));
+ advance();
+ };
+ advance();
+ }
+ });
+ });
+ }
+
+ CodeMirror.commands.find = function(cm) {clearSearch(cm); doSearch(cm);};
+ CodeMirror.commands.findNext = doSearch;
+ CodeMirror.commands.findPrev = function(cm) {doSearch(cm, true);};
+ CodeMirror.commands.clearSearch = clearSearch;
+ CodeMirror.commands.replace = replace;
+ CodeMirror.commands.replaceAll = function(cm) {replace(cm, true);};
+});
diff --git a/codemirror/addon/search/searchcursor.js b/codemirror/addon/search/searchcursor.js
new file mode 100644
index 00000000..55c108b5
--- /dev/null
+++ b/codemirror/addon/search/searchcursor.js
@@ -0,0 +1,189 @@
+// CodeMirror, copyright (c) by Marijn Haverbeke and others
+// Distributed under an MIT license: http://codemirror.net/LICENSE
+
+(function(mod) {
+ if (typeof exports == "object" && typeof module == "object") // CommonJS
+ mod(require("../../lib/codemirror"));
+ else if (typeof define == "function" && define.amd) // AMD
+ define(["../../lib/codemirror"], mod);
+ else // Plain browser env
+ mod(CodeMirror);
+})(function(CodeMirror) {
+ "use strict";
+ var Pos = CodeMirror.Pos;
+
+ function SearchCursor(doc, query, pos, caseFold) {
+ this.atOccurrence = false; this.doc = doc;
+ if (caseFold == null && typeof query == "string") caseFold = false;
+
+ pos = pos ? doc.clipPos(pos) : Pos(0, 0);
+ this.pos = {from: pos, to: pos};
+
+ // The matches method is filled in based on the type of query.
+ // It takes a position and a direction, and returns an object
+ // describing the next occurrence of the query, or null if no
+ // more matches were found.
+ if (typeof query != "string") { // Regexp match
+ if (!query.global) query = new RegExp(query.source, query.ignoreCase ? "ig" : "g");
+ this.matches = function(reverse, pos) {
+ if (reverse) {
+ query.lastIndex = 0;
+ var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start;
+ for (;;) {
+ query.lastIndex = cutOff;
+ var newMatch = query.exec(line);
+ if (!newMatch) break;
+ match = newMatch;
+ start = match.index;
+ cutOff = match.index + (match[0].length || 1);
+ if (cutOff == line.length) break;
+ }
+ var matchLen = (match && match[0].length) || 0;
+ if (!matchLen) {
+ if (start == 0 && line.length == 0) {match = undefined;}
+ else if (start != doc.getLine(pos.line).length) {
+ matchLen++;
+ }
+ }
+ } else {
+ query.lastIndex = pos.ch;
+ var line = doc.getLine(pos.line), match = query.exec(line);
+ var matchLen = (match && match[0].length) || 0;
+ var start = match && match.index;
+ if (start + matchLen != line.length && !matchLen) matchLen = 1;
+ }
+ if (match && matchLen)
+ return {from: Pos(pos.line, start),
+ to: Pos(pos.line, start + matchLen),
+ match: match};
+ };
+ } else { // String query
+ var origQuery = query;
+ if (caseFold) query = query.toLowerCase();
+ var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;};
+ var target = query.split("\n");
+ // Different methods for single-line and multi-line queries
+ if (target.length == 1) {
+ if (!query.length) {
+ // Empty string would match anything and never progress, so
+ // we define it to match nothing instead.
+ this.matches = function() {};
+ } else {
+ this.matches = function(reverse, pos) {
+ if (reverse) {
+ var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig);
+ var match = line.lastIndexOf(query);
+ if (match > -1) {
+ match = adjustPos(orig, line, match);
+ return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
+ }
+ } else {
+ var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig);
+ var match = line.indexOf(query);
+ if (match > -1) {
+ match = adjustPos(orig, line, match) + pos.ch;
+ return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)};
+ }
+ }
+ };
+ }
+ } else {
+ var origTarget = origQuery.split("\n");
+ this.matches = function(reverse, pos) {
+ var last = target.length - 1;
+ if (reverse) {
+ if (pos.line - (target.length - 1) < doc.firstLine()) return;
+ if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return;
+ var to = Pos(pos.line, origTarget[last].length);
+ for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln)
+ if (target[i] != fold(doc.getLine(ln))) return;
+ var line = doc.getLine(ln), cut = line.length - origTarget[0].length;
+ if (fold(line.slice(cut)) != target[0]) return;
+ return {from: Pos(ln, cut), to: to};
+ } else {
+ if (pos.line + (target.length - 1) > doc.lastLine()) return;
+ var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length;
+ if (fold(line.slice(cut)) != target[0]) return;
+ var from = Pos(pos.line, cut);
+ for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln)
+ if (target[i] != fold(doc.getLine(ln))) return;
+ if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return;
+ return {from: from, to: Pos(ln, origTarget[last].length)};
+ }
+ };
+ }
+ }
+ }
+
+ SearchCursor.prototype = {
+ findNext: function() {return this.find(false);},
+ findPrevious: function() {return this.find(true);},
+
+ find: function(reverse) {
+ var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to);
+ function savePosAndFail(line) {
+ var pos = Pos(line, 0);
+ self.pos = {from: pos, to: pos};
+ self.atOccurrence = false;
+ return false;
+ }
+
+ for (;;) {
+ if (this.pos = this.matches(reverse, pos)) {
+ this.atOccurrence = true;
+ return this.pos.match || true;
+ }
+ if (reverse) {
+ if (!pos.line) return savePosAndFail(0);
+ pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length);
+ }
+ else {
+ var maxLine = this.doc.lineCount();
+ if (pos.line == maxLine - 1) return savePosAndFail(maxLine);
+ pos = Pos(pos.line + 1, 0);
+ }
+ }
+ },
+
+ from: function() {if (this.atOccurrence) return this.pos.from;},
+ to: function() {if (this.atOccurrence) return this.pos.to;},
+
+ replace: function(newText) {
+ if (!this.atOccurrence) return;
+ var lines = CodeMirror.splitLines(newText);
+ this.doc.replaceRange(lines, this.pos.from, this.pos.to);
+ this.pos.to = Pos(this.pos.from.line + lines.length - 1,
+ lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0));
+ }
+ };
+
+ // Maps a position in a case-folded line back to a position in the original line
+ // (compensating for codepoints increasing in number during folding)
+ function adjustPos(orig, folded, pos) {
+ if (orig.length == folded.length) return pos;
+ for (var pos1 = Math.min(pos, orig.length);;) {
+ var len1 = orig.slice(0, pos1).toLowerCase().length;
+ if (len1 < pos) ++pos1;
+ else if (len1 > pos) --pos1;
+ else return pos1;
+ }
+ }
+
+ CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) {
+ return new SearchCursor(this.doc, query, pos, caseFold);
+ });
+ CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) {
+ return new SearchCursor(this, query, pos, caseFold);
+ });
+
+ CodeMirror.defineExtension("selectMatches", function(query, caseFold) {
+ var ranges = [], next;
+ var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold);
+ while (next = cur.findNext()) {
+ if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break;
+ ranges.push({anchor: cur.from(), head: cur.to()});
+ }
+ if (ranges.length)
+ this.setSelections(ranges, 0);
+ });
+});
diff --git a/csslint/csslint.js b/csslint/csslint.js
new file mode 100644
index 00000000..6a60eb99
--- /dev/null
+++ b/csslint/csslint.js
@@ -0,0 +1,9259 @@
+/*!
+CSSLint
+Copyright (c) 2013 Nicole Sullivan and Nicholas C. Zakas. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+/* Build: v0.10.0 15-August-2013 01:07:22 */
+var exports = exports || {};
+var CSSLint = (function(){
+/*!
+Parser-Lib
+Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+/* Version v0.2.3, Build time: 19-June-2013 11:16:15 */
+var parserlib = {};
+(function(){
+
+
+/**
+ * A generic base to inherit from for any object
+ * that needs event handling.
+ * @class EventTarget
+ * @constructor
+ */
+function EventTarget(){
+
+ /**
+ * The array of listeners for various events.
+ * @type Object
+ * @property _listeners
+ * @private
+ */
+ this._listeners = {};
+}
+
+EventTarget.prototype = {
+
+ //restore constructor
+ constructor: EventTarget,
+
+ /**
+ * Adds a listener for a given event type.
+ * @param {String} type The type of event to add a listener for.
+ * @param {Function} listener The function to call when the event occurs.
+ * @return {void}
+ * @method addListener
+ */
+ addListener: function(type, listener){
+ if (!this._listeners[type]){
+ this._listeners[type] = [];
+ }
+
+ this._listeners[type].push(listener);
+ },
+
+ /**
+ * Fires an event based on the passed-in object.
+ * @param {Object|String} event An object with at least a 'type' attribute
+ * or a string indicating the event name.
+ * @return {void}
+ * @method fire
+ */
+ fire: function(event){
+ if (typeof event == "string"){
+ event = { type: event };
+ }
+ if (typeof event.target != "undefined"){
+ event.target = this;
+ }
+
+ if (typeof event.type == "undefined"){
+ throw new Error("Event object missing 'type' property.");
+ }
+
+ if (this._listeners[event.type]){
+
+ //create a copy of the array and use that so listeners can't chane
+ var listeners = this._listeners[event.type].concat();
+ for (var i=0, len=listeners.length; i < len; i++){
+ listeners[i].call(this, event);
+ }
+ }
+ },
+
+ /**
+ * Removes a listener for a given event type.
+ * @param {String} type The type of event to remove a listener from.
+ * @param {Function} listener The function to remove from the event.
+ * @return {void}
+ * @method removeListener
+ */
+ removeListener: function(type, listener){
+ if (this._listeners[type]){
+ var listeners = this._listeners[type];
+ for (var i=0, len=listeners.length; i < len; i++){
+ if (listeners[i] === listener){
+ listeners.splice(i, 1);
+ break;
+ }
+ }
+
+
+ }
+ }
+};
+/**
+ * Convenient way to read through strings.
+ * @namespace parserlib.util
+ * @class StringReader
+ * @constructor
+ * @param {String} text The text to read.
+ */
+function StringReader(text){
+
+ /**
+ * The input text with line endings normalized.
+ * @property _input
+ * @type String
+ * @private
+ */
+ this._input = text.replace(/\n\r?/g, "\n");
+
+
+ /**
+ * The row for the character to be read next.
+ * @property _line
+ * @type int
+ * @private
+ */
+ this._line = 1;
+
+
+ /**
+ * The column for the character to be read next.
+ * @property _col
+ * @type int
+ * @private
+ */
+ this._col = 1;
+
+ /**
+ * The index of the character in the input to be read next.
+ * @property _cursor
+ * @type int
+ * @private
+ */
+ this._cursor = 0;
+}
+
+StringReader.prototype = {
+
+ //restore constructor
+ constructor: StringReader,
+
+ //-------------------------------------------------------------------------
+ // Position info
+ //-------------------------------------------------------------------------
+
+ /**
+ * Returns the column of the character to be read next.
+ * @return {int} The column of the character to be read next.
+ * @method getCol
+ */
+ getCol: function(){
+ return this._col;
+ },
+
+ /**
+ * Returns the row of the character to be read next.
+ * @return {int} The row of the character to be read next.
+ * @method getLine
+ */
+ getLine: function(){
+ return this._line ;
+ },
+
+ /**
+ * Determines if you're at the end of the input.
+ * @return {Boolean} True if there's no more input, false otherwise.
+ * @method eof
+ */
+ eof: function(){
+ return (this._cursor == this._input.length);
+ },
+
+ //-------------------------------------------------------------------------
+ // Basic reading
+ //-------------------------------------------------------------------------
+
+ /**
+ * Reads the next character without advancing the cursor.
+ * @param {int} count How many characters to look ahead (default is 1).
+ * @return {String} The next character or null if there is no next character.
+ * @method peek
+ */
+ peek: function(count){
+ var c = null;
+ count = (typeof count == "undefined" ? 1 : count);
+
+ //if we're not at the end of the input...
+ if (this._cursor < this._input.length){
+
+ //get character and increment cursor and column
+ c = this._input.charAt(this._cursor + count - 1);
+ }
+
+ return c;
+ },
+
+ /**
+ * Reads the next character from the input and adjusts the row and column
+ * accordingly.
+ * @return {String} The next character or null if there is no next character.
+ * @method read
+ */
+ read: function(){
+ var c = null;
+
+ //if we're not at the end of the input...
+ if (this._cursor < this._input.length){
+
+ //if the last character was a newline, increment row count
+ //and reset column count
+ if (this._input.charAt(this._cursor) == "\n"){
+ this._line++;
+ this._col=1;
+ } else {
+ this._col++;
+ }
+
+ //get character and increment cursor and column
+ c = this._input.charAt(this._cursor++);
+ }
+
+ return c;
+ },
+
+ //-------------------------------------------------------------------------
+ // Misc
+ //-------------------------------------------------------------------------
+
+ /**
+ * Saves the current location so it can be returned to later.
+ * @method mark
+ * @return {void}
+ */
+ mark: function(){
+ this._bookmark = {
+ cursor: this._cursor,
+ line: this._line,
+ col: this._col
+ };
+ },
+
+ reset: function(){
+ if (this._bookmark){
+ this._cursor = this._bookmark.cursor;
+ this._line = this._bookmark.line;
+ this._col = this._bookmark.col;
+ delete this._bookmark;
+ }
+ },
+
+ //-------------------------------------------------------------------------
+ // Advanced reading
+ //-------------------------------------------------------------------------
+
+ /**
+ * Reads up to and including the given string. Throws an error if that
+ * string is not found.
+ * @param {String} pattern The string to read.
+ * @return {String} The string when it is found.
+ * @throws Error when the string pattern is not found.
+ * @method readTo
+ */
+ readTo: function(pattern){
+
+ var buffer = "",
+ c;
+
+ /*
+ * First, buffer must be the same length as the pattern.
+ * Then, buffer must end with the pattern or else reach the
+ * end of the input.
+ */
+ while (buffer.length < pattern.length || buffer.lastIndexOf(pattern) != buffer.length - pattern.length){
+ c = this.read();
+ if (c){
+ buffer += c;
+ } else {
+ throw new Error("Expected \"" + pattern + "\" at line " + this._line + ", col " + this._col + ".");
+ }
+ }
+
+ return buffer;
+
+ },
+
+ /**
+ * Reads characters while each character causes the given
+ * filter function to return true. The function is passed
+ * in each character and either returns true to continue
+ * reading or false to stop.
+ * @param {Function} filter The function to read on each character.
+ * @return {String} The string made up of all characters that passed the
+ * filter check.
+ * @method readWhile
+ */
+ readWhile: function(filter){
+
+ var buffer = "",
+ c = this.read();
+
+ while(c !== null && filter(c)){
+ buffer += c;
+ c = this.read();
+ }
+
+ return buffer;
+
+ },
+
+ /**
+ * Reads characters that match either text or a regular expression and
+ * returns those characters. If a match is found, the row and column
+ * are adjusted; if no match is found, the reader's state is unchanged.
+ * reading or false to stop.
+ * @param {String|RegExp} matchter If a string, then the literal string
+ * value is searched for. If a regular expression, then any string
+ * matching the pattern is search for.
+ * @return {String} The string made up of all characters that matched or
+ * null if there was no match.
+ * @method readMatch
+ */
+ readMatch: function(matcher){
+
+ var source = this._input.substring(this._cursor),
+ value = null;
+
+ //if it's a string, just do a straight match
+ if (typeof matcher == "string"){
+ if (source.indexOf(matcher) === 0){
+ value = this.readCount(matcher.length);
+ }
+ } else if (matcher instanceof RegExp){
+ if (matcher.test(source)){
+ value = this.readCount(RegExp.lastMatch.length);
+ }
+ }
+
+ return value;
+ },
+
+
+ /**
+ * Reads a given number of characters. If the end of the input is reached,
+ * it reads only the remaining characters and does not throw an error.
+ * @param {int} count The number of characters to read.
+ * @return {String} The string made up the read characters.
+ * @method readCount
+ */
+ readCount: function(count){
+ var buffer = "";
+
+ while(count--){
+ buffer += this.read();
+ }
+
+ return buffer;
+ }
+
+};
+/**
+ * Type to use when a syntax error occurs.
+ * @class SyntaxError
+ * @namespace parserlib.util
+ * @constructor
+ * @param {String} message The error message.
+ * @param {int} line The line at which the error occurred.
+ * @param {int} col The column at which the error occurred.
+ */
+function SyntaxError(message, line, col){
+
+ /**
+ * The column at which the error occurred.
+ * @type int
+ * @property col
+ */
+ this.col = col;
+
+ /**
+ * The line at which the error occurred.
+ * @type int
+ * @property line
+ */
+ this.line = line;
+
+ /**
+ * The text representation of the unit.
+ * @type String
+ * @property text
+ */
+ this.message = message;
+
+}
+
+//inherit from Error
+SyntaxError.prototype = new Error();
+/**
+ * Base type to represent a single syntactic unit.
+ * @class SyntaxUnit
+ * @namespace parserlib.util
+ * @constructor
+ * @param {String} text The text of the unit.
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ */
+function SyntaxUnit(text, line, col, type){
+
+
+ /**
+ * The column of text on which the unit resides.
+ * @type int
+ * @property col
+ */
+ this.col = col;
+
+ /**
+ * The line of text on which the unit resides.
+ * @type int
+ * @property line
+ */
+ this.line = line;
+
+ /**
+ * The text representation of the unit.
+ * @type String
+ * @property text
+ */
+ this.text = text;
+
+ /**
+ * The type of syntax unit.
+ * @type int
+ * @property type
+ */
+ this.type = type;
+}
+
+/**
+ * Create a new syntax unit based solely on the given token.
+ * Convenience method for creating a new syntax unit when
+ * it represents a single token instead of multiple.
+ * @param {Object} token The token object to represent.
+ * @return {parserlib.util.SyntaxUnit} The object representing the token.
+ * @static
+ * @method fromToken
+ */
+SyntaxUnit.fromToken = function(token){
+ return new SyntaxUnit(token.value, token.startLine, token.startCol);
+};
+
+SyntaxUnit.prototype = {
+
+ //restore constructor
+ constructor: SyntaxUnit,
+
+ /**
+ * Returns the text representation of the unit.
+ * @return {String} The text representation of the unit.
+ * @method valueOf
+ */
+ valueOf: function(){
+ return this.toString();
+ },
+
+ /**
+ * Returns the text representation of the unit.
+ * @return {String} The text representation of the unit.
+ * @method toString
+ */
+ toString: function(){
+ return this.text;
+ }
+
+};
+/*global StringReader, SyntaxError*/
+
+/**
+ * Generic TokenStream providing base functionality.
+ * @class TokenStreamBase
+ * @namespace parserlib.util
+ * @constructor
+ * @param {String|StringReader} input The text to tokenize or a reader from
+ * which to read the input.
+ */
+function TokenStreamBase(input, tokenData){
+
+ /**
+ * The string reader for easy access to the text.
+ * @type StringReader
+ * @property _reader
+ * @private
+ */
+ this._reader = input ? new StringReader(input.toString()) : null;
+
+ /**
+ * Token object for the last consumed token.
+ * @type Token
+ * @property _token
+ * @private
+ */
+ this._token = null;
+
+ /**
+ * The array of token information.
+ * @type Array
+ * @property _tokenData
+ * @private
+ */
+ this._tokenData = tokenData;
+
+ /**
+ * Lookahead token buffer.
+ * @type Array
+ * @property _lt
+ * @private
+ */
+ this._lt = [];
+
+ /**
+ * Lookahead token buffer index.
+ * @type int
+ * @property _ltIndex
+ * @private
+ */
+ this._ltIndex = 0;
+
+ this._ltIndexCache = [];
+}
+
+/**
+ * Accepts an array of token information and outputs
+ * an array of token data containing key-value mappings
+ * and matching functions that the TokenStream needs.
+ * @param {Array} tokens An array of token descriptors.
+ * @return {Array} An array of processed token data.
+ * @method createTokenData
+ * @static
+ */
+TokenStreamBase.createTokenData = function(tokens){
+
+ var nameMap = [],
+ typeMap = {},
+ tokenData = tokens.concat([]),
+ i = 0,
+ len = tokenData.length+1;
+
+ tokenData.UNKNOWN = -1;
+ tokenData.unshift({name:"EOF"});
+
+ for (; i < len; i++){
+ nameMap.push(tokenData[i].name);
+ tokenData[tokenData[i].name] = i;
+ if (tokenData[i].text){
+ typeMap[tokenData[i].text] = i;
+ }
+ }
+
+ tokenData.name = function(tt){
+ return nameMap[tt];
+ };
+
+ tokenData.type = function(c){
+ return typeMap[c];
+ };
+
+ return tokenData;
+};
+
+TokenStreamBase.prototype = {
+
+ //restore constructor
+ constructor: TokenStreamBase,
+
+ //-------------------------------------------------------------------------
+ // Matching methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Determines if the next token matches the given token type.
+ * If so, that token is consumed; if not, the token is placed
+ * back onto the token stream. You can pass in any number of
+ * token types and this will return true if any of the token
+ * types is found.
+ * @param {int|int[]} tokenTypes Either a single token type or an array of
+ * token types that the next token might be. If an array is passed,
+ * it's assumed that the token can be any of these.
+ * @param {variant} channel (Optional) The channel to read from. If not
+ * provided, reads from the default (unnamed) channel.
+ * @return {Boolean} True if the token type matches, false if not.
+ * @method match
+ */
+ match: function(tokenTypes, channel){
+
+ //always convert to an array, makes things easier
+ if (!(tokenTypes instanceof Array)){
+ tokenTypes = [tokenTypes];
+ }
+
+ var tt = this.get(channel),
+ i = 0,
+ len = tokenTypes.length;
+
+ while(i < len){
+ if (tt == tokenTypes[i++]){
+ return true;
+ }
+ }
+
+ //no match found, put the token back
+ this.unget();
+ return false;
+ },
+
+ /**
+ * Determines if the next token matches the given token type.
+ * If so, that token is consumed; if not, an error is thrown.
+ * @param {int|int[]} tokenTypes Either a single token type or an array of
+ * token types that the next token should be. If an array is passed,
+ * it's assumed that the token must be one of these.
+ * @param {variant} channel (Optional) The channel to read from. If not
+ * provided, reads from the default (unnamed) channel.
+ * @return {void}
+ * @method mustMatch
+ */
+ mustMatch: function(tokenTypes, channel){
+
+ var token;
+
+ //always convert to an array, makes things easier
+ if (!(tokenTypes instanceof Array)){
+ tokenTypes = [tokenTypes];
+ }
+
+ if (!this.match.apply(this, arguments)){
+ token = this.LT(1);
+ throw new SyntaxError("Expected " + this._tokenData[tokenTypes[0]].name +
+ " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
+ }
+ },
+
+ //-------------------------------------------------------------------------
+ // Consuming methods
+ //-------------------------------------------------------------------------
+
+ /**
+ * Keeps reading from the token stream until either one of the specified
+ * token types is found or until the end of the input is reached.
+ * @param {int|int[]} tokenTypes Either a single token type or an array of
+ * token types that the next token should be. If an array is passed,
+ * it's assumed that the token must be one of these.
+ * @param {variant} channel (Optional) The channel to read from. If not
+ * provided, reads from the default (unnamed) channel.
+ * @return {void}
+ * @method advance
+ */
+ advance: function(tokenTypes, channel){
+
+ while(this.LA(0) !== 0 && !this.match(tokenTypes, channel)){
+ this.get();
+ }
+
+ return this.LA(0);
+ },
+
+ /**
+ * Consumes the next token from the token stream.
+ * @return {int} The token type of the token that was just consumed.
+ * @method get
+ */
+ get: function(channel){
+
+ var tokenInfo = this._tokenData,
+ reader = this._reader,
+ value,
+ i =0,
+ len = tokenInfo.length,
+ found = false,
+ token,
+ info;
+
+ //check the lookahead buffer first
+ if (this._lt.length && this._ltIndex >= 0 && this._ltIndex < this._lt.length){
+
+ i++;
+ this._token = this._lt[this._ltIndex++];
+ info = tokenInfo[this._token.type];
+
+ //obey channels logic
+ while((info.channel !== undefined && channel !== info.channel) &&
+ this._ltIndex < this._lt.length){
+ this._token = this._lt[this._ltIndex++];
+ info = tokenInfo[this._token.type];
+ i++;
+ }
+
+ //here be dragons
+ if ((info.channel === undefined || channel === info.channel) &&
+ this._ltIndex <= this._lt.length){
+ this._ltIndexCache.push(i);
+ return this._token.type;
+ }
+ }
+
+ //call token retriever method
+ token = this._getToken();
+
+ //if it should be hidden, don't save a token
+ if (token.type > -1 && !tokenInfo[token.type].hide){
+
+ //apply token channel
+ token.channel = tokenInfo[token.type].channel;
+
+ //save for later
+ this._token = token;
+ this._lt.push(token);
+
+ //save space that will be moved (must be done before array is truncated)
+ this._ltIndexCache.push(this._lt.length - this._ltIndex + i);
+
+ //keep the buffer under 5 items
+ if (this._lt.length > 5){
+ this._lt.shift();
+ }
+
+ //also keep the shift buffer under 5 items
+ if (this._ltIndexCache.length > 5){
+ this._ltIndexCache.shift();
+ }
+
+ //update lookahead index
+ this._ltIndex = this._lt.length;
+ }
+
+ /*
+ * Skip to the next token if:
+ * 1. The token type is marked as hidden.
+ * 2. The token type has a channel specified and it isn't the current channel.
+ */
+ info = tokenInfo[token.type];
+ if (info &&
+ (info.hide ||
+ (info.channel !== undefined && channel !== info.channel))){
+ return this.get(channel);
+ } else {
+ //return just the type
+ return token.type;
+ }
+ },
+
+ /**
+ * Looks ahead a certain number of tokens and returns the token type at
+ * that position. This will throw an error if you lookahead past the
+ * end of input, past the size of the lookahead buffer, or back past
+ * the first token in the lookahead buffer.
+ * @param {int} The index of the token type to retrieve. 0 for the
+ * current token, 1 for the next, -1 for the previous, etc.
+ * @return {int} The token type of the token in the given position.
+ * @method LA
+ */
+ LA: function(index){
+ var total = index,
+ tt;
+ if (index > 0){
+ //TODO: Store 5 somewhere
+ if (index > 5){
+ throw new Error("Too much lookahead.");
+ }
+
+ //get all those tokens
+ while(total){
+ tt = this.get();
+ total--;
+ }
+
+ //unget all those tokens
+ while(total < index){
+ this.unget();
+ total++;
+ }
+ } else if (index < 0){
+
+ if(this._lt[this._ltIndex+index]){
+ tt = this._lt[this._ltIndex+index].type;
+ } else {
+ throw new Error("Too much lookbehind.");
+ }
+
+ } else {
+ tt = this._token.type;
+ }
+
+ return tt;
+
+ },
+
+ /**
+ * Looks ahead a certain number of tokens and returns the token at
+ * that position. This will throw an error if you lookahead past the
+ * end of input, past the size of the lookahead buffer, or back past
+ * the first token in the lookahead buffer.
+ * @param {int} The index of the token type to retrieve. 0 for the
+ * current token, 1 for the next, -1 for the previous, etc.
+ * @return {Object} The token of the token in the given position.
+ * @method LA
+ */
+ LT: function(index){
+
+ //lookahead first to prime the token buffer
+ this.LA(index);
+
+ //now find the token, subtract one because _ltIndex is already at the next index
+ return this._lt[this._ltIndex+index-1];
+ },
+
+ /**
+ * Returns the token type for the next token in the stream without
+ * consuming it.
+ * @return {int} The token type of the next token in the stream.
+ * @method peek
+ */
+ peek: function(){
+ return this.LA(1);
+ },
+
+ /**
+ * Returns the actual token object for the last consumed token.
+ * @return {Token} The token object for the last consumed token.
+ * @method token
+ */
+ token: function(){
+ return this._token;
+ },
+
+ /**
+ * Returns the name of the token for the given token type.
+ * @param {int} tokenType The type of token to get the name of.
+ * @return {String} The name of the token or "UNKNOWN_TOKEN" for any
+ * invalid token type.
+ * @method tokenName
+ */
+ tokenName: function(tokenType){
+ if (tokenType < 0 || tokenType > this._tokenData.length){
+ return "UNKNOWN_TOKEN";
+ } else {
+ return this._tokenData[tokenType].name;
+ }
+ },
+
+ /**
+ * Returns the token type value for the given token name.
+ * @param {String} tokenName The name of the token whose value should be returned.
+ * @return {int} The token type value for the given token name or -1
+ * for an unknown token.
+ * @method tokenName
+ */
+ tokenType: function(tokenName){
+ return this._tokenData[tokenName] || -1;
+ },
+
+ /**
+ * Returns the last consumed token to the token stream.
+ * @method unget
+ */
+ unget: function(){
+ //if (this._ltIndex > -1){
+ if (this._ltIndexCache.length){
+ this._ltIndex -= this._ltIndexCache.pop();//--;
+ this._token = this._lt[this._ltIndex - 1];
+ } else {
+ throw new Error("Too much lookahead.");
+ }
+ }
+
+};
+
+
+
+
+parserlib.util = {
+StringReader: StringReader,
+SyntaxError : SyntaxError,
+SyntaxUnit : SyntaxUnit,
+EventTarget : EventTarget,
+TokenStreamBase : TokenStreamBase
+};
+})();
+
+
+/*
+Parser-Lib
+Copyright (c) 2009-2011 Nicholas C. Zakas. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+/* Version v0.2.3, Build time: 19-June-2013 11:16:15 */
+(function(){
+var EventTarget = parserlib.util.EventTarget,
+TokenStreamBase = parserlib.util.TokenStreamBase,
+StringReader = parserlib.util.StringReader,
+SyntaxError = parserlib.util.SyntaxError,
+SyntaxUnit = parserlib.util.SyntaxUnit;
+
+
+var Colors = {
+ aliceblue :"#f0f8ff",
+ antiquewhite :"#faebd7",
+ aqua :"#00ffff",
+ aquamarine :"#7fffd4",
+ azure :"#f0ffff",
+ beige :"#f5f5dc",
+ bisque :"#ffe4c4",
+ black :"#000000",
+ blanchedalmond :"#ffebcd",
+ blue :"#0000ff",
+ blueviolet :"#8a2be2",
+ brown :"#a52a2a",
+ burlywood :"#deb887",
+ cadetblue :"#5f9ea0",
+ chartreuse :"#7fff00",
+ chocolate :"#d2691e",
+ coral :"#ff7f50",
+ cornflowerblue :"#6495ed",
+ cornsilk :"#fff8dc",
+ crimson :"#dc143c",
+ cyan :"#00ffff",
+ darkblue :"#00008b",
+ darkcyan :"#008b8b",
+ darkgoldenrod :"#b8860b",
+ darkgray :"#a9a9a9",
+ darkgreen :"#006400",
+ darkkhaki :"#bdb76b",
+ darkmagenta :"#8b008b",
+ darkolivegreen :"#556b2f",
+ darkorange :"#ff8c00",
+ darkorchid :"#9932cc",
+ darkred :"#8b0000",
+ darksalmon :"#e9967a",
+ darkseagreen :"#8fbc8f",
+ darkslateblue :"#483d8b",
+ darkslategray :"#2f4f4f",
+ darkturquoise :"#00ced1",
+ darkviolet :"#9400d3",
+ deeppink :"#ff1493",
+ deepskyblue :"#00bfff",
+ dimgray :"#696969",
+ dodgerblue :"#1e90ff",
+ firebrick :"#b22222",
+ floralwhite :"#fffaf0",
+ forestgreen :"#228b22",
+ fuchsia :"#ff00ff",
+ gainsboro :"#dcdcdc",
+ ghostwhite :"#f8f8ff",
+ gold :"#ffd700",
+ goldenrod :"#daa520",
+ gray :"#808080",
+ green :"#008000",
+ greenyellow :"#adff2f",
+ honeydew :"#f0fff0",
+ hotpink :"#ff69b4",
+ indianred :"#cd5c5c",
+ indigo :"#4b0082",
+ ivory :"#fffff0",
+ khaki :"#f0e68c",
+ lavender :"#e6e6fa",
+ lavenderblush :"#fff0f5",
+ lawngreen :"#7cfc00",
+ lemonchiffon :"#fffacd",
+ lightblue :"#add8e6",
+ lightcoral :"#f08080",
+ lightcyan :"#e0ffff",
+ lightgoldenrodyellow :"#fafad2",
+ lightgray :"#d3d3d3",
+ lightgreen :"#90ee90",
+ lightpink :"#ffb6c1",
+ lightsalmon :"#ffa07a",
+ lightseagreen :"#20b2aa",
+ lightskyblue :"#87cefa",
+ lightslategray :"#778899",
+ lightsteelblue :"#b0c4de",
+ lightyellow :"#ffffe0",
+ lime :"#00ff00",
+ limegreen :"#32cd32",
+ linen :"#faf0e6",
+ magenta :"#ff00ff",
+ maroon :"#800000",
+ mediumaquamarine:"#66cdaa",
+ mediumblue :"#0000cd",
+ mediumorchid :"#ba55d3",
+ mediumpurple :"#9370d8",
+ mediumseagreen :"#3cb371",
+ mediumslateblue :"#7b68ee",
+ mediumspringgreen :"#00fa9a",
+ mediumturquoise :"#48d1cc",
+ mediumvioletred :"#c71585",
+ midnightblue :"#191970",
+ mintcream :"#f5fffa",
+ mistyrose :"#ffe4e1",
+ moccasin :"#ffe4b5",
+ navajowhite :"#ffdead",
+ navy :"#000080",
+ oldlace :"#fdf5e6",
+ olive :"#808000",
+ olivedrab :"#6b8e23",
+ orange :"#ffa500",
+ orangered :"#ff4500",
+ orchid :"#da70d6",
+ palegoldenrod :"#eee8aa",
+ palegreen :"#98fb98",
+ paleturquoise :"#afeeee",
+ palevioletred :"#d87093",
+ papayawhip :"#ffefd5",
+ peachpuff :"#ffdab9",
+ peru :"#cd853f",
+ pink :"#ffc0cb",
+ plum :"#dda0dd",
+ powderblue :"#b0e0e6",
+ purple :"#800080",
+ red :"#ff0000",
+ rosybrown :"#bc8f8f",
+ royalblue :"#4169e1",
+ saddlebrown :"#8b4513",
+ salmon :"#fa8072",
+ sandybrown :"#f4a460",
+ seagreen :"#2e8b57",
+ seashell :"#fff5ee",
+ sienna :"#a0522d",
+ silver :"#c0c0c0",
+ skyblue :"#87ceeb",
+ slateblue :"#6a5acd",
+ slategray :"#708090",
+ snow :"#fffafa",
+ springgreen :"#00ff7f",
+ steelblue :"#4682b4",
+ tan :"#d2b48c",
+ teal :"#008080",
+ thistle :"#d8bfd8",
+ tomato :"#ff6347",
+ turquoise :"#40e0d0",
+ violet :"#ee82ee",
+ wheat :"#f5deb3",
+ white :"#ffffff",
+ whitesmoke :"#f5f5f5",
+ yellow :"#ffff00",
+ yellowgreen :"#9acd32",
+ //CSS2 system colors http://www.w3.org/TR/css3-color/#css2-system
+ activeBorder :"Active window border.",
+ activecaption :"Active window caption.",
+ appworkspace :"Background color of multiple document interface.",
+ background :"Desktop background.",
+ buttonface :"The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.",
+ buttonhighlight :"The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
+ buttonshadow :"The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
+ buttontext :"Text on push buttons.",
+ captiontext :"Text in caption, size box, and scrollbar arrow box.",
+ graytext :"Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.",
+ highlight :"Item(s) selected in a control.",
+ highlighttext :"Text of item(s) selected in a control.",
+ inactiveborder :"Inactive window border.",
+ inactivecaption :"Inactive window caption.",
+ inactivecaptiontext :"Color of text in an inactive caption.",
+ infobackground :"Background color for tooltip controls.",
+ infotext :"Text color for tooltip controls.",
+ menu :"Menu background.",
+ menutext :"Text in menus.",
+ scrollbar :"Scroll bar gray area.",
+ threeddarkshadow :"The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
+ threedface :"The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
+ threedhighlight :"The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
+ threedlightshadow :"The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
+ threedshadow :"The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
+ window :"Window background.",
+ windowframe :"Window frame.",
+ windowtext :"Text in windows."
+};
+/*global SyntaxUnit, Parser*/
+/**
+ * Represents a selector combinator (whitespace, +, >).
+ * @namespace parserlib.css
+ * @class Combinator
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ * @param {String} text The text representation of the unit.
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ */
+function Combinator(text, line, col){
+
+ SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE);
+
+ /**
+ * The type of modifier.
+ * @type String
+ * @property type
+ */
+ this.type = "unknown";
+
+ //pretty simple
+ if (/^\s+$/.test(text)){
+ this.type = "descendant";
+ } else if (text == ">"){
+ this.type = "child";
+ } else if (text == "+"){
+ this.type = "adjacent-sibling";
+ } else if (text == "~"){
+ this.type = "sibling";
+ }
+
+}
+
+Combinator.prototype = new SyntaxUnit();
+Combinator.prototype.constructor = Combinator;
+
+
+/*global SyntaxUnit, Parser*/
+/**
+ * Represents a media feature, such as max-width:500.
+ * @namespace parserlib.css
+ * @class MediaFeature
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ * @param {SyntaxUnit} name The name of the feature.
+ * @param {SyntaxUnit} value The value of the feature or null if none.
+ */
+function MediaFeature(name, value){
+
+ SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE);
+
+ /**
+ * The name of the media feature
+ * @type String
+ * @property name
+ */
+ this.name = name;
+
+ /**
+ * The value for the feature or null if there is none.
+ * @type SyntaxUnit
+ * @property value
+ */
+ this.value = value;
+}
+
+MediaFeature.prototype = new SyntaxUnit();
+MediaFeature.prototype.constructor = MediaFeature;
+
+
+/*global SyntaxUnit, Parser*/
+/**
+ * Represents an individual media query.
+ * @namespace parserlib.css
+ * @class MediaQuery
+ * @extends parserlib.util.SyntaxUnit
+ * @constructor
+ * @param {String} modifier The modifier "not" or "only" (or null).
+ * @param {String} mediaType The type of media (i.e., "print").
+ * @param {Array} parts Array of selectors parts making up this selector.
+ * @param {int} line The line of text on which the unit resides.
+ * @param {int} col The column of text on which the unit resides.
+ */
+function MediaQuery(modifier, mediaType, features, line, col){
+
+ SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE);
+
+ /**
+ * The media modifier ("not" or "only")
+ * @type String
+ * @property modifier
+ */
+ this.modifier = modifier;
+
+ /**
+ * The mediaType (i.e., "print")
+ * @type String
+ * @property mediaType
+ */
+ this.mediaType = mediaType;
+
+ /**
+ * The parts that make up the selector.
+ * @type Array
+ * @property features
+ */
+ this.features = features;
+
+}
+
+MediaQuery.prototype = new SyntaxUnit();
+MediaQuery.prototype.constructor = MediaQuery;
+
+
+/*global Tokens, TokenStream, SyntaxError, Properties, Validation, ValidationError, SyntaxUnit,
+ PropertyValue, PropertyValuePart, SelectorPart, SelectorSubPart, Selector,
+ PropertyName, Combinator, MediaFeature, MediaQuery, EventTarget */
+
+/**
+ * A CSS3 parser.
+ * @namespace parserlib.css
+ * @class Parser
+ * @constructor
+ * @param {Object} options (Optional) Various options for the parser:
+ * starHack (true|false) to allow IE6 star hack as valid,
+ * underscoreHack (true|false) to interpret leading underscores
+ * as IE6-7 targeting for known properties, ieFilters (true|false)
+ * to indicate that IE < 8 filters should be accepted and not throw
+ * syntax errors.
+ */
+function Parser(options){
+
+ //inherit event functionality
+ EventTarget.call(this);
+
+
+ this.options = options || {};
+
+ this._tokenStream = null;
+}
+
+//Static constants
+Parser.DEFAULT_TYPE = 0;
+Parser.COMBINATOR_TYPE = 1;
+Parser.MEDIA_FEATURE_TYPE = 2;
+Parser.MEDIA_QUERY_TYPE = 3;
+Parser.PROPERTY_NAME_TYPE = 4;
+Parser.PROPERTY_VALUE_TYPE = 5;
+Parser.PROPERTY_VALUE_PART_TYPE = 6;
+Parser.SELECTOR_TYPE = 7;
+Parser.SELECTOR_PART_TYPE = 8;
+Parser.SELECTOR_SUB_PART_TYPE = 9;
+
+Parser.prototype = function(){
+
+ var proto = new EventTarget(), //new prototype
+ prop,
+ additions = {
+
+ //restore constructor
+ constructor: Parser,
+
+ //instance constants - yuck
+ DEFAULT_TYPE : 0,
+ COMBINATOR_TYPE : 1,
+ MEDIA_FEATURE_TYPE : 2,
+ MEDIA_QUERY_TYPE : 3,
+ PROPERTY_NAME_TYPE : 4,
+ PROPERTY_VALUE_TYPE : 5,
+ PROPERTY_VALUE_PART_TYPE : 6,
+ SELECTOR_TYPE : 7,
+ SELECTOR_PART_TYPE : 8,
+ SELECTOR_SUB_PART_TYPE : 9,
+
+ //-----------------------------------------------------------------
+ // Grammar
+ //-----------------------------------------------------------------
+
+ _stylesheet: function(){
+
+ /*
+ * stylesheet
+ * : [ CHARSET_SYM S* STRING S* ';' ]?
+ * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
+ * [ namespace [S|CDO|CDC]* ]*
+ * [ [ ruleset | media | page | font_face | keyframes ] [S|CDO|CDC]* ]*
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ charset = null,
+ count,
+ token,
+ tt;
+
+ this.fire("startstylesheet");
+
+ //try to read character set
+ this._charset();
+
+ this._skipCruft();
+
+ //try to read imports - may be more than one
+ while (tokenStream.peek() == Tokens.IMPORT_SYM){
+ this._import();
+ this._skipCruft();
+ }
+
+ //try to read namespaces - may be more than one
+ while (tokenStream.peek() == Tokens.NAMESPACE_SYM){
+ this._namespace();
+ this._skipCruft();
+ }
+
+ //get the next token
+ tt = tokenStream.peek();
+
+ //try to read the rest
+ while(tt > Tokens.EOF){
+
+ try {
+
+ switch(tt){
+ case Tokens.MEDIA_SYM:
+ this._media();
+ this._skipCruft();
+ break;
+ case Tokens.PAGE_SYM:
+ this._page();
+ this._skipCruft();
+ break;
+ case Tokens.FONT_FACE_SYM:
+ this._font_face();
+ this._skipCruft();
+ break;
+ case Tokens.KEYFRAMES_SYM:
+ this._keyframes();
+ this._skipCruft();
+ break;
+ case Tokens.VIEWPORT_SYM:
+ this._viewport();
+ this._skipCruft();
+ break;
+ case Tokens.UNKNOWN_SYM: //unknown @ rule
+ tokenStream.get();
+ if (!this.options.strict){
+
+ //fire error event
+ this.fire({
+ type: "error",
+ error: null,
+ message: "Unknown @ rule: " + tokenStream.LT(0).value + ".",
+ line: tokenStream.LT(0).startLine,
+ col: tokenStream.LT(0).startCol
+ });
+
+ //skip braces
+ count=0;
+ while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) == Tokens.LBRACE){
+ count++; //keep track of nesting depth
+ }
+
+ while(count){
+ tokenStream.advance([Tokens.RBRACE]);
+ count--;
+ }
+
+ } else {
+ //not a syntax error, rethrow it
+ throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol);
+ }
+ break;
+ case Tokens.S:
+ this._readWhitespace();
+ break;
+ default:
+ if(!this._ruleset()){
+
+ //error handling for known issues
+ switch(tt){
+ case Tokens.CHARSET_SYM:
+ token = tokenStream.LT(1);
+ this._charset(false);
+ throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol);
+ case Tokens.IMPORT_SYM:
+ token = tokenStream.LT(1);
+ this._import(false);
+ throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol);
+ case Tokens.NAMESPACE_SYM:
+ token = tokenStream.LT(1);
+ this._namespace(false);
+ throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol);
+ default:
+ tokenStream.get(); //get the last token
+ this._unexpectedToken(tokenStream.token());
+ }
+
+ }
+ }
+ } catch(ex) {
+ if (ex instanceof SyntaxError && !this.options.strict){
+ this.fire({
+ type: "error",
+ error: ex,
+ message: ex.message,
+ line: ex.line,
+ col: ex.col
+ });
+ } else {
+ throw ex;
+ }
+ }
+
+ tt = tokenStream.peek();
+ }
+
+ if (tt != Tokens.EOF){
+ this._unexpectedToken(tokenStream.token());
+ }
+
+ this.fire("endstylesheet");
+ },
+
+ _charset: function(emit){
+ var tokenStream = this._tokenStream,
+ charset,
+ token,
+ line,
+ col;
+
+ if (tokenStream.match(Tokens.CHARSET_SYM)){
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol;
+
+ this._readWhitespace();
+ tokenStream.mustMatch(Tokens.STRING);
+
+ token = tokenStream.token();
+ charset = token.value;
+
+ this._readWhitespace();
+ tokenStream.mustMatch(Tokens.SEMICOLON);
+
+ if (emit !== false){
+ this.fire({
+ type: "charset",
+ charset:charset,
+ line: line,
+ col: col
+ });
+ }
+ }
+ },
+
+ _import: function(emit){
+ /*
+ * import
+ * : IMPORT_SYM S*
+ * [STRING|URI] S* media_query_list? ';' S*
+ */
+
+ var tokenStream = this._tokenStream,
+ tt,
+ uri,
+ importToken,
+ mediaList = [];
+
+ //read import symbol
+ tokenStream.mustMatch(Tokens.IMPORT_SYM);
+ importToken = tokenStream.token();
+ this._readWhitespace();
+
+ tokenStream.mustMatch([Tokens.STRING, Tokens.URI]);
+
+ //grab the URI value
+ uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1");
+
+ this._readWhitespace();
+
+ mediaList = this._media_query_list();
+
+ //must end with a semicolon
+ tokenStream.mustMatch(Tokens.SEMICOLON);
+ this._readWhitespace();
+
+ if (emit !== false){
+ this.fire({
+ type: "import",
+ uri: uri,
+ media: mediaList,
+ line: importToken.startLine,
+ col: importToken.startCol
+ });
+ }
+
+ },
+
+ _namespace: function(emit){
+ /*
+ * namespace
+ * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
+ */
+
+ var tokenStream = this._tokenStream,
+ line,
+ col,
+ prefix,
+ uri;
+
+ //read import symbol
+ tokenStream.mustMatch(Tokens.NAMESPACE_SYM);
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol;
+ this._readWhitespace();
+
+ //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT
+ if (tokenStream.match(Tokens.IDENT)){
+ prefix = tokenStream.token().value;
+ this._readWhitespace();
+ }
+
+ tokenStream.mustMatch([Tokens.STRING, Tokens.URI]);
+ /*if (!tokenStream.match(Tokens.STRING)){
+ tokenStream.mustMatch(Tokens.URI);
+ }*/
+
+ //grab the URI value
+ uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1");
+
+ this._readWhitespace();
+
+ //must end with a semicolon
+ tokenStream.mustMatch(Tokens.SEMICOLON);
+ this._readWhitespace();
+
+ if (emit !== false){
+ this.fire({
+ type: "namespace",
+ prefix: prefix,
+ uri: uri,
+ line: line,
+ col: col
+ });
+ }
+
+ },
+
+ _media: function(){
+ /*
+ * media
+ * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S*
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ line,
+ col,
+ mediaList;// = [];
+
+ //look for @media
+ tokenStream.mustMatch(Tokens.MEDIA_SYM);
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol;
+
+ this._readWhitespace();
+
+ mediaList = this._media_query_list();
+
+ tokenStream.mustMatch(Tokens.LBRACE);
+ this._readWhitespace();
+
+ this.fire({
+ type: "startmedia",
+ media: mediaList,
+ line: line,
+ col: col
+ });
+
+ while(true) {
+ if (tokenStream.peek() == Tokens.PAGE_SYM){
+ this._page();
+ } else if (tokenStream.peek() == Tokens.FONT_FACE_SYM){
+ this._font_face();
+ } else if (!this._ruleset()){
+ break;
+ }
+ }
+
+ tokenStream.mustMatch(Tokens.RBRACE);
+ this._readWhitespace();
+
+ this.fire({
+ type: "endmedia",
+ media: mediaList,
+ line: line,
+ col: col
+ });
+ },
+
+
+ //CSS3 Media Queries
+ _media_query_list: function(){
+ /*
+ * media_query_list
+ * : S* [media_query [ ',' S* media_query ]* ]?
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ mediaList = [];
+
+
+ this._readWhitespace();
+
+ if (tokenStream.peek() == Tokens.IDENT || tokenStream.peek() == Tokens.LPAREN){
+ mediaList.push(this._media_query());
+ }
+
+ while(tokenStream.match(Tokens.COMMA)){
+ this._readWhitespace();
+ mediaList.push(this._media_query());
+ }
+
+ return mediaList;
+ },
+
+ /*
+ * Note: "expression" in the grammar maps to the _media_expression
+ * method.
+
+ */
+ _media_query: function(){
+ /*
+ * media_query
+ * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]*
+ * | expression [ AND S* expression ]*
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ type = null,
+ ident = null,
+ token = null,
+ expressions = [];
+
+ if (tokenStream.match(Tokens.IDENT)){
+ ident = tokenStream.token().value.toLowerCase();
+
+ //since there's no custom tokens for these, need to manually check
+ if (ident != "only" && ident != "not"){
+ tokenStream.unget();
+ ident = null;
+ } else {
+ token = tokenStream.token();
+ }
+ }
+
+ this._readWhitespace();
+
+ if (tokenStream.peek() == Tokens.IDENT){
+ type = this._media_type();
+ if (token === null){
+ token = tokenStream.token();
+ }
+ } else if (tokenStream.peek() == Tokens.LPAREN){
+ if (token === null){
+ token = tokenStream.LT(1);
+ }
+ expressions.push(this._media_expression());
+ }
+
+ if (type === null && expressions.length === 0){
+ return null;
+ } else {
+ this._readWhitespace();
+ while (tokenStream.match(Tokens.IDENT)){
+ if (tokenStream.token().value.toLowerCase() != "and"){
+ this._unexpectedToken(tokenStream.token());
+ }
+
+ this._readWhitespace();
+ expressions.push(this._media_expression());
+ }
+ }
+
+ return new MediaQuery(ident, type, expressions, token.startLine, token.startCol);
+ },
+
+ //CSS3 Media Queries
+ _media_type: function(){
+ /*
+ * media_type
+ * : IDENT
+ * ;
+ */
+ return this._media_feature();
+ },
+
+ /**
+ * Note: in CSS3 Media Queries, this is called "expression".
+ * Renamed here to avoid conflict with CSS3 Selectors
+ * definition of "expression". Also note that "expr" in the
+ * grammar now maps to "expression" from CSS3 selectors.
+ * @method _media_expression
+ * @private
+ */
+ _media_expression: function(){
+ /*
+ * expression
+ * : '(' S* media_feature S* [ ':' S* expr ]? ')' S*
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ feature = null,
+ token,
+ expression = null;
+
+ tokenStream.mustMatch(Tokens.LPAREN);
+
+ feature = this._media_feature();
+ this._readWhitespace();
+
+ if (tokenStream.match(Tokens.COLON)){
+ this._readWhitespace();
+ token = tokenStream.LT(1);
+ expression = this._expression();
+ }
+
+ tokenStream.mustMatch(Tokens.RPAREN);
+ this._readWhitespace();
+
+ return new MediaFeature(feature, (expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null));
+ },
+
+ //CSS3 Media Queries
+ _media_feature: function(){
+ /*
+ * media_feature
+ * : IDENT
+ * ;
+ */
+ var tokenStream = this._tokenStream;
+
+ tokenStream.mustMatch(Tokens.IDENT);
+
+ return SyntaxUnit.fromToken(tokenStream.token());
+ },
+
+ //CSS3 Paged Media
+ _page: function(){
+ /*
+ * page:
+ * PAGE_SYM S* IDENT? pseudo_page? S*
+ * '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S*
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ line,
+ col,
+ identifier = null,
+ pseudoPage = null;
+
+ //look for @page
+ tokenStream.mustMatch(Tokens.PAGE_SYM);
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol;
+
+ this._readWhitespace();
+
+ if (tokenStream.match(Tokens.IDENT)){
+ identifier = tokenStream.token().value;
+
+ //The value 'auto' may not be used as a page name and MUST be treated as a syntax error.
+ if (identifier.toLowerCase() === "auto"){
+ this._unexpectedToken(tokenStream.token());
+ }
+ }
+
+ //see if there's a colon upcoming
+ if (tokenStream.peek() == Tokens.COLON){
+ pseudoPage = this._pseudo_page();
+ }
+
+ this._readWhitespace();
+
+ this.fire({
+ type: "startpage",
+ id: identifier,
+ pseudo: pseudoPage,
+ line: line,
+ col: col
+ });
+
+ this._readDeclarations(true, true);
+
+ this.fire({
+ type: "endpage",
+ id: identifier,
+ pseudo: pseudoPage,
+ line: line,
+ col: col
+ });
+
+ },
+
+ //CSS3 Paged Media
+ _margin: function(){
+ /*
+ * margin :
+ * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ line,
+ col,
+ marginSym = this._margin_sym();
+
+ if (marginSym){
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol;
+
+ this.fire({
+ type: "startpagemargin",
+ margin: marginSym,
+ line: line,
+ col: col
+ });
+
+ this._readDeclarations(true);
+
+ this.fire({
+ type: "endpagemargin",
+ margin: marginSym,
+ line: line,
+ col: col
+ });
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ //CSS3 Paged Media
+ _margin_sym: function(){
+
+ /*
+ * margin_sym :
+ * TOPLEFTCORNER_SYM |
+ * TOPLEFT_SYM |
+ * TOPCENTER_SYM |
+ * TOPRIGHT_SYM |
+ * TOPRIGHTCORNER_SYM |
+ * BOTTOMLEFTCORNER_SYM |
+ * BOTTOMLEFT_SYM |
+ * BOTTOMCENTER_SYM |
+ * BOTTOMRIGHT_SYM |
+ * BOTTOMRIGHTCORNER_SYM |
+ * LEFTTOP_SYM |
+ * LEFTMIDDLE_SYM |
+ * LEFTBOTTOM_SYM |
+ * RIGHTTOP_SYM |
+ * RIGHTMIDDLE_SYM |
+ * RIGHTBOTTOM_SYM
+ * ;
+ */
+
+ var tokenStream = this._tokenStream;
+
+ if(tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM,
+ Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM,
+ Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM,
+ Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM,
+ Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM,
+ Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM,
+ Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM]))
+ {
+ return SyntaxUnit.fromToken(tokenStream.token());
+ } else {
+ return null;
+ }
+
+ },
+
+ _pseudo_page: function(){
+ /*
+ * pseudo_page
+ * : ':' IDENT
+ * ;
+ */
+
+ var tokenStream = this._tokenStream;
+
+ tokenStream.mustMatch(Tokens.COLON);
+ tokenStream.mustMatch(Tokens.IDENT);
+
+ //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed
+
+ return tokenStream.token().value;
+ },
+
+ _font_face: function(){
+ /*
+ * font_face
+ * : FONT_FACE_SYM S*
+ * '{' S* declaration [ ';' S* declaration ]* '}' S*
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ line,
+ col;
+
+ //look for @page
+ tokenStream.mustMatch(Tokens.FONT_FACE_SYM);
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol;
+
+ this._readWhitespace();
+
+ this.fire({
+ type: "startfontface",
+ line: line,
+ col: col
+ });
+
+ this._readDeclarations(true);
+
+ this.fire({
+ type: "endfontface",
+ line: line,
+ col: col
+ });
+ },
+
+ _viewport: function(){
+ /*
+ * viewport
+ * : VIEWPORT_SYM S*
+ * '{' S* declaration? [ ';' S* declaration? ]* '}' S*
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ line,
+ col;
+
+ tokenStream.mustMatch(Tokens.VIEWPORT_SYM);
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol;
+
+ this._readWhitespace();
+
+ this.fire({
+ type: "startviewport",
+ line: line,
+ col: col
+ });
+
+ this._readDeclarations(true);
+
+ this.fire({
+ type: "endviewport",
+ line: line,
+ col: col
+ });
+
+ },
+
+ _operator: function(inFunction){
+
+ /*
+ * operator (outside function)
+ * : '/' S* | ',' S* | /( empty )/
+ * operator (inside function)
+ * : '/' S* | '+' S* | '*' S* | '-' S* /( empty )/
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ token = null;
+
+ if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) ||
+ (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))){
+ token = tokenStream.token();
+ this._readWhitespace();
+ }
+ return token ? PropertyValuePart.fromToken(token) : null;
+
+ },
+
+ _combinator: function(){
+
+ /*
+ * combinator
+ * : PLUS S* | GREATER S* | TILDE S* | S+
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ value = null,
+ token;
+
+ if(tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])){
+ token = tokenStream.token();
+ value = new Combinator(token.value, token.startLine, token.startCol);
+ this._readWhitespace();
+ }
+
+ return value;
+ },
+
+ _unary_operator: function(){
+
+ /*
+ * unary_operator
+ * : '-' | '+'
+ * ;
+ */
+
+ var tokenStream = this._tokenStream;
+
+ if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])){
+ return tokenStream.token().value;
+ } else {
+ return null;
+ }
+ },
+
+ _property: function(){
+
+ /*
+ * property
+ * : IDENT S*
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ value = null,
+ hack = null,
+ tokenValue,
+ token,
+ line,
+ col;
+
+ //check for star hack - throws error if not allowed
+ if (tokenStream.peek() == Tokens.STAR && this.options.starHack){
+ tokenStream.get();
+ token = tokenStream.token();
+ hack = token.value;
+ line = token.startLine;
+ col = token.startCol;
+ }
+
+ if(tokenStream.match(Tokens.IDENT)){
+ token = tokenStream.token();
+ tokenValue = token.value;
+
+ //check for underscore hack - no error if not allowed because it's valid CSS syntax
+ if (tokenValue.charAt(0) == "_" && this.options.underscoreHack){
+ hack = "_";
+ tokenValue = tokenValue.substring(1);
+ }
+
+ value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol));
+ this._readWhitespace();
+ }
+
+ return value;
+ },
+
+ //Augmented with CSS3 Selectors
+ _ruleset: function(){
+ /*
+ * ruleset
+ * : selectors_group
+ * '{' S* declaration? [ ';' S* declaration? ]* '}' S*
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ tt,
+ selectors;
+
+
+ /*
+ * Error Recovery: If even a single selector fails to parse,
+ * then the entire ruleset should be thrown away.
+ */
+ try {
+ selectors = this._selectors_group();
+ } catch (ex){
+ if (ex instanceof SyntaxError && !this.options.strict){
+
+ //fire error event
+ this.fire({
+ type: "error",
+ error: ex,
+ message: ex.message,
+ line: ex.line,
+ col: ex.col
+ });
+
+ //skip over everything until closing brace
+ tt = tokenStream.advance([Tokens.RBRACE]);
+ if (tt == Tokens.RBRACE){
+ //if there's a right brace, the rule is finished so don't do anything
+ } else {
+ //otherwise, rethrow the error because it wasn't handled properly
+ throw ex;
+ }
+
+ } else {
+ //not a syntax error, rethrow it
+ throw ex;
+ }
+
+ //trigger parser to continue
+ return true;
+ }
+
+ //if it got here, all selectors parsed
+ if (selectors){
+
+ this.fire({
+ type: "startrule",
+ selectors: selectors,
+ line: selectors[0].line,
+ col: selectors[0].col
+ });
+
+ this._readDeclarations(true);
+
+ this.fire({
+ type: "endrule",
+ selectors: selectors,
+ line: selectors[0].line,
+ col: selectors[0].col
+ });
+
+ }
+
+ return selectors;
+
+ },
+
+ //CSS3 Selectors
+ _selectors_group: function(){
+
+ /*
+ * selectors_group
+ * : selector [ COMMA S* selector ]*
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ selectors = [],
+ selector;
+
+ selector = this._selector();
+ if (selector !== null){
+
+ selectors.push(selector);
+ while(tokenStream.match(Tokens.COMMA)){
+ this._readWhitespace();
+ selector = this._selector();
+ if (selector !== null){
+ selectors.push(selector);
+ } else {
+ this._unexpectedToken(tokenStream.LT(1));
+ }
+ }
+ }
+
+ return selectors.length ? selectors : null;
+ },
+
+ //CSS3 Selectors
+ _selector: function(){
+ /*
+ * selector
+ * : simple_selector_sequence [ combinator simple_selector_sequence ]*
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ selector = [],
+ nextSelector = null,
+ combinator = null,
+ ws = null;
+
+ //if there's no simple selector, then there's no selector
+ nextSelector = this._simple_selector_sequence();
+ if (nextSelector === null){
+ return null;
+ }
+
+ selector.push(nextSelector);
+
+ do {
+
+ //look for a combinator
+ combinator = this._combinator();
+
+ if (combinator !== null){
+ selector.push(combinator);
+ nextSelector = this._simple_selector_sequence();
+
+ //there must be a next selector
+ if (nextSelector === null){
+ this._unexpectedToken(tokenStream.LT(1));
+ } else {
+
+ //nextSelector is an instance of SelectorPart
+ selector.push(nextSelector);
+ }
+ } else {
+
+ //if there's not whitespace, we're done
+ if (this._readWhitespace()){
+
+ //add whitespace separator
+ ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol);
+
+ //combinator is not required
+ combinator = this._combinator();
+
+ //selector is required if there's a combinator
+ nextSelector = this._simple_selector_sequence();
+ if (nextSelector === null){
+ if (combinator !== null){
+ this._unexpectedToken(tokenStream.LT(1));
+ }
+ } else {
+
+ if (combinator !== null){
+ selector.push(combinator);
+ } else {
+ selector.push(ws);
+ }
+
+ selector.push(nextSelector);
+ }
+ } else {
+ break;
+ }
+
+ }
+ } while(true);
+
+ return new Selector(selector, selector[0].line, selector[0].col);
+ },
+
+ //CSS3 Selectors
+ _simple_selector_sequence: function(){
+ /*
+ * simple_selector_sequence
+ * : [ type_selector | universal ]
+ * [ HASH | class | attrib | pseudo | negation ]*
+ * | [ HASH | class | attrib | pseudo | negation ]+
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+
+ //parts of a simple selector
+ elementName = null,
+ modifiers = [],
+
+ //complete selector text
+ selectorText= "",
+
+ //the different parts after the element name to search for
+ components = [
+ //HASH
+ function(){
+ return tokenStream.match(Tokens.HASH) ?
+ new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
+ null;
+ },
+ this._class,
+ this._attrib,
+ this._pseudo,
+ this._negation
+ ],
+ i = 0,
+ len = components.length,
+ component = null,
+ found = false,
+ line,
+ col;
+
+
+ //get starting line and column for the selector
+ line = tokenStream.LT(1).startLine;
+ col = tokenStream.LT(1).startCol;
+
+ elementName = this._type_selector();
+ if (!elementName){
+ elementName = this._universal();
+ }
+
+ if (elementName !== null){
+ selectorText += elementName;
+ }
+
+ while(true){
+
+ //whitespace means we're done
+ if (tokenStream.peek() === Tokens.S){
+ break;
+ }
+
+ //check for each component
+ while(i < len && component === null){
+ component = components[i++].call(this);
+ }
+
+ if (component === null){
+
+ //we don't have a selector
+ if (selectorText === ""){
+ return null;
+ } else {
+ break;
+ }
+ } else {
+ i = 0;
+ modifiers.push(component);
+ selectorText += component.toString();
+ component = null;
+ }
+ }
+
+
+ return selectorText !== "" ?
+ new SelectorPart(elementName, modifiers, selectorText, line, col) :
+ null;
+ },
+
+ //CSS3 Selectors
+ _type_selector: function(){
+ /*
+ * type_selector
+ * : [ namespace_prefix ]? element_name
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ ns = this._namespace_prefix(),
+ elementName = this._element_name();
+
+ if (!elementName){
+ /*
+ * Need to back out the namespace that was read due to both
+ * type_selector and universal reading namespace_prefix
+ * first. Kind of hacky, but only way I can figure out
+ * right now how to not change the grammar.
+ */
+ if (ns){
+ tokenStream.unget();
+ if (ns.length > 1){
+ tokenStream.unget();
+ }
+ }
+
+ return null;
+ } else {
+ if (ns){
+ elementName.text = ns + elementName.text;
+ elementName.col -= ns.length;
+ }
+ return elementName;
+ }
+ },
+
+ //CSS3 Selectors
+ _class: function(){
+ /*
+ * class
+ * : '.' IDENT
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ token;
+
+ if (tokenStream.match(Tokens.DOT)){
+ tokenStream.mustMatch(Tokens.IDENT);
+ token = tokenStream.token();
+ return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1);
+ } else {
+ return null;
+ }
+
+ },
+
+ //CSS3 Selectors
+ _element_name: function(){
+ /*
+ * element_name
+ * : IDENT
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ token;
+
+ if (tokenStream.match(Tokens.IDENT)){
+ token = tokenStream.token();
+ return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol);
+
+ } else {
+ return null;
+ }
+ },
+
+ //CSS3 Selectors
+ _namespace_prefix: function(){
+ /*
+ * namespace_prefix
+ * : [ IDENT | '*' ]? '|'
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ value = "";
+
+ //verify that this is a namespace prefix
+ if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE){
+
+ if(tokenStream.match([Tokens.IDENT, Tokens.STAR])){
+ value += tokenStream.token().value;
+ }
+
+ tokenStream.mustMatch(Tokens.PIPE);
+ value += "|";
+
+ }
+
+ return value.length ? value : null;
+ },
+
+ //CSS3 Selectors
+ _universal: function(){
+ /*
+ * universal
+ * : [ namespace_prefix ]? '*'
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ value = "",
+ ns;
+
+ ns = this._namespace_prefix();
+ if(ns){
+ value += ns;
+ }
+
+ if(tokenStream.match(Tokens.STAR)){
+ value += "*";
+ }
+
+ return value.length ? value : null;
+
+ },
+
+ //CSS3 Selectors
+ _attrib: function(){
+ /*
+ * attrib
+ * : '[' S* [ namespace_prefix ]? IDENT S*
+ * [ [ PREFIXMATCH |
+ * SUFFIXMATCH |
+ * SUBSTRINGMATCH |
+ * '=' |
+ * INCLUDES |
+ * DASHMATCH ] S* [ IDENT | STRING ] S*
+ * ]? ']'
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ value = null,
+ ns,
+ token;
+
+ if (tokenStream.match(Tokens.LBRACKET)){
+ token = tokenStream.token();
+ value = token.value;
+ value += this._readWhitespace();
+
+ ns = this._namespace_prefix();
+
+ if (ns){
+ value += ns;
+ }
+
+ tokenStream.mustMatch(Tokens.IDENT);
+ value += tokenStream.token().value;
+ value += this._readWhitespace();
+
+ if(tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH,
+ Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])){
+
+ value += tokenStream.token().value;
+ value += this._readWhitespace();
+
+ tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
+ value += tokenStream.token().value;
+ value += this._readWhitespace();
+ }
+
+ tokenStream.mustMatch(Tokens.RBRACKET);
+
+ return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol);
+ } else {
+ return null;
+ }
+ },
+
+ //CSS3 Selectors
+ _pseudo: function(){
+
+ /*
+ * pseudo
+ * : ':' ':'? [ IDENT | functional_pseudo ]
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ pseudo = null,
+ colons = ":",
+ line,
+ col;
+
+ if (tokenStream.match(Tokens.COLON)){
+
+ if (tokenStream.match(Tokens.COLON)){
+ colons += ":";
+ }
+
+ if (tokenStream.match(Tokens.IDENT)){
+ pseudo = tokenStream.token().value;
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol - colons.length;
+ } else if (tokenStream.peek() == Tokens.FUNCTION){
+ line = tokenStream.LT(1).startLine;
+ col = tokenStream.LT(1).startCol - colons.length;
+ pseudo = this._functional_pseudo();
+ }
+
+ if (pseudo){
+ pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col);
+ }
+ }
+
+ return pseudo;
+ },
+
+ //CSS3 Selectors
+ _functional_pseudo: function(){
+ /*
+ * functional_pseudo
+ * : FUNCTION S* expression ')'
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ value = null;
+
+ if(tokenStream.match(Tokens.FUNCTION)){
+ value = tokenStream.token().value;
+ value += this._readWhitespace();
+ value += this._expression();
+ tokenStream.mustMatch(Tokens.RPAREN);
+ value += ")";
+ }
+
+ return value;
+ },
+
+ //CSS3 Selectors
+ _expression: function(){
+ /*
+ * expression
+ * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ value = "";
+
+ while(tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION,
+ Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH,
+ Tokens.FREQ, Tokens.ANGLE, Tokens.TIME,
+ Tokens.RESOLUTION, Tokens.SLASH])){
+
+ value += tokenStream.token().value;
+ value += this._readWhitespace();
+ }
+
+ return value.length ? value : null;
+
+ },
+
+ //CSS3 Selectors
+ _negation: function(){
+ /*
+ * negation
+ * : NOT S* negation_arg S* ')'
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ line,
+ col,
+ value = "",
+ arg,
+ subpart = null;
+
+ if (tokenStream.match(Tokens.NOT)){
+ value = tokenStream.token().value;
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol;
+ value += this._readWhitespace();
+ arg = this._negation_arg();
+ value += arg;
+ value += this._readWhitespace();
+ tokenStream.match(Tokens.RPAREN);
+ value += tokenStream.token().value;
+
+ subpart = new SelectorSubPart(value, "not", line, col);
+ subpart.args.push(arg);
+ }
+
+ return subpart;
+ },
+
+ //CSS3 Selectors
+ _negation_arg: function(){
+ /*
+ * negation_arg
+ * : type_selector | universal | HASH | class | attrib | pseudo
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ args = [
+ this._type_selector,
+ this._universal,
+ function(){
+ return tokenStream.match(Tokens.HASH) ?
+ new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) :
+ null;
+ },
+ this._class,
+ this._attrib,
+ this._pseudo
+ ],
+ arg = null,
+ i = 0,
+ len = args.length,
+ elementName,
+ line,
+ col,
+ part;
+
+ line = tokenStream.LT(1).startLine;
+ col = tokenStream.LT(1).startCol;
+
+ while(i < len && arg === null){
+
+ arg = args[i].call(this);
+ i++;
+ }
+
+ //must be a negation arg
+ if (arg === null){
+ this._unexpectedToken(tokenStream.LT(1));
+ }
+
+ //it's an element name
+ if (arg.type == "elementName"){
+ part = new SelectorPart(arg, [], arg.toString(), line, col);
+ } else {
+ part = new SelectorPart(null, [arg], arg.toString(), line, col);
+ }
+
+ return part;
+ },
+
+ _declaration: function(){
+
+ /*
+ * declaration
+ * : property ':' S* expr prio?
+ * | /( empty )/
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ property = null,
+ expr = null,
+ prio = null,
+ error = null,
+ invalid = null,
+ propertyName= "";
+
+ property = this._property();
+ if (property !== null){
+
+ tokenStream.mustMatch(Tokens.COLON);
+ this._readWhitespace();
+
+ expr = this._expr();
+
+ //if there's no parts for the value, it's an error
+ if (!expr || expr.length === 0){
+ this._unexpectedToken(tokenStream.LT(1));
+ }
+
+ prio = this._prio();
+
+ /*
+ * If hacks should be allowed, then only check the root
+ * property. If hacks should not be allowed, treat
+ * _property or *property as invalid properties.
+ */
+ propertyName = property.toString();
+ if (this.options.starHack && property.hack == "*" ||
+ this.options.underscoreHack && property.hack == "_") {
+
+ propertyName = property.text;
+ }
+
+ try {
+ this._validateProperty(propertyName, expr);
+ } catch (ex) {
+ invalid = ex;
+ }
+
+ this.fire({
+ type: "property",
+ property: property,
+ value: expr,
+ important: prio,
+ line: property.line,
+ col: property.col,
+ invalid: invalid
+ });
+
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ _prio: function(){
+ /*
+ * prio
+ * : IMPORTANT_SYM S*
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ result = tokenStream.match(Tokens.IMPORTANT_SYM);
+
+ this._readWhitespace();
+ return result;
+ },
+
+ _expr: function(inFunction){
+ /*
+ * expr
+ * : term [ operator term ]*
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ values = [],
+ //valueParts = [],
+ value = null,
+ operator = null;
+
+ value = this._term();
+ if (value !== null){
+
+ values.push(value);
+
+ do {
+ operator = this._operator(inFunction);
+
+ //if there's an operator, keep building up the value parts
+ if (operator){
+ values.push(operator);
+ } /*else {
+ //if there's not an operator, you have a full value
+ values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
+ valueParts = [];
+ }*/
+
+ value = this._term();
+
+ if (value === null){
+ break;
+ } else {
+ values.push(value);
+ }
+ } while(true);
+ }
+
+ //cleanup
+ /*if (valueParts.length){
+ values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col));
+ }*/
+
+ return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null;
+ },
+
+ _term: function(){
+
+ /*
+ * term
+ * : unary_operator?
+ * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* |
+ * TIME S* | FREQ S* | function | ie_function ]
+ * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ unary = null,
+ value = null,
+ token,
+ line,
+ col;
+
+ //returns the operator or null
+ unary = this._unary_operator();
+ if (unary !== null){
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol;
+ }
+
+ //exception for IE filters
+ if (tokenStream.peek() == Tokens.IE_FUNCTION && this.options.ieFilters){
+
+ value = this._ie_function();
+ if (unary === null){
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol;
+ }
+
+ //see if there's a simple match
+ } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH,
+ Tokens.ANGLE, Tokens.TIME,
+ Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])){
+
+ value = tokenStream.token().value;
+ if (unary === null){
+ line = tokenStream.token().startLine;
+ col = tokenStream.token().startCol;
+ }
+ this._readWhitespace();
+ } else {
+
+ //see if it's a color
+ token = this._hexcolor();
+ if (token === null){
+
+ //if there's no unary, get the start of the next token for line/col info
+ if (unary === null){
+ line = tokenStream.LT(1).startLine;
+ col = tokenStream.LT(1).startCol;
+ }
+
+ //has to be a function
+ if (value === null){
+
+ /*
+ * This checks for alpha(opacity=0) style of IE
+ * functions. IE_FUNCTION only presents progid: style.
+ */
+ if (tokenStream.LA(3) == Tokens.EQUALS && this.options.ieFilters){
+ value = this._ie_function();
+ } else {
+ value = this._function();
+ }
+ }
+
+ /*if (value === null){
+ return null;
+ //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + ".");
+ }*/
+
+ } else {
+ value = token.value;
+ if (unary === null){
+ line = token.startLine;
+ col = token.startCol;
+ }
+ }
+
+ }
+
+ return value !== null ?
+ new PropertyValuePart(unary !== null ? unary + value : value, line, col) :
+ null;
+
+ },
+
+ _function: function(){
+
+ /*
+ * function
+ * : FUNCTION S* expr ')' S*
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ functionText = null,
+ expr = null,
+ lt;
+
+ if (tokenStream.match(Tokens.FUNCTION)){
+ functionText = tokenStream.token().value;
+ this._readWhitespace();
+ expr = this._expr(true);
+ functionText += expr;
+
+ //START: Horrible hack in case it's an IE filter
+ if (this.options.ieFilters && tokenStream.peek() == Tokens.EQUALS){
+ do {
+
+ if (this._readWhitespace()){
+ functionText += tokenStream.token().value;
+ }
+
+ //might be second time in the loop
+ if (tokenStream.LA(0) == Tokens.COMMA){
+ functionText += tokenStream.token().value;
+ }
+
+ tokenStream.match(Tokens.IDENT);
+ functionText += tokenStream.token().value;
+
+ tokenStream.match(Tokens.EQUALS);
+ functionText += tokenStream.token().value;
+
+ //functionText += this._term();
+ lt = tokenStream.peek();
+ while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){
+ tokenStream.get();
+ functionText += tokenStream.token().value;
+ lt = tokenStream.peek();
+ }
+ } while(tokenStream.match([Tokens.COMMA, Tokens.S]));
+ }
+
+ //END: Horrible Hack
+
+ tokenStream.match(Tokens.RPAREN);
+ functionText += ")";
+ this._readWhitespace();
+ }
+
+ return functionText;
+ },
+
+ _ie_function: function(){
+
+ /* (My own extension)
+ * ie_function
+ * : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S*
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ functionText = null,
+ expr = null,
+ lt;
+
+ //IE function can begin like a regular function, too
+ if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])){
+ functionText = tokenStream.token().value;
+
+ do {
+
+ if (this._readWhitespace()){
+ functionText += tokenStream.token().value;
+ }
+
+ //might be second time in the loop
+ if (tokenStream.LA(0) == Tokens.COMMA){
+ functionText += tokenStream.token().value;
+ }
+
+ tokenStream.match(Tokens.IDENT);
+ functionText += tokenStream.token().value;
+
+ tokenStream.match(Tokens.EQUALS);
+ functionText += tokenStream.token().value;
+
+ //functionText += this._term();
+ lt = tokenStream.peek();
+ while(lt != Tokens.COMMA && lt != Tokens.S && lt != Tokens.RPAREN){
+ tokenStream.get();
+ functionText += tokenStream.token().value;
+ lt = tokenStream.peek();
+ }
+ } while(tokenStream.match([Tokens.COMMA, Tokens.S]));
+
+ tokenStream.match(Tokens.RPAREN);
+ functionText += ")";
+ this._readWhitespace();
+ }
+
+ return functionText;
+ },
+
+ _hexcolor: function(){
+ /*
+ * There is a constraint on the color that it must
+ * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
+ * after the "#"; e.g., "#000" is OK, but "#abcd" is not.
+ *
+ * hexcolor
+ * : HASH S*
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ token = null,
+ color;
+
+ if(tokenStream.match(Tokens.HASH)){
+
+ //need to do some validation here
+
+ token = tokenStream.token();
+ color = token.value;
+ if (!/#[a-f0-9]{3,6}/i.test(color)){
+ throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
+ }
+ this._readWhitespace();
+ }
+
+ return token;
+ },
+
+ //-----------------------------------------------------------------
+ // Animations methods
+ //-----------------------------------------------------------------
+
+ _keyframes: function(){
+
+ /*
+ * keyframes:
+ * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' {
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ token,
+ tt,
+ name,
+ prefix = "";
+
+ tokenStream.mustMatch(Tokens.KEYFRAMES_SYM);
+ token = tokenStream.token();
+ if (/^@\-([^\-]+)\-/.test(token.value)) {
+ prefix = RegExp.$1;
+ }
+
+ this._readWhitespace();
+ name = this._keyframe_name();
+
+ this._readWhitespace();
+ tokenStream.mustMatch(Tokens.LBRACE);
+
+ this.fire({
+ type: "startkeyframes",
+ name: name,
+ prefix: prefix,
+ line: token.startLine,
+ col: token.startCol
+ });
+
+ this._readWhitespace();
+ tt = tokenStream.peek();
+
+ //check for key
+ while(tt == Tokens.IDENT || tt == Tokens.PERCENTAGE) {
+ this._keyframe_rule();
+ this._readWhitespace();
+ tt = tokenStream.peek();
+ }
+
+ this.fire({
+ type: "endkeyframes",
+ name: name,
+ prefix: prefix,
+ line: token.startLine,
+ col: token.startCol
+ });
+
+ this._readWhitespace();
+ tokenStream.mustMatch(Tokens.RBRACE);
+
+ },
+
+ _keyframe_name: function(){
+
+ /*
+ * keyframe_name:
+ * : IDENT
+ * | STRING
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ token;
+
+ tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]);
+ return SyntaxUnit.fromToken(tokenStream.token());
+ },
+
+ _keyframe_rule: function(){
+
+ /*
+ * keyframe_rule:
+ * : key_list S*
+ * '{' S* declaration [ ';' S* declaration ]* '}' S*
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ token,
+ keyList = this._key_list();
+
+ this.fire({
+ type: "startkeyframerule",
+ keys: keyList,
+ line: keyList[0].line,
+ col: keyList[0].col
+ });
+
+ this._readDeclarations(true);
+
+ this.fire({
+ type: "endkeyframerule",
+ keys: keyList,
+ line: keyList[0].line,
+ col: keyList[0].col
+ });
+
+ },
+
+ _key_list: function(){
+
+ /*
+ * key_list:
+ * : key [ S* ',' S* key]*
+ * ;
+ */
+ var tokenStream = this._tokenStream,
+ token,
+ key,
+ keyList = [];
+
+ //must be least one key
+ keyList.push(this._key());
+
+ this._readWhitespace();
+
+ while(tokenStream.match(Tokens.COMMA)){
+ this._readWhitespace();
+ keyList.push(this._key());
+ this._readWhitespace();
+ }
+
+ return keyList;
+ },
+
+ _key: function(){
+ /*
+ * There is a restriction that IDENT can be only "from" or "to".
+ *
+ * key
+ * : PERCENTAGE
+ * | IDENT
+ * ;
+ */
+
+ var tokenStream = this._tokenStream,
+ token;
+
+ if (tokenStream.match(Tokens.PERCENTAGE)){
+ return SyntaxUnit.fromToken(tokenStream.token());
+ } else if (tokenStream.match(Tokens.IDENT)){
+ token = tokenStream.token();
+
+ if (/from|to/i.test(token.value)){
+ return SyntaxUnit.fromToken(token);
+ }
+
+ tokenStream.unget();
+ }
+
+ //if it gets here, there wasn't a valid token, so time to explode
+ this._unexpectedToken(tokenStream.LT(1));
+ },
+
+ //-----------------------------------------------------------------
+ // Helper methods
+ //-----------------------------------------------------------------
+
+ /**
+ * Not part of CSS grammar, but useful for skipping over
+ * combination of white space and HTML-style comments.
+ * @return {void}
+ * @method _skipCruft
+ * @private
+ */
+ _skipCruft: function(){
+ while(this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])){
+ //noop
+ }
+ },
+
+ /**
+ * Not part of CSS grammar, but this pattern occurs frequently
+ * in the official CSS grammar. Split out here to eliminate
+ * duplicate code.
+ * @param {Boolean} checkStart Indicates if the rule should check
+ * for the left brace at the beginning.
+ * @param {Boolean} readMargins Indicates if the rule should check
+ * for margin patterns.
+ * @return {void}
+ * @method _readDeclarations
+ * @private
+ */
+ _readDeclarations: function(checkStart, readMargins){
+ /*
+ * Reads the pattern
+ * S* '{' S* declaration [ ';' S* declaration ]* '}' S*
+ * or
+ * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S*
+ * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect.
+ * A semicolon is only necessary following a declaration is there's another declaration
+ * or margin afterwards.
+ */
+ var tokenStream = this._tokenStream,
+ tt;
+
+
+ this._readWhitespace();
+
+ if (checkStart){
+ tokenStream.mustMatch(Tokens.LBRACE);
+ }
+
+ this._readWhitespace();
+
+ try {
+
+ while(true){
+
+ if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())){
+ //noop
+ } else if (this._declaration()){
+ if (!tokenStream.match(Tokens.SEMICOLON)){
+ break;
+ }
+ } else {
+ break;
+ }
+
+ //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){
+ // break;
+ //}
+ this._readWhitespace();
+ }
+
+ tokenStream.mustMatch(Tokens.RBRACE);
+ this._readWhitespace();
+
+ } catch (ex) {
+ if (ex instanceof SyntaxError && !this.options.strict){
+
+ //fire error event
+ this.fire({
+ type: "error",
+ error: ex,
+ message: ex.message,
+ line: ex.line,
+ col: ex.col
+ });
+
+ //see if there's another declaration
+ tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]);
+ if (tt == Tokens.SEMICOLON){
+ //if there's a semicolon, then there might be another declaration
+ this._readDeclarations(false, readMargins);
+ } else if (tt != Tokens.RBRACE){
+ //if there's a right brace, the rule is finished so don't do anything
+ //otherwise, rethrow the error because it wasn't handled properly
+ throw ex;
+ }
+
+ } else {
+ //not a syntax error, rethrow it
+ throw ex;
+ }
+ }
+
+ },
+
+ /**
+ * In some cases, you can end up with two white space tokens in a
+ * row. Instead of making a change in every function that looks for
+ * white space, this function is used to match as much white space
+ * as necessary.
+ * @method _readWhitespace
+ * @return {String} The white space if found, empty string if not.
+ * @private
+ */
+ _readWhitespace: function(){
+
+ var tokenStream = this._tokenStream,
+ ws = "";
+
+ while(tokenStream.match(Tokens.S)){
+ ws += tokenStream.token().value;
+ }
+
+ return ws;
+ },
+
+
+ /**
+ * Throws an error when an unexpected token is found.
+ * @param {Object} token The token that was found.
+ * @method _unexpectedToken
+ * @return {void}
+ * @private
+ */
+ _unexpectedToken: function(token){
+ throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
+ },
+
+ /**
+ * Helper method used for parsing subparts of a style sheet.
+ * @return {void}
+ * @method _verifyEnd
+ * @private
+ */
+ _verifyEnd: function(){
+ if (this._tokenStream.LA(1) != Tokens.EOF){
+ this._unexpectedToken(this._tokenStream.LT(1));
+ }
+ },
+
+ //-----------------------------------------------------------------
+ // Validation methods
+ //-----------------------------------------------------------------
+ _validateProperty: function(property, value){
+ Validation.validate(property, value);
+ },
+
+ //-----------------------------------------------------------------
+ // Parsing methods
+ //-----------------------------------------------------------------
+
+ parse: function(input){
+ this._tokenStream = new TokenStream(input, Tokens);
+ this._stylesheet();
+ },
+
+ parseStyleSheet: function(input){
+ //just passthrough
+ return this.parse(input);
+ },
+
+ parseMediaQuery: function(input){
+ this._tokenStream = new TokenStream(input, Tokens);
+ var result = this._media_query();
+
+ //if there's anything more, then it's an invalid selector
+ this._verifyEnd();
+
+ //otherwise return result
+ return result;
+ },
+
+ /**
+ * Parses a property value (everything after the semicolon).
+ * @return {parserlib.css.PropertyValue} The property value.
+ * @throws parserlib.util.SyntaxError If an unexpected token is found.
+ * @method parserPropertyValue
+ */
+ parsePropertyValue: function(input){
+
+ this._tokenStream = new TokenStream(input, Tokens);
+ this._readWhitespace();
+
+ var result = this._expr();
+
+ //okay to have a trailing white space
+ this._readWhitespace();
+
+ //if there's anything more, then it's an invalid selector
+ this._verifyEnd();
+
+ //otherwise return result
+ return result;
+ },
+
+ /**
+ * Parses a complete CSS rule, including selectors and
+ * properties.
+ * @param {String} input The text to parser.
+ * @return {Boolean} True if the parse completed successfully, false if not.
+ * @method parseRule
+ */
+ parseRule: function(input){
+ this._tokenStream = new TokenStream(input, Tokens);
+
+ //skip any leading white space
+ this._readWhitespace();
+
+ var result = this._ruleset();
+
+ //skip any trailing white space
+ this._readWhitespace();
+
+ //if there's anything more, then it's an invalid selector
+ this._verifyEnd();
+
+ //otherwise return result
+ return result;
+ },
+
+ /**
+ * Parses a single CSS selector (no comma)
+ * @param {String} input The text to parse as a CSS selector.
+ * @return {Selector} An object representing the selector.
+ * @throws parserlib.util.SyntaxError If an unexpected token is found.
+ * @method parseSelector
+ */
+ parseSelector: function(input){
+
+ this._tokenStream = new TokenStream(input, Tokens);
+
+ //skip any leading white space
+ this._readWhitespace();
+
+ var result = this._selector();
+
+ //skip any trailing white space
+ this._readWhitespace();
+
+ //if there's anything more, then it's an invalid selector
+ this._verifyEnd();
+
+ //otherwise return result
+ return result;
+ },
+
+ /**
+ * Parses an HTML style attribute: a set of CSS declarations
+ * separated by semicolons.
+ * @param {String} input The text to parse as a style attribute
+ * @return {void}
+ * @method parseStyleAttribute
+ */
+ parseStyleAttribute: function(input){
+ input += "}"; // for error recovery in _readDeclarations()
+ this._tokenStream = new TokenStream(input, Tokens);
+ this._readDeclarations();
+ }
+ };
+
+ //copy over onto prototype
+ for (prop in additions){
+ if (additions.hasOwnProperty(prop)){
+ proto[prop] = additions[prop];
+ }
+ }
+
+ return proto;
+}();
+
+
+/*
+nth
+ : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? |
+ ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S*
+ ;
+*/
+
+/*global Validation, ValidationTypes, ValidationError*/
+var Properties = {
+
+ //A
+ "alignment-adjust" : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | | ",
+ "alignment-baseline" : "baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical",
+ "animation" : 1,
+ "animation-delay" : { multi: "