diff --git a/media/CodeMirror-0.62/LICENSE b/media/CodeMirror-0.62/LICENSE
new file mode 100644
index 0000000..44ceed6
--- /dev/null
+++ b/media/CodeMirror-0.62/LICENSE
@@ -0,0 +1,23 @@
+ Copyright (c) 2007-2009 Marijn Haverbeke
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any
+ damages arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any
+ purpose, including commercial applications, and to alter it and
+ redistribute it freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must
+ not claim that you wrote the original software. If you use this
+ software in a product, an acknowledgment in the product
+ documentation would be appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must
+ not be misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+
+ Marijn Haverbeke
+ marijnh at gmail
diff --git a/media/CodeMirror-0.62/contrib/lua/LICENSE b/media/CodeMirror-0.62/contrib/lua/LICENSE
new file mode 100644
index 0000000..1af1908
--- /dev/null
+++ b/media/CodeMirror-0.62/contrib/lua/LICENSE
@@ -0,0 +1,32 @@
+Copyright (c) 2009, Franciszek Wawrzak
+All rights reserved.
+
+This software is provided for use in connection with the
+CodeMirror suite of modules and utilities, hosted and maintained
+at http://marijn.haverbeke.nl/codemirror/.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/media/CodeMirror-0.62/contrib/lua/css/luacolors.css b/media/CodeMirror-0.62/contrib/lua/css/luacolors.css
new file mode 100644
index 0000000..6df44b5
--- /dev/null
+++ b/media/CodeMirror-0.62/contrib/lua/css/luacolors.css
@@ -0,0 +1,59 @@
+.editbox {
+ margin: .4em;
+ padding: 0;
+ font-family: monospace;
+ font-size: 10pt;
+ color: black;
+}
+
+pre.code, .editbox {
+ color: #666666;
+}
+
+.editbox p {
+ margin: 0;
+}
+
+span.lua-comment {
+ color: #BB9977;
+}
+
+span.lua-keyword {
+ font-weight: bold;
+ color: blue;
+}
+
+span.lua-string {
+ color: #AA2222;
+}
+
+span.lua-stdfunc {
+ font-weight: bold;
+ color: #077;
+}
+span.lua-customfunc {
+ font-weight: bold;
+ color: #0AA;
+}
+
+
+span.lua-identifier {
+ color: black;
+}
+
+span.lua-number {
+ color: #3A3;
+}
+
+span.lua-token {
+ color: #151;
+}
+
+span.lua-error {
+ color: #FFF;
+ background-color: #F00;
+}
+
+
+
+
diff --git a/media/CodeMirror-0.62/contrib/lua/index.html b/media/CodeMirror-0.62/contrib/lua/index.html
new file mode 100644
index 0000000..03a3229
--- /dev/null
+++ b/media/CodeMirror-0.62/contrib/lua/index.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+
diff --git a/media/CodeMirror-0.62/contrib/lua/js/parselua.js b/media/CodeMirror-0.62/contrib/lua/js/parselua.js
new file mode 100644
index 0000000..2bc891b
--- /dev/null
+++ b/media/CodeMirror-0.62/contrib/lua/js/parselua.js
@@ -0,0 +1,253 @@
+/*
+ Simple parser for LUA
+ Written for Lua 5.1, based on parsecss and other parsers.
+ features: highlights keywords, strings, comments (no leveling supported! ("[==[")),tokens, basic indenting
+
+ to make this parser highlight your special functions pass table with this functions names to parserConfig argument of creator,
+
+ parserConfig: ["myfunction1","myfunction2"],
+ */
+
+
+function findFirstRegexp(words) {
+ return new RegExp("^(?:" + words.join("|") + ")", "i");
+}
+
+function matchRegexp(words) {
+ return new RegExp("^(?:" + words.join("|") + ")$", "i");
+}
+
+
+
+var luaCustomFunctions= matchRegexp([]);
+
+function configureLUA(parserConfig){
+ if(parserConfig)
+ luaCustomFunctions= matchRegexp(parserConfig);
+}
+
+
+//long list of standard functions from lua manual
+var luaStdFunctions = matchRegexp([
+"_G","_VERSION","assert","collectgarbage","dofile","error","getfenv","getmetatable","ipairs","load","loadfile","loadstring","module","next","pairs","pcall","print","rawequal","rawget","rawset","require","select","setfenv","setmetatable","tonumber","tostring","type","unpack","xpcall",
+
+"coroutine.create","coroutine.resume","coroutine.running","coroutine.status","coroutine.wrap","coroutine.yield",
+
+"debug.debug","debug.getfenv","debug.gethook","debug.getinfo","debug.getlocal","debug.getmetatable","debug.getregistry","debug.getupvalue","debug.setfenv","debug.sethook","debug.setlocal","debug.setmetatable","debug.setupvalue","debug.traceback",
+
+"close","flush","lines","read","seek","setvbuf","write",
+
+"io.close","io.flush","io.input","io.lines","io.open","io.output","io.popen","io.read","io.stderr","io.stdin","io.stdout","io.tmpfile","io.type","io.write",
+
+"math.abs","math.acos","math.asin","math.atan","math.atan2","math.ceil","math.cos","math.cosh","math.deg","math.exp","math.floor","math.fmod","math.frexp","math.huge","math.ldexp","math.log","math.log10","math.max","math.min","math.modf","math.pi","math.pow","math.rad","math.random","math.randomseed","math.sin","math.sinh","math.sqrt","math.tan","math.tanh",
+
+"os.clock","os.date","os.difftime","os.execute","os.exit","os.getenv","os.remove","os.rename","os.setlocale","os.time","os.tmpname",
+
+"package.cpath","package.loaded","package.loaders","package.loadlib","package.path","package.preload","package.seeall",
+
+"string.byte","string.char","string.dump","string.find","string.format","string.gmatch","string.gsub","string.len","string.lower","string.match","string.rep","string.reverse","string.sub","string.upper",
+
+"table.concat","table.insert","table.maxn","table.remove","table.sort"
+]);
+
+
+
+ var luaKeywords = matchRegexp(["and","break","elseif","false","nil","not","or","return",
+ "true","function", "end", "if", "then", "else", "do",
+ "while", "repeat", "until", "for", "in", "local" ]);
+
+ var luaIndentKeys = matchRegexp(["function", "if","repeat","for","while", "[\(]", "{"]);
+ var luaUnindentKeys = matchRegexp(["end", "until", "[\)]", "}"]);
+
+ var luaUnindentKeys2 = findFirstRegexp(["end", "until", "[\)]", "}"]);
+ var luaMiddleKeys = findFirstRegexp(["else","elseif"]);
+
+
+
+var LUAParser = Editor.Parser = (function() {
+ var tokenizeLUA = (function() {
+ function normal(source, setState) {
+ var ch = source.next();
+
+ if (ch == "-" && source.equals("-")) {
+ source.next();
+ setState(inSLComment);
+ return null;
+ }
+ else if (ch == "\"" || ch == "'") {
+ setState(inString(ch));
+ return null;
+ }
+ if (ch == "[" && (source.equals("[") || source.equals("="))) {
+ var level = 0;
+ while(source.equals("=")){
+ level ++;
+ source.next();
+ }
+ if(! source.equals("[") )
+ return "lua-error";
+ setState(inMLSomething(level,"lua-string"));
+ return null;
+ }
+
+ else if (ch == "=") {
+ if (source.equals("="))
+ source.next();
+ return "lua-token";
+ }
+
+ else if (ch == ".") {
+ if (source.equals("."))
+ source.next();
+ if (source.equals("."))
+ source.next();
+ return "lua-token";
+ }
+
+ else if (ch == "+" || ch == "-" || ch == "*" || ch == "/" || ch == "%" || ch == "^" || ch == "#" ) {
+ return "lua-token";
+ }
+ else if (ch == ">" || ch == "<" || ch == "(" || ch == ")" || ch == "{" || ch == "}" || ch == "[" ) {
+ return "lua-token";
+ }
+ else if (ch == "]" || ch == ";" || ch == ":" || ch == ",") {
+ return "lua-token";
+ }
+ else if (source.equals("=") && (ch == "~" || ch == "<" || ch == ">")) {
+ source.next();
+ return "lua-token";
+ }
+
+ else if (/\d/.test(ch)) {
+ source.nextWhileMatches(/[\w.%]/);
+ return "lua-number";
+ }
+ else {
+ source.nextWhileMatches(/[\w\\\-_.]/);
+ return "lua-identifier";
+ }
+ }
+
+function inSLComment(source, setState) {
+ var start = true;
+ var count=0;
+ while (!source.endOfLine()) {
+ var ch = source.next();
+ var level = 0;
+ if ((ch =="[") && start)
+ while(source.equals("=")){
+ source.next();
+ level++;
+ }
+ if (source.equals("[")){
+ setState(inMLSomething(level,"lua-comment"));
+ return null;
+ }
+ start = false;
+ }
+ setState(normal);
+ return "lua-comment";
+
+ }
+
+ function inMLSomething(level,what) {
+ //wat sholud be "lua-string" or "lua-comment", level is the number of "=" in opening mark.
+ return function(source, setState){
+ var dashes = 0;
+ while (!source.endOfLine()) {
+ var ch = source.next();
+ if (dashes == level+1 && ch == "]" ) {
+ setState(normal);
+ break;
+ }
+ if (dashes == 0)
+ dashes = (ch == "]") ? 1:0;
+ else
+ dashes = (ch == "=") ? dashes + 1 : 0;
+ }
+ return what;
+ }
+ }
+
+
+ function inString(quote) {
+ return function(source, setState) {
+ var escaped = false;
+ while (!source.endOfLine()) {
+ var ch = source.next();
+ if (ch == quote && !escaped)
+ break;
+ escaped = !escaped && ch == "\\";
+ }
+ if (!escaped)
+ setState(normal);
+ return "lua-string";
+ };
+ }
+
+ return function(source, startState) {
+ return tokenizer(source, startState || normal);
+ };
+ })();
+
+ function indentLUA(indentDepth, base) {
+ return function(nextChars) {
+
+ var closing = (luaUnindentKeys2.test(nextChars) || luaMiddleKeys.test(nextChars));
+
+
+ return base + ( indentUnit * (indentDepth - (closing?1:0)) );
+ };
+ }
+
+
+function parseLUA(source,basecolumn) {
+ basecolumn = basecolumn || 0;
+
+ var tokens = tokenizeLUA(source);
+ var indentDepth = 0;
+
+ var iter = {
+ next: function() {
+ var token = tokens.next(), style = token.style, content = token.content;
+
+
+
+ if (style == "lua-identifier" && luaKeywords.test(content)){
+ token.style = "lua-keyword";
+ }
+ if (style == "lua-identifier" && luaStdFunctions.test(content)){
+ token.style = "lua-stdfunc";
+ }
+ if (style == "lua-identifier" && luaCustomFunctions.test(content)){
+ token.style = "lua-customfunc";
+ }
+
+ if (luaIndentKeys.test(content))
+ indentDepth++;
+ else if (luaUnindentKeys.test(content))
+ indentDepth--;
+
+
+ if (content == "\n")
+ token.indentation = indentLUA( indentDepth, basecolumn);
+
+ return token;
+ },
+
+ copy: function() {
+ var _tokenState = tokens.state, _indentDepth = indentDepth;
+ return function(source) {
+ tokens = tokenizeLUA(source, _tokenState);
+
+ indentDepth = _indentDepth;
+ return iter;
+ };
+ }
+ };
+ return iter;
+ }
+
+ return {make: parseLUA, configure:configureLUA, electricChars: "delf})"}; //en[d] els[e] unti[l] elsei[f] // this should be taken from Keys keywords
+})();
+
diff --git a/media/CodeMirror-0.62/contrib/php/LICENSE b/media/CodeMirror-0.62/contrib/php/LICENSE
new file mode 100644
index 0000000..ce4c5e4
--- /dev/null
+++ b/media/CodeMirror-0.62/contrib/php/LICENSE
@@ -0,0 +1,37 @@
+Copyright (c) 2008-2009, Yahoo! Inc.
+All rights reserved.
+
+This software is provided for use in connection with the
+CodeMirror suite of modules and utilities, hosted and maintained
+at http://marijn.haverbeke.nl/codemirror/.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* Neither the name of Yahoo! Inc. nor the names of its
+ contributors may be used to endorse or promote products
+ derived from this software without specific prior
+ written permission of Yahoo! Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/media/CodeMirror-0.62/contrib/php/css/phpcolors.css b/media/CodeMirror-0.62/contrib/php/css/phpcolors.css
new file mode 100644
index 0000000..1dd1aec
--- /dev/null
+++ b/media/CodeMirror-0.62/contrib/php/css/phpcolors.css
@@ -0,0 +1,110 @@
+/*
+Copyright (c) 2008-2009 Yahoo! Inc. All rights reserved.
+The copyrights embodied in the content of this file are licensed by
+Yahoo! Inc. under the BSD (revised) open source license
+
+@author Dan Vlad Dascalescu
+*/
+
+.editbox {
+ margin: .4em;
+ padding: 0;
+ font-family: monospace;
+ font-size: 10pt;
+}
+
+/*We should define specific styles for every element of the syntax.
+ the setting below will cause some annoying color to show through if we missed
+ defining a style for a token. This is also the "color" of the whitespace and
+ of the cursor.
+*/
+pre.code, .editbox {
+ color: red;
+}
+
+.editbox p {
+ margin: 0;
+}
+
+span.php-punctuation {
+ color: blue;
+}
+
+span.php-keyword {
+ color: #770088;
+ font-weight: bold;
+}
+
+span.php-operator {
+ color: blue;
+}
+
+/* __FILE__ etc.; http://php.net/manual/en/reserved.php */
+span.php-compile-time-constant {
+ color: #776088;
+ font-weight: bold;
+}
+
+/* output of get_defined_constants(). Differs from http://php.net/manual/en/reserved.constants.php */
+span.php-predefined-constant {
+ color: darkgreen;
+ font-weight: bold;
+}
+
+/* PHP reserved "language constructs"... echo() etc.; http://php.net/manual/en/reserved.php */
+span.php-reserved-language-construct {
+ color: green;
+ font-weight: bold;
+}
+
+/* PHP built-in functions: glob(), chr() etc.; output of get_defined_functions()["internal"] */
+span.php-predefined-function {
+ color: green;
+}
+
+/* PHP predefined classes: PDO, Exception etc.; output of get_declared_classes() and different from http://php.net/manual/en/reserved.classes.php */
+span.php-predefined-class {
+ color: green;
+}
+
+span.php-atom {
+ color: #228811;
+}
+
+/* class, interface, namespace or function names, but not $variables */
+span.php-t_string {
+ color: black;
+}
+
+span.php-variable {
+ color: black;
+ font-weight: bold;
+}
+
+
+span.js-localvariable {
+ color: #004499;
+}
+
+span.php-comment {
+ color: #AA7700;
+ font-stretch: condensed;
+/* font-style: italic; This causes line height to slightly change, getting line numbers out of sync */
+}
+
+span.php-string-single-quoted {
+ color: #AA2222;
+}
+/* double quoted strings allow interpolation */
+span.php-string-double-quoted {
+ color: #AA2222;
+ font-weight: bold;
+}
+
+span.syntax-error {
+ background-color: red;
+}
+
+span.deprecated {
+ font-size: smaller;
+}
diff --git a/media/CodeMirror-0.62/contrib/php/index.html b/media/CodeMirror-0.62/contrib/php/index.html
new file mode 100644
index 0000000..f35f5d2
--- /dev/null
+++ b/media/CodeMirror-0.62/contrib/php/index.html
@@ -0,0 +1,292 @@
+
+
+
+
+
+
+ CodeMirror: PHP+HTML+JavaScript+CSS mixed-mode demonstration
+
+
+
+
+
This is a complex demonstration of the PHP+HTML+JavaScript+CSS mixed-mode
+ syntax highlight capabilities of CodeMirror.
+ <?php ... ?> tags use the PHP parser, <script> tags use the JavaScript
+ parser, and <style> tags use the CSS parser. The rest of the content is
+ parsed using the XML parser in HTML mode.
+
+
Features of the PHP parser:
+
+
special "deprecated" style for PHP4 keywords like 'var'
+
support for PHP 5.3 keywords: 'namespace', 'use'
+
911 predefined constants, 1301 predefined functions, 105 predeclared classes
+ from a typical PHP installation in a LAMP environment
+
+
+
+
+
+
diff --git a/media/CodeMirror-0.62/js/codemirror.js b/media/CodeMirror-0.62/js/codemirror.js
new file mode 100644
index 0000000..aac55f5
--- /dev/null
+++ b/media/CodeMirror-0.62/js/codemirror.js
@@ -0,0 +1,298 @@
+/* CodeMirror main module
+ *
+ * Implements the CodeMirror constructor and prototype, which take care
+ * of initializing the editor frame, and providing the outside interface.
+ */
+
+// The CodeMirrorConfig object is used to specify a default
+// configuration. If you specify such an object before loading this
+// file, the values you put into it will override the defaults given
+// below. You can also assign to it after loading.
+var CodeMirrorConfig = window.CodeMirrorConfig || {};
+
+var CodeMirror = (function(){
+ function setDefaults(object, defaults) {
+ for (var option in defaults) {
+ if (!object.hasOwnProperty(option))
+ object[option] = defaults[option];
+ }
+ }
+ function forEach(array, action) {
+ for (var i = 0; i < array.length; i++)
+ action(array[i]);
+ }
+
+ // These default options can be overridden by passing a set of
+ // options to a specific CodeMirror constructor. See manual.html for
+ // their meaning.
+ setDefaults(CodeMirrorConfig, {
+ stylesheet: "",
+ path: "",
+ parserfile: [],
+ basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"],
+ iframeClass: null,
+ passDelay: 200,
+ passTime: 50,
+ continuousScanning: false,
+ saveFunction: null,
+ onChange: null,
+ undoDepth: 50,
+ undoDelay: 800,
+ disableSpellcheck: true,
+ textWrapping: true,
+ readOnly: false,
+ width: "100%",
+ height: "300px",
+ autoMatchParens: false,
+ parserConfig: null,
+ tabMode: "indent", // or "spaces", "default", "shift"
+ reindentOnLoad: false,
+ activeTokens: null,
+ cursorActivity: null,
+ lineNumbers: false,
+ indentUnit: 2
+ });
+
+ function wrapLineNumberDiv(place) {
+ return function(node) {
+ var container = document.createElement("DIV"),
+ nums = document.createElement("DIV"),
+ scroller = document.createElement("DIV");
+ container.style.position = "relative";
+ nums.style.position = "absolute";
+ nums.style.height = "100%";
+ if (nums.style.setExpression) {
+ try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");}
+ catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions
+ }
+ nums.style.top = "0px";
+ nums.style.overflow = "hidden";
+ place(container);
+ container.appendChild(node);
+ container.appendChild(nums);
+ scroller.className = "CodeMirror-line-numbers";
+ nums.appendChild(scroller);
+ }
+ }
+
+ function applyLineNumbers(frame) {
+ var win = frame.contentWindow, doc = win.document,
+ nums = frame.nextSibling, scroller = nums.firstChild;
+
+ var nextNum = 1, barWidth = null;
+ function sizeBar() {
+ if (nums.offsetWidth != barWidth) {
+ barWidth = nums.offsetWidth;
+ nums.style.left = "-" + (frame.parentNode.style.marginLeft = barWidth + "px");
+ }
+ }
+ function update() {
+ var diff = 20 + Math.max(doc.body.offsetHeight, frame.offsetHeight) - scroller.offsetHeight;
+ for (var n = Math.ceil(diff / 10); n > 0; n--) {
+ var div = document.createElement("DIV");
+ div.appendChild(document.createTextNode(nextNum++));
+ scroller.appendChild(div);
+ }
+ nums.scrollTop = doc.body.scrollTop || doc.documentElement.scrollTop || 0;
+ }
+ sizeBar();
+ update();
+ win.addEventHandler(win, "scroll", update);
+ setInterval(sizeBar, 500);
+ }
+
+ function CodeMirror(place, options) {
+ // Backward compatibility for deprecated options.
+ if (options.dumbTabs) options.tabMode = "spaces";
+ else if (options.normalTab) options.tabMode = "default";
+
+ // Use passed options, if any, to override defaults.
+ this.options = options = options || {};
+ setDefaults(options, CodeMirrorConfig);
+
+ var frame = this.frame = document.createElement("IFRAME");
+ if (options.iframeClass) frame.className = options.iframeClass;
+ frame.frameBorder = 0;
+ frame.src = "javascript:false;";
+ frame.style.border = "0";
+ frame.style.width = options.width;
+ frame.style.height = options.height;
+ // display: block occasionally suppresses some Firefox bugs, so we
+ // always add it, redundant as it sounds.
+ frame.style.display = "block";
+
+ if (place.appendChild) {
+ var node = place;
+ place = function(n){node.appendChild(n);};
+ }
+ if (options.lineNumbers) place = wrapLineNumberDiv(place);
+ place(frame);
+
+ // Link back to this object, so that the editor can fetch options
+ // and add a reference to itself.
+ frame.CodeMirror = this;
+ this.win = frame.contentWindow;
+
+ if (typeof options.parserfile == "string")
+ options.parserfile = [options.parserfile];
+ if (typeof options.stylesheet == "string")
+ options.stylesheet = [options.stylesheet];
+
+ var html = [""];
+ forEach(options.stylesheet, function(file) {
+ html.push("");
+ });
+ forEach(options.basefiles.concat(options.parserfile), function(file) {
+ html.push("");
+ });
+ html.push("");
+
+ var doc = this.win.document;
+ doc.open();
+ doc.write(html.join(""));
+ doc.close();
+ }
+
+ CodeMirror.prototype = {
+ init: function() {
+ if (this.options.initCallback) this.options.initCallback(this);
+ if (this.options.lineNumbers) applyLineNumbers(this.frame);
+ if (this.options.reindentOnLoad) this.reindent();
+ },
+
+ getCode: function() {return this.editor.getCode();},
+ setCode: function(code) {this.editor.importCode(code);},
+ selection: function() {return this.editor.selectedText();},
+ reindent: function() {this.editor.reindent();},
+ reindentSelection: function() {this.editor.reindentSelection(null);},
+
+ focus: function() {
+ this.win.focus();
+ if (this.editor.selectionSnapshot) // IE hack
+ this.win.select.selectCoords(this.win, this.editor.selectionSnapshot);
+ },
+ replaceSelection: function(text) {
+ this.focus();
+ this.editor.replaceSelection(text);
+ return true;
+ },
+ replaceChars: function(text, start, end) {
+ this.editor.replaceChars(text, start, end);
+ },
+ getSearchCursor: function(string, fromCursor) {
+ return this.editor.getSearchCursor(string, fromCursor);
+ },
+
+ undo: function() {this.editor.history.undo();},
+ redo: function() {this.editor.history.redo();},
+ historySize: function() {return this.editor.history.historySize();},
+ clearHistory: function() {this.editor.history.clear();},
+
+ grabKeys: function(callback, filter) {this.editor.grabKeys(callback, filter);},
+ ungrabKeys: function() {this.editor.ungrabKeys();},
+
+ setParser: function(name) {this.editor.setParser(name);},
+
+ cursorPosition: function(start) {
+ if (this.win.select.ie_selection) this.focus();
+ return this.editor.cursorPosition(start);
+ },
+ firstLine: function() {return this.editor.firstLine();},
+ lastLine: function() {return this.editor.lastLine();},
+ nextLine: function(line) {return this.editor.nextLine(line);},
+ prevLine: function(line) {return this.editor.prevLine(line);},
+ lineContent: function(line) {return this.editor.lineContent(line);},
+ setLineContent: function(line, content) {this.editor.setLineContent(line, content);},
+ insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);},
+ selectLines: function(startLine, startOffset, endLine, endOffset) {
+ this.win.focus();
+ this.editor.selectLines(startLine, startOffset, endLine, endOffset);
+ },
+ nthLine: function(n) {
+ var line = this.firstLine();
+ for (; n > 1 && line !== false; n--)
+ line = this.nextLine(line);
+ return line;
+ },
+ lineNumber: function(line) {
+ var num = 0;
+ while (line !== false) {
+ num++;
+ line = this.prevLine(line);
+ }
+ return num;
+ },
+
+ // Old number-based line interface
+ jumpToLine: function(n) {
+ this.selectLines(this.nthLine(n), 0);
+ this.win.focus();
+ },
+ currentLine: function() {
+ return this.lineNumber(this.cursorPosition().line);
+ }
+ };
+
+ CodeMirror.InvalidLineHandle = {toString: function(){return "CodeMirror.InvalidLineHandle";}};
+
+ CodeMirror.replace = function(element) {
+ if (typeof element == "string")
+ element = document.getElementById(element);
+ return function(newElement) {
+ element.parentNode.replaceChild(newElement, element);
+ };
+ };
+
+ CodeMirror.fromTextArea = function(area, options) {
+ if (typeof area == "string")
+ area = document.getElementById(area);
+
+ options = options || {};
+ if (area.style.width && options.width == null)
+ options.width = area.style.width;
+ if (area.style.height && options.height == null)
+ options.height = area.style.height;
+ if (options.content == null) options.content = area.value;
+
+ if (area.form) {
+ function updateField() {
+ area.value = mirror.getCode();
+ }
+ if (typeof area.form.addEventListener == "function")
+ area.form.addEventListener("submit", updateField, false);
+ else
+ area.form.attachEvent("onsubmit", updateField);
+ }
+
+ function insert(frame) {
+ if (area.nextSibling)
+ area.parentNode.insertBefore(frame, area.nextSibling);
+ else
+ area.parentNode.appendChild(frame);
+ }
+
+ area.style.display = "none";
+ var mirror = new CodeMirror(insert, options);
+ return mirror;
+ };
+
+ CodeMirror.isProbablySupported = function() {
+ // This is rather awful, but can be useful.
+ var match;
+ if (window.opera)
+ return Number(window.opera.version()) >= 9.52;
+ else if (/Apple Computers, Inc/.test(navigator.vendor) && (match = navigator.userAgent.match(/Version\/(\d+(?:\.\d+)?)\./)))
+ return Number(match[1]) >= 3;
+ else if (document.selection && window.ActiveXObject && (match = navigator.userAgent.match(/MSIE (\d+(?:\.\d*)?)\b/)))
+ return Number(match[1]) >= 6;
+ else if (match = navigator.userAgent.match(/gecko\/(\d{8})/i))
+ return Number(match[1]) >= 20050901;
+ else if (match = navigator.userAgent.match(/AppleWebKit\/(\d+)/))
+ return Number(match[1]) >= 525;
+ else
+ return null;
+ };
+
+ return CodeMirror;
+})();
diff --git a/media/CodeMirror-0.62/js/editor.js b/media/CodeMirror-0.62/js/editor.js
new file mode 100644
index 0000000..e580ccb
--- /dev/null
+++ b/media/CodeMirror-0.62/js/editor.js
@@ -0,0 +1,1303 @@
+/* The Editor object manages the content of the editable frame. It
+ * catches events, colours nodes, and indents lines. This file also
+ * holds some functions for transforming arbitrary DOM structures into
+ * plain sequences of and elements
+ */
+
+// Make sure a string does not contain two consecutive 'collapseable'
+// whitespace characters.
+function makeWhiteSpace(n) {
+ var buffer = [], nb = true;
+ for (; n > 0; n--) {
+ buffer.push((nb || n == 1) ? nbsp : " ");
+ nb = !nb;
+ }
+ return buffer.join("");
+}
+
+// Create a set of white-space characters that will not be collapsed
+// by the browser, but will not break text-wrapping either.
+function fixSpaces(string) {
+ if (string.charAt(0) == " ") string = nbsp + string.slice(1);
+ return string.replace(/\t/g, function(){return makeWhiteSpace(indentUnit);})
+ .replace(/[ \u00a0]{2,}/g, function(s) {return makeWhiteSpace(s.length);});
+}
+
+function cleanText(text) {
+ return text.replace(/\u00a0/g, " ").replace(/\u200b/g, "");
+}
+
+// Create a SPAN node with the expected properties for document part
+// spans.
+function makePartSpan(value, doc) {
+ var text = value;
+ if (value.nodeType == 3) text = value.nodeValue;
+ else value = doc.createTextNode(text);
+
+ var span = doc.createElement("SPAN");
+ span.isPart = true;
+ span.appendChild(value);
+ span.currentText = text;
+ return span;
+}
+
+// On webkit, when the last BR of the document does not have text
+// behind it, the cursor can not be put on the line after it. This
+// makes pressing enter at the end of the document occasionally do
+// nothing (or at least seem to do nothing). To work around it, this
+// function makes sure the document ends with a span containing a
+// zero-width space character. The traverseDOM iterator filters such
+// character out again, so that the parsers won't see them. This
+// function is called from a few strategic places to make sure the
+// zwsp is restored after the highlighting process eats it.
+var webkitLastLineHack = webkit ?
+ function(container) {
+ var last = container.lastChild;
+ if (!last || !last.isPart || last.textContent != "\u200b")
+ container.appendChild(makePartSpan("\u200b", container.ownerDocument));
+ } : function() {};
+
+var Editor = (function(){
+ // The HTML elements whose content should be suffixed by a newline
+ // when converting them to flat text.
+ var newlineElements = {"P": true, "DIV": true, "LI": true};
+
+ function asEditorLines(string) {
+ var tab = makeWhiteSpace(indentUnit);
+ return map(string.replace(/\t/g, tab).replace(/\u00a0/g, " ").replace(/\r\n?/g, "\n").split("\n"), fixSpaces);
+ }
+
+ // Helper function for traverseDOM. Flattens an arbitrary DOM node
+ // into an array of textnodes and tags.
+ function simplifyDOM(root) {
+ var doc = root.ownerDocument;
+ var result = [];
+ var leaving = true;
+
+ function simplifyNode(node) {
+ if (node.nodeType == 3) {
+ var text = node.nodeValue = fixSpaces(node.nodeValue.replace(/[\r\u200b]/g, "").replace(/\n/g, " "));
+ if (text.length) leaving = false;
+ result.push(node);
+ }
+ else if (node.nodeName == "BR" && node.childNodes.length == 0) {
+ leaving = true;
+ result.push(node);
+ }
+ else {
+ forEach(node.childNodes, simplifyNode);
+ if (!leaving && newlineElements.hasOwnProperty(node.nodeName)) {
+ leaving = true;
+ result.push(doc.createElement("BR"));
+ }
+ }
+ }
+
+ simplifyNode(root);
+ return result;
+ }
+
+ // Creates a MochiKit-style iterator that goes over a series of DOM
+ // nodes. The values it yields are strings, the textual content of
+ // the nodes. It makes sure that all nodes up to and including the
+ // one whose text is being yielded have been 'normalized' to be just
+ // and elements.
+ // See the story.html file for some short remarks about the use of
+ // continuation-passing style in this iterator.
+ function traverseDOM(start){
+ function yield(value, c){cc = c; return value;}
+ function push(fun, arg, c){return function(){return fun(arg, c);};}
+ function stop(){cc = stop; throw StopIteration;};
+ var cc = push(scanNode, start, stop);
+ var owner = start.ownerDocument;
+ var nodeQueue = [];
+
+ // Create a function that can be used to insert nodes after the
+ // one given as argument.
+ function pointAt(node){
+ var parent = node.parentNode;
+ var next = node.nextSibling;
+ return function(newnode) {
+ parent.insertBefore(newnode, next);
+ };
+ }
+ var point = null;
+
+ // Insert a normalized node at the current point. If it is a text
+ // node, wrap it in a , and give that span a currentText
+ // property -- this is used to cache the nodeValue, because
+ // directly accessing nodeValue is horribly slow on some browsers.
+ // The dirty property is used by the highlighter to determine
+ // which parts of the document have to be re-highlighted.
+ function insertPart(part){
+ var text = "\n";
+ if (part.nodeType == 3) {
+ select.snapshotChanged();
+ part = makePartSpan(part, owner);
+ text = part.currentText;
+ }
+ part.dirty = true;
+ nodeQueue.push(part);
+ point(part);
+ return text;
+ }
+
+ // Extract the text and newlines from a DOM node, insert them into
+ // the document, and yield the textual content. Used to replace
+ // non-normalized nodes.
+ function writeNode(node, c){
+ var toYield = [];
+ forEach(simplifyDOM(node), function(part) {
+ toYield.push(insertPart(part));
+ });
+ return yield(toYield.join(""), c);
+ }
+
+ // Check whether a node is a normalized element.
+ function partNode(node){
+ if (node.isPart && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
+ node.currentText = node.firstChild.nodeValue;
+ return !/[\n\t\r]/.test(node.currentText);
+ }
+ return false;
+ }
+
+ // Handle a node. Add its successor to the continuation if there
+ // is one, find out whether the node is normalized. If it is,
+ // yield its content, otherwise, normalize it (writeNode will take
+ // care of yielding).
+ function scanNode(node, c){
+ if (node.nextSibling)
+ c = push(scanNode, node.nextSibling, c);
+
+ if (partNode(node)){
+ nodeQueue.push(node);
+ return yield(node.currentText, c);
+ }
+ else if (node.nodeName == "BR") {
+ nodeQueue.push(node);
+ return yield("\n", c);
+ }
+ else {
+ point = pointAt(node);
+ removeElement(node);
+ return writeNode(node, c);
+ }
+ }
+
+ // MochiKit iterators are objects with a next function that
+ // returns the next value or throws StopIteration when there are
+ // no more values.
+ return {next: function(){return cc();}, nodes: nodeQueue};
+ }
+
+ // Determine the text size of a processed node.
+ function nodeSize(node) {
+ if (node.nodeName == "BR")
+ return 1;
+ else
+ return node.currentText.length;
+ }
+
+ // Search backwards through the top-level nodes until the next BR or
+ // the start of the frame.
+ function startOfLine(node) {
+ while (node && node.nodeName != "BR") node = node.previousSibling;
+ return node;
+ }
+ function endOfLine(node, container) {
+ if (!node) node = container.firstChild;
+ else if (node.nodeName == "BR") node = node.nextSibling;
+
+ while (node && node.nodeName != "BR") node = node.nextSibling;
+ return node;
+ }
+
+ function time() {return new Date().getTime();}
+
+ // Replace all DOM nodes in the current selection with new ones.
+ // Needed to prevent issues in IE where the old DOM nodes can be
+ // pasted back into the document, still holding their old undo
+ // information.
+ function scrubPasted(container, start, start2) {
+ var end = select.selectionTopNode(container, true),
+ doc = container.ownerDocument;
+ if (start != null && start.parentNode != container) start = start2;
+ if (start === false) start = null;
+ if (start == end || !end || !container.firstChild) return;
+
+ var clear = traverseDOM(start ? start.nextSibling : container.firstChild);
+ while (end.parentNode == container) try{clear.next();}catch(e){break;}
+ forEach(clear.nodes, function(node) {
+ var newNode = node.nodeName == "BR" ? doc.createElement("BR") : makePartSpan(node.currentText, doc);
+ container.replaceChild(newNode, node);
+ });
+ }
+
+ // Client interface for searching the content of the editor. Create
+ // these by calling CodeMirror.getSearchCursor. To use, call
+ // findNext on the resulting object -- this returns a boolean
+ // indicating whether anything was found, and can be called again to
+ // skip to the next find. Use the select and replace methods to
+ // actually do something with the found locations.
+ function SearchCursor(editor, string, fromCursor) {
+ this.editor = editor;
+ this.history = editor.history;
+ this.history.commit();
+
+ // Are we currently at an occurrence of the search string?
+ this.atOccurrence = false;
+ // The object stores a set of nodes coming after its current
+ // position, so that when the current point is taken out of the
+ // DOM tree, we can still try to continue.
+ this.fallbackSize = 15;
+ var cursor;
+ // Start from the cursor when specified and a cursor can be found.
+ if (fromCursor && (cursor = select.cursorPos(this.editor.container))) {
+ this.line = cursor.node;
+ this.offset = cursor.offset;
+ }
+ else {
+ this.line = null;
+ this.offset = 0;
+ }
+ this.valid = !!string;
+
+ // Create a matcher function based on the kind of string we have.
+ var target = string.split("\n"), self = this;
+ this.matches = (target.length == 1) ?
+ // For one-line strings, searching can be done simply by calling
+ // indexOf on the current line.
+ function() {
+ var match = cleanText(self.history.textAfter(self.line).slice(self.offset)).indexOf(string);
+ if (match > -1)
+ return {from: {node: self.line, offset: self.offset + match},
+ to: {node: self.line, offset: self.offset + match + string.length}};
+ } :
+ // Multi-line strings require internal iteration over lines, and
+ // some clunky checks to make sure the first match ends at the
+ // end of the line and the last match starts at the start.
+ function() {
+ var firstLine = cleanText(self.history.textAfter(self.line).slice(self.offset));
+ var match = firstLine.lastIndexOf(target[0]);
+ if (match == -1 || match != firstLine.length - target[0].length)
+ return false;
+ var startOffset = self.offset + match;
+
+ var line = self.history.nodeAfter(self.line);
+ for (var i = 1; i < target.length - 1; i++) {
+ if (cleanText(self.history.textAfter(line)) != target[i])
+ return false;
+ line = self.history.nodeAfter(line);
+ }
+
+ if (cleanText(self.history.textAfter(line)).indexOf(target[target.length - 1]) != 0)
+ return false;
+
+ return {from: {node: self.line, offset: startOffset},
+ to: {node: line, offset: target[target.length - 1].length}};
+ };
+ }
+
+ SearchCursor.prototype = {
+ findNext: function() {
+ if (!this.valid) return false;
+ this.atOccurrence = false;
+ var self = this;
+
+ // Go back to the start of the document if the current line is
+ // no longer in the DOM tree.
+ if (this.line && !this.line.parentNode) {
+ this.line = null;
+ this.offset = 0;
+ }
+
+ // Set the cursor's position one character after the given
+ // position.
+ function saveAfter(pos) {
+ if (self.history.textAfter(pos.node).length < pos.offset) {
+ self.line = pos.node;
+ self.offset = pos.offset + 1;
+ }
+ else {
+ self.line = self.history.nodeAfter(pos.node);
+ self.offset = 0;
+ }
+ }
+
+ while (true) {
+ var match = this.matches();
+ // Found the search string.
+ if (match) {
+ this.atOccurrence = match;
+ saveAfter(match.from);
+ return true;
+ }
+ this.line = this.history.nodeAfter(this.line);
+ this.offset = 0;
+ // End of document.
+ if (!this.line) {
+ this.valid = false;
+ return false;
+ }
+ }
+ },
+
+ select: function() {
+ if (this.atOccurrence) {
+ select.setCursorPos(this.editor.container, this.atOccurrence.from, this.atOccurrence.to);
+ select.scrollToCursor(this.editor.container);
+ }
+ },
+
+ replace: function(string) {
+ if (this.atOccurrence) {
+ var end = this.editor.replaceRange(this.atOccurrence.from, this.atOccurrence.to, string);
+ this.line = end.node;
+ this.offset = end.offset;
+ this.atOccurrence = false;
+ }
+ }
+ };
+
+ // The Editor object is the main inside-the-iframe interface.
+ function Editor(options) {
+ this.options = options;
+ window.indentUnit = options.indentUnit;
+ this.parent = parent;
+ this.doc = document;
+ var container = this.container = this.doc.body;
+ this.win = window;
+ this.history = new History(container, options.undoDepth, options.undoDelay,
+ this, options.onChange);
+ var self = this;
+
+ if (!Editor.Parser)
+ throw "No parser loaded.";
+ if (options.parserConfig && Editor.Parser.configure)
+ Editor.Parser.configure(options.parserConfig);
+
+ if (!options.readOnly)
+ select.setCursorPos(container, {node: null, offset: 0});
+
+ this.dirty = [];
+ if (options.content)
+ this.importCode(options.content);
+ else // FF acts weird when the editable document is completely empty
+ container.appendChild(this.doc.createElement("BR"));
+
+ if (!options.readOnly) {
+ if (options.continuousScanning !== false) {
+ this.scanner = this.documentScanner(options.passTime);
+ this.delayScanning();
+ }
+
+ function setEditable() {
+ // In IE, designMode frames can not run any scripts, so we use
+ // contentEditable instead.
+ if (document.body.contentEditable != undefined && internetExplorer)
+ document.body.contentEditable = "true";
+ else
+ document.designMode = "on";
+
+ document.documentElement.style.borderWidth = "0";
+ if (!options.textWrapping)
+ container.style.whiteSpace = "nowrap";
+ }
+
+ // If setting the frame editable fails, try again when the user
+ // focus it (happens when the frame is not visible on
+ // initialisation, in Firefox).
+ try {
+ setEditable();
+ }
+ catch(e) {
+ var focusEvent = addEventHandler(document, "focus", function() {
+ focusEvent();
+ setEditable();
+ }, true);
+ }
+
+ addEventHandler(document, "keydown", method(this, "keyDown"));
+ addEventHandler(document, "keypress", method(this, "keyPress"));
+ addEventHandler(document, "keyup", method(this, "keyUp"));
+
+ function cursorActivity() {self.cursorActivity(false);}
+ addEventHandler(document.body, "mouseup", cursorActivity);
+ addEventHandler(document.body, "paste", function(event) {
+ cursorActivity();
+ if (internetExplorer) {
+ var text = null;
+ try {text = window.clipboardData.getData("Text");}catch(e){}
+ if (text != null) {
+ self.replaceSelection(text);
+ event.stop();
+ }
+ else {
+ var start = select.selectionTopNode(self.container, true),
+ start2 = start && start.previousSibling;
+ setTimeout(function(){scrubPasted(self.container, start, start2);}, 0);
+ }
+ }
+ });
+ addEventHandler(document.body, "cut", cursorActivity);
+
+ if (this.options.autoMatchParens)
+ addEventHandler(document.body, "click", method(this, "scheduleParenBlink"));
+ }
+ else if (!options.textWrapping) {
+ container.style.whiteSpace = "nowrap";
+ }
+ }
+
+ function isSafeKey(code) {
+ return (code >= 16 && code <= 18) || // shift, control, alt
+ (code >= 33 && code <= 40); // arrows, home, end
+ }
+
+ Editor.prototype = {
+ // Import a piece of code into the editor.
+ importCode: function(code) {
+ this.history.push(null, null, asEditorLines(code));
+ this.history.reset();
+ },
+
+ // Extract the code from the editor.
+ getCode: function() {
+ if (!this.container.firstChild)
+ return "";
+
+ var accum = [];
+ select.markSelection(this.win);
+ forEach(traverseDOM(this.container.firstChild), method(accum, "push"));
+ webkitLastLineHack(this.container);
+ select.selectMarked();
+ return cleanText(accum.join(""));
+ },
+
+ checkLine: function(node) {
+ if (node === false || !(node == null || node.parentNode == this.container))
+ throw parent.CodeMirror.InvalidLineHandle;
+ },
+
+ cursorPosition: function(start) {
+ if (start == null) start = true;
+ var pos = select.cursorPos(this.container, start);
+ if (pos) return {line: pos.node, character: pos.offset};
+ else return {line: null, character: 0};
+ },
+
+ firstLine: function() {
+ return null;
+ },
+
+ lastLine: function() {
+ if (this.container.lastChild) return startOfLine(this.container.lastChild);
+ else return null;
+ },
+
+ nextLine: function(line) {
+ this.checkLine(line);
+ var end = endOfLine(line, this.container);
+ return end || false;
+ },
+
+ prevLine: function(line) {
+ this.checkLine(line);
+ if (line == null) return false;
+ return startOfLine(line.previousSibling);
+ },
+
+ selectLines: function(startLine, startOffset, endLine, endOffset) {
+ this.checkLine(startLine);
+ var start = {node: startLine, offset: startOffset}, end = null;
+ if (endOffset !== undefined) {
+ this.checkLine(endLine);
+ end = {node: endLine, offset: endOffset};
+ }
+ select.setCursorPos(this.container, start, end);
+ select.scrollToCursor(this.container);
+ },
+
+ lineContent: function(line) {
+ this.checkLine(line);
+ var accum = [];
+ for (line = line ? line.nextSibling : this.container.firstChild;
+ line && line.nodeName != "BR"; line = line.nextSibling)
+ accum.push(nodeText(line));
+ return cleanText(accum.join(""));
+ },
+
+ setLineContent: function(line, content) {
+ this.history.commit();
+ this.replaceRange({node: line, offset: 0},
+ {node: line, offset: this.history.textAfter(line).length},
+ content);
+ this.addDirtyNode(line);
+ this.scheduleHighlight();
+ },
+
+ insertIntoLine: function(line, position, content) {
+ var before = null;
+ if (position == "end") {
+ before = endOfLine(line, this.container);
+ }
+ else {
+ for (var cur = line ? line.nextSibling : this.container.firstChild; cur; cur = cur.nextSibling) {
+ if (position == 0) {
+ before = cur;
+ break;
+ }
+ var text = (cur.innerText || cur.textContent || cur.nodeValue || "");
+ if (text.length > position) {
+ before = cur.nextSibling;
+ content = text.slice(0, position) + content + text.slice(position);
+ removeElement(cur);
+ break;
+ }
+ position -= text.length;
+ }
+ }
+
+ var lines = asEditorLines(content), doc = this.container.ownerDocument;
+ for (var i = 0; i < lines.length; i++) {
+ if (i > 0) this.container.insertBefore(doc.createElement("BR"), before);
+ this.container.insertBefore(makePartSpan(lines[i], doc), before);
+ }
+ this.addDirtyNode(line);
+ this.scheduleHighlight();
+ },
+
+ // Retrieve the selected text.
+ selectedText: function() {
+ var h = this.history;
+ h.commit();
+
+ var start = select.cursorPos(this.container, true),
+ end = select.cursorPos(this.container, false);
+ if (!start || !end) return "";
+
+ if (start.node == end.node)
+ return h.textAfter(start.node).slice(start.offset, end.offset);
+
+ var text = [h.textAfter(start.node).slice(start.offset)];
+ for (var pos = h.nodeAfter(start.node); pos != end.node; pos = h.nodeAfter(pos))
+ text.push(h.textAfter(pos));
+ text.push(h.textAfter(end.node).slice(0, end.offset));
+ return cleanText(text.join("\n"));
+ },
+
+ // Replace the selection with another piece of text.
+ replaceSelection: function(text) {
+ this.history.commit();
+ var start = select.cursorPos(this.container, true),
+ end = select.cursorPos(this.container, false);
+ if (!start || !end) return;
+
+ end = this.replaceRange(start, end, text);
+ select.setCursorPos(this.container, start, end);
+ },
+
+ replaceRange: function(from, to, text) {
+ var lines = asEditorLines(text);
+ lines[0] = this.history.textAfter(from.node).slice(0, from.offset) + lines[0];
+ var lastLine = lines[lines.length - 1];
+ lines[lines.length - 1] = lastLine + this.history.textAfter(to.node).slice(to.offset);
+ var end = this.history.nodeAfter(to.node);
+ this.history.push(from.node, end, lines);
+ return {node: this.history.nodeBefore(end),
+ offset: lastLine.length};
+ },
+
+ getSearchCursor: function(string, fromCursor) {
+ return new SearchCursor(this, string, fromCursor);
+ },
+
+ // Re-indent the whole buffer
+ reindent: function() {
+ if (this.container.firstChild)
+ this.indentRegion(null, this.container.lastChild);
+ },
+
+ reindentSelection: function(direction) {
+ if (!select.somethingSelected(this.win)) {
+ this.indentAtCursor(direction);
+ }
+ else {
+ var start = select.selectionTopNode(this.container, true),
+ end = select.selectionTopNode(this.container, false);
+ if (start === false || end === false) return;
+ this.indentRegion(start, end, direction);
+ }
+ },
+
+ grabKeys: function(eventHandler, filter) {
+ this.frozen = eventHandler;
+ this.keyFilter = filter;
+ },
+ ungrabKeys: function() {
+ this.frozen = "leave";
+ this.keyFilter = null;
+ },
+
+ setParser: function(name) {
+ Editor.Parser = window[name];
+ if (this.container.firstChild) {
+ forEach(this.container.childNodes, function(n) {
+ if (n.nodeType != 3) n.dirty = true;
+ });
+ this.addDirtyNode(this.firstChild);
+ this.scheduleHighlight();
+ }
+ },
+
+ // Intercept enter and tab, and assign their new functions.
+ keyDown: function(event) {
+ if (this.frozen == "leave") this.frozen = null;
+ if (this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode))) {
+ event.stop();
+ this.frozen(event);
+ return;
+ }
+
+ var code = event.keyCode;
+ // Don't scan when the user is typing.
+ this.delayScanning();
+ // Schedule a paren-highlight event, if configured.
+ if (this.options.autoMatchParens)
+ this.scheduleParenBlink();
+
+ // The variouschecks for !altKey are there because AltGr sets both
+ // ctrlKey and altKey to true, and should not be recognised as
+ // Control.
+ if (code == 13) { // enter
+ if (event.ctrlKey && !event.altKey) {
+ this.reparseBuffer();
+ }
+ else {
+ select.insertNewlineAtCursor(this.win);
+ this.indentAtCursor();
+ select.scrollToCursor(this.container);
+ }
+ event.stop();
+ }
+ else if (code == 9 && this.options.tabMode != "default") { // tab
+ this.handleTab(!event.ctrlKey && !event.shiftKey);
+ event.stop();
+ }
+ else if (code == 32 && event.shiftKey && this.options.tabMode == "default") { // space
+ this.handleTab(true);
+ event.stop();
+ }
+ else if (code == 36 && !event.shiftKey) { // home
+ if (this.home())
+ event.stop();
+ }
+ else if ((code == 219 || code == 221) && event.ctrlKey && !event.altKey) { // [, ]
+ this.blinkParens(event.shiftKey);
+ event.stop();
+ }
+ else if (event.metaKey && !event.shiftKey && (code == 37 || code == 39)) { // Meta-left/right
+ var cursor = select.selectionTopNode(this.container);
+ if (cursor === false || !this.container.firstChild) return;
+
+ if (code == 37) select.focusAfterNode(startOfLine(cursor), this.container);
+ else {
+ var end = endOfLine(cursor, this.container);
+ select.focusAfterNode(end ? end.previousSibling : this.container.lastChild, this.container);
+ }
+ event.stop();
+ }
+ else if ((event.ctrlKey || event.metaKey) && !event.altKey) {
+ if ((event.shiftKey && code == 90) || code == 89) { // shift-Z, Y
+ select.scrollToNode(this.history.redo());
+ event.stop();
+ }
+ else if (code == 90 || code == 8) { // Z, backspace
+ select.scrollToNode(this.history.undo());
+ event.stop();
+ }
+ else if (code == 83 && this.options.saveFunction) { // S
+ this.options.saveFunction();
+ event.stop();
+ }
+ }
+ },
+
+ // Check for characters that should re-indent the current line,
+ // and prevent Opera from handling enter and tab anyway.
+ keyPress: function(event) {
+ var electric = /indent|default/.test(this.options.tabMode) && Editor.Parser.electricChars;
+ // Hack for Opera, and Firefox on OS X, in which stopping a
+ // keydown event does not prevent the associated keypress event
+ // from happening, so we have to cancel enter and tab again
+ // here.
+ if ((this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode))) ||
+ event.code == 13 || (event.code == 9 && this.options.tabMode != "default") ||
+ (event.keyCode == 32 && event.shiftKey && this.options.tabMode == "default"))
+ event.stop();
+ else if (electric && electric.indexOf(event.character) != -1)
+ this.parent.setTimeout(method(this, "indentAtCursor"), 0);
+ },
+
+ // Mark the node at the cursor dirty when a non-safe key is
+ // released.
+ keyUp: function(event) {
+ this.cursorActivity(isSafeKey(event.keyCode));
+ },
+
+ // Indent the line following a given , or null for the first
+ // line. If given a element, this must have been highlighted
+ // so that it has an indentation method. Returns the whitespace
+ // element that has been modified or created (if any).
+ indentLineAfter: function(start, direction) {
+ // whiteSpace is the whitespace span at the start of the line,
+ // or null if there is no such node.
+ var whiteSpace = start ? start.nextSibling : this.container.firstChild;
+ if (whiteSpace && !hasClass(whiteSpace, "whitespace"))
+ whiteSpace = null;
+
+ // Sometimes the start of the line can influence the correct
+ // indentation, so we retrieve it.
+ var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild);
+ var nextChars = (start && firstText && firstText.currentText) ? firstText.currentText : "";
+
+ // Ask the lexical context for the correct indentation, and
+ // compute how much this differs from the current indentation.
+ var newIndent = 0, curIndent = whiteSpace ? whiteSpace.currentText.length : 0;
+ if (direction != null && this.options.tabMode == "shift")
+ newIndent = direction ? curIndent + indentUnit : Math.max(0, curIndent - indentUnit)
+ else if (start)
+ newIndent = start.indentation(nextChars, curIndent, direction);
+ else if (Editor.Parser.firstIndentation)
+ newIndent = Editor.Parser.firstIndentation(nextChars, curIndent, direction);
+ var indentDiff = newIndent - curIndent;
+
+ // If there is too much, this is just a matter of shrinking a span.
+ if (indentDiff < 0) {
+ if (newIndent == 0) {
+ if (firstText) select.snapshotMove(whiteSpace.firstChild, firstText.firstChild, 0);
+ removeElement(whiteSpace);
+ whiteSpace = null;
+ }
+ else {
+ select.snapshotMove(whiteSpace.firstChild, whiteSpace.firstChild, indentDiff, true);
+ whiteSpace.currentText = makeWhiteSpace(newIndent);
+ whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
+ }
+ }
+ // Not enough...
+ else if (indentDiff > 0) {
+ // If there is whitespace, we grow it.
+ if (whiteSpace) {
+ whiteSpace.currentText = makeWhiteSpace(newIndent);
+ whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
+ }
+ // Otherwise, we have to add a new whitespace node.
+ else {
+ whiteSpace = makePartSpan(makeWhiteSpace(newIndent), this.doc);
+ whiteSpace.className = "whitespace";
+ if (start) insertAfter(whiteSpace, start);
+ else this.container.insertBefore(whiteSpace, this.container.firstChild);
+ }
+ if (firstText) select.snapshotMove(firstText.firstChild, whiteSpace.firstChild, curIndent, false, true);
+ }
+ if (indentDiff != 0) this.addDirtyNode(start);
+ return whiteSpace;
+ },
+
+ // Re-highlight the selected part of the document.
+ highlightAtCursor: function() {
+ var pos = select.selectionTopNode(this.container, true);
+ var to = select.selectionTopNode(this.container, false);
+ if (pos === false || to === false) return;
+
+ select.markSelection(this.win);
+ if (this.highlight(pos, endOfLine(to, this.container), true, 20) === false)
+ return false;
+ select.selectMarked();
+ return true;
+ },
+
+ // When tab is pressed with text selected, the whole selection is
+ // re-indented, when nothing is selected, the line with the cursor
+ // is re-indented.
+ handleTab: function(direction) {
+ if (this.options.tabMode == "spaces")
+ select.insertTabAtCursor(this.win);
+ else
+ this.reindentSelection(direction);
+ },
+
+ home: function() {
+ var cur = select.selectionTopNode(this.container, true), start = cur;
+ if (cur === false || !(!cur || cur.isPart || cur.nodeName == "BR") || !this.container.firstChild)
+ return false;
+
+ while (cur && cur.nodeName != "BR") cur = cur.previousSibling;
+ var next = cur ? cur.nextSibling : this.container.firstChild;
+ if (next && next != start && next.isPart && hasClass(next, "whitespace"))
+ select.focusAfterNode(next, this.container);
+ else
+ select.focusAfterNode(cur, this.container);
+ return true;
+ },
+
+ // Delay (or initiate) the next paren blink event.
+ scheduleParenBlink: function() {
+ if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
+ var self = this;
+ this.parenEvent = this.parent.setTimeout(function(){self.blinkParens();}, 300);
+ },
+
+ // Take the token before the cursor. If it contains a character in
+ // '()[]{}', search for the matching paren/brace/bracket, and
+ // highlight them in green for a moment, or red if no proper match
+ // was found.
+ blinkParens: function(jump) {
+ if (!window.select) return;
+ // Clear the event property.
+ if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
+ this.parenEvent = null;
+
+ // Extract a 'paren' from a piece of text.
+ function paren(node) {
+ if (node.currentText) {
+ var match = node.currentText.match(/^[\s\u00a0]*([\(\)\[\]{}])[\s\u00a0]*$/);
+ return match && match[1];
+ }
+ }
+ // Determine the direction a paren is facing.
+ function forward(ch) {
+ return /[\(\[\{]/.test(ch);
+ }
+
+ var ch, self = this, cursor = select.selectionTopNode(this.container, true);
+ if (!cursor || !this.highlightAtCursor()) return;
+ cursor = select.selectionTopNode(this.container, true);
+ if (!(cursor && ((ch = paren(cursor)) || (cursor = cursor.nextSibling) && (ch = paren(cursor)))))
+ return;
+ // We only look for tokens with the same className.
+ var className = cursor.className, dir = forward(ch), match = matching[ch];
+
+ // Since parts of the document might not have been properly
+ // highlighted, and it is hard to know in advance which part we
+ // have to scan, we just try, and when we find dirty nodes we
+ // abort, parse them, and re-try.
+ function tryFindMatch() {
+ var stack = [], ch, ok = true;;
+ for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) {
+ if (runner.className == className && runner.nodeName == "SPAN" && (ch = paren(runner))) {
+ if (forward(ch) == dir)
+ stack.push(ch);
+ else if (!stack.length)
+ ok = false;
+ else if (stack.pop() != matching[ch])
+ ok = false;
+ if (!stack.length) break;
+ }
+ else if (runner.dirty || runner.nodeName != "SPAN" && runner.nodeName != "BR") {
+ return {node: runner, status: "dirty"};
+ }
+ }
+ return {node: runner, status: runner && ok};
+ }
+ // Temporarily give the relevant nodes a colour.
+ function blink(node, ok) {
+ node.style.fontWeight = "bold";
+ node.style.color = ok ? "#8F8" : "#F88";
+ self.parent.setTimeout(function() {node.style.fontWeight = ""; node.style.color = "";}, 500);
+ }
+
+ while (true) {
+ var found = tryFindMatch();
+ if (found.status == "dirty") {
+ this.highlight(found.node, endOfLine(found.node));
+ // Needed because in some corner cases a highlight does not
+ // reach a node.
+ found.node.dirty = false;
+ continue;
+ }
+ else {
+ blink(cursor, found.status);
+ if (found.node) {
+ blink(found.node, found.status);
+ if (jump) select.focusAfterNode(found.node.previousSibling, this.container);
+ }
+ break;
+ }
+ }
+ },
+
+ // Adjust the amount of whitespace at the start of the line that
+ // the cursor is on so that it is indented properly.
+ indentAtCursor: function(direction) {
+ if (!this.container.firstChild) return;
+ // The line has to have up-to-date lexical information, so we
+ // highlight it first.
+ if (!this.highlightAtCursor()) return;
+ var cursor = select.selectionTopNode(this.container, false);
+ // If we couldn't determine the place of the cursor,
+ // there's nothing to indent.
+ if (cursor === false)
+ return;
+ var lineStart = startOfLine(cursor);
+ var whiteSpace = this.indentLineAfter(lineStart, direction);
+ if (cursor == lineStart && whiteSpace)
+ cursor = whiteSpace;
+ // This means the indentation has probably messed up the cursor.
+ if (cursor == whiteSpace)
+ select.focusAfterNode(cursor, this.container);
+ },
+
+ // Indent all lines whose start falls inside of the current
+ // selection.
+ indentRegion: function(start, end, direction) {
+ var current = (start = startOfLine(start)), before = start && startOfLine(start.previousSibling);
+ if (end.nodeName != "BR") end = endOfLine(end, this.container);
+
+ do {
+ var next = endOfLine(current, this.container);
+ if (current) this.highlight(before, next, true);
+ this.indentLineAfter(current, direction);
+ before = current;
+ current = next;
+ } while (current != end);
+ select.setCursorPos(this.container, {node: start, offset: 0}, {node: end, offset: 0});
+ },
+
+ // Find the node that the cursor is in, mark it as dirty, and make
+ // sure a highlight pass is scheduled.
+ cursorActivity: function(safe) {
+ if (internetExplorer) {
+ this.container.createTextRange().execCommand("unlink");
+ this.selectionSnapshot = select.selectionCoords(this.win);
+ }
+
+ var activity = this.options.cursorActivity;
+ if (!safe || activity) {
+ var cursor = select.selectionTopNode(this.container, false);
+ if (cursor === false || !this.container.firstChild) return;
+ cursor = cursor || this.container.firstChild;
+ if (activity) activity(cursor);
+ if (!safe) {
+ this.scheduleHighlight();
+ this.addDirtyNode(cursor);
+ }
+ }
+ },
+
+ reparseBuffer: function() {
+ forEach(this.container.childNodes, function(node) {node.dirty = true;});
+ if (this.container.firstChild)
+ this.addDirtyNode(this.container.firstChild);
+ },
+
+ // Add a node to the set of dirty nodes, if it isn't already in
+ // there.
+ addDirtyNode: function(node) {
+ node = node || this.container.firstChild;
+ if (!node) return;
+
+ for (var i = 0; i < this.dirty.length; i++)
+ if (this.dirty[i] == node) return;
+
+ if (node.nodeType != 3)
+ node.dirty = true;
+ this.dirty.push(node);
+ },
+
+ // Cause a highlight pass to happen in options.passDelay
+ // milliseconds. Clear the existing timeout, if one exists. This
+ // way, the passes do not happen while the user is typing, and
+ // should as unobtrusive as possible.
+ scheduleHighlight: function() {
+ // Timeouts are routed through the parent window, because on
+ // some browsers designMode windows do not fire timeouts.
+ var self = this;
+ this.parent.clearTimeout(this.highlightTimeout);
+ this.highlightTimeout = this.parent.setTimeout(function(){self.highlightDirty();}, this.options.passDelay);
+ },
+
+ // Fetch one dirty node, and remove it from the dirty set.
+ getDirtyNode: function() {
+ while (this.dirty.length > 0) {
+ var found = this.dirty.pop();
+ // IE8 sometimes throws an unexplainable 'invalid argument'
+ // exception for found.parentNode
+ try {
+ // If the node has been coloured in the meantime, or is no
+ // longer in the document, it should not be returned.
+ while (found && found.parentNode != this.container)
+ found = found.parentNode
+ if (found && (found.dirty || found.nodeType == 3))
+ return found;
+ } catch (e) {}
+ }
+ return null;
+ },
+
+ // Pick dirty nodes, and highlight them, until options.passTime
+ // milliseconds have gone by. The highlight method will continue
+ // to next lines as long as it finds dirty nodes. It returns
+ // information about the place where it stopped. If there are
+ // dirty nodes left after this function has spent all its lines,
+ // it shedules another highlight to finish the job.
+ highlightDirty: function(force) {
+ // Prevent FF from raising an error when it is firing timeouts
+ // on a page that's no longer loaded.
+ if (!window.select) return;
+
+ if (!this.options.readOnly) select.markSelection(this.win);
+ var start, endTime = force ? null : time() + this.options.passTime;
+ while (time() < endTime && (start = this.getDirtyNode())) {
+ var result = this.highlight(start, endTime);
+ if (result && result.node && result.dirty)
+ this.addDirtyNode(result.node);
+ }
+ if (!this.options.readOnly) select.selectMarked();
+ if (start) this.scheduleHighlight();
+ return this.dirty.length == 0;
+ },
+
+ // Creates a function that, when called through a timeout, will
+ // continuously re-parse the document.
+ documentScanner: function(passTime) {
+ var self = this, pos = null;
+ return function() {
+ // FF timeout weirdness workaround.
+ if (!window.select) return;
+ // If the current node is no longer in the document... oh
+ // well, we start over.
+ if (pos && pos.parentNode != self.container)
+ pos = null;
+ select.markSelection(self.win);
+ var result = self.highlight(pos, time() + passTime, true);
+ select.selectMarked();
+ var newPos = result ? (result.node && result.node.nextSibling) : null;
+ pos = (pos == newPos) ? null : newPos;
+ self.delayScanning();
+ };
+ },
+
+ // Starts the continuous scanning process for this document after
+ // a given interval.
+ delayScanning: function() {
+ if (this.scanner) {
+ this.parent.clearTimeout(this.documentScan);
+ this.documentScan = this.parent.setTimeout(this.scanner, this.options.continuousScanning);
+ }
+ },
+
+ // The function that does the actual highlighting/colouring (with
+ // help from the parser and the DOM normalizer). Its interface is
+ // rather overcomplicated, because it is used in different
+ // situations: ensuring that a certain line is highlighted, or
+ // highlighting up to X milliseconds starting from a certain
+ // point. The 'from' argument gives the node at which it should
+ // start. If this is null, it will start at the beginning of the
+ // document. When a timestamp is given with the 'target' argument,
+ // it will stop highlighting at that time. If this argument holds
+ // a DOM node, it will highlight until it reaches that node. If at
+ // any time it comes across two 'clean' lines (no dirty nodes), it
+ // will stop, except when 'cleanLines' is true. maxBacktrack is
+ // the maximum number of lines to backtrack to find an existing
+ // parser instance. This is used to give up in situations where a
+ // highlight would take too long and freeze the browser interface.
+ highlight: function(from, target, cleanLines, maxBacktrack){
+ var container = this.container, self = this, active = this.options.activeTokens;
+ var endTime = (typeof target == "number" ? target : null);
+
+ if (!container.firstChild)
+ return;
+ // Backtrack to the first node before from that has a partial
+ // parse stored.
+ while (from && (!from.parserFromHere || from.dirty)) {
+ if (maxBacktrack != null && from.nodeName == "BR" && (--maxBacktrack) < 0)
+ return false;
+ from = from.previousSibling;
+ }
+ // If we are at the end of the document, do nothing.
+ if (from && !from.nextSibling)
+ return;
+
+ // Check whether a part ( node) and the corresponding token
+ // match.
+ function correctPart(token, part){
+ return !part.reduced && part.currentText == token.value && part.className == token.style;
+ }
+ // Shorten the text associated with a part by chopping off
+ // characters from the front. Note that only the currentText
+ // property gets changed. For efficiency reasons, we leave the
+ // nodeValue alone -- we set the reduced flag to indicate that
+ // this part must be replaced.
+ function shortenPart(part, minus){
+ part.currentText = part.currentText.substring(minus);
+ part.reduced = true;
+ }
+ // Create a part corresponding to a given token.
+ function tokenPart(token){
+ var part = makePartSpan(token.value, self.doc);
+ part.className = token.style;
+ return part;
+ }
+
+ function maybeTouch(node) {
+ if (node) {
+ if (lineDirty || node.nextSibling != node.oldNextSibling)
+ self.history.touch(node);
+ node.oldNextSibling = node.nextSibling;
+ }
+ else {
+ if (lineDirty || self.container.firstChild != self.container.oldFirstChild)
+ self.history.touch(node);
+ self.container.oldFirstChild = self.container.firstChild;
+ }
+ }
+
+ // Get the token stream. If from is null, we start with a new
+ // parser from the start of the frame, otherwise a partial parse
+ // is resumed.
+ var traversal = traverseDOM(from ? from.nextSibling : container.firstChild),
+ stream = stringStream(traversal),
+ parsed = from ? from.parserFromHere(stream) : Editor.Parser.make(stream);
+
+ // parts is an interface to make it possible to 'delay' fetching
+ // the next DOM node until we are completely done with the one
+ // before it. This is necessary because often the next node is
+ // not yet available when we want to proceed past the current
+ // one.
+ var parts = {
+ current: null,
+ // Fetch current node.
+ get: function(){
+ if (!this.current)
+ this.current = traversal.nodes.shift();
+ return this.current;
+ },
+ // Advance to the next part (do not fetch it yet).
+ next: function(){
+ this.current = null;
+ },
+ // Remove the current part from the DOM tree, and move to the
+ // next.
+ remove: function(){
+ container.removeChild(this.get());
+ this.current = null;
+ },
+ // Advance to the next part that is not empty, discarding empty
+ // parts.
+ getNonEmpty: function(){
+ var part = this.get();
+ // Allow empty nodes when they are alone on a line, needed
+ // for the FF cursor bug workaround (see select.js,
+ // insertNewlineAtCursor).
+ while (part && part.nodeName == "SPAN" && part.currentText == "") {
+ var old = part;
+ this.remove();
+ part = this.get();
+ // Adjust selection information, if any. See select.js for details.
+ select.snapshotMove(old.firstChild, part && (part.firstChild || part), 0);
+ }
+ return part;
+ }
+ };
+
+ var lineDirty = false, prevLineDirty = true, lineNodes = 0;
+
+ // This forEach loops over the tokens from the parsed stream, and
+ // at the same time uses the parts object to proceed through the
+ // corresponding DOM nodes.
+ forEach(parsed, function(token){
+ var part = parts.getNonEmpty();
+
+ if (token.value == "\n"){
+ // The idea of the two streams actually staying synchronized
+ // is such a long shot that we explicitly check.
+ if (part.nodeName != "BR")
+ throw "Parser out of sync. Expected BR.";
+
+ if (part.dirty || !part.indentation) lineDirty = true;
+ maybeTouch(from);
+ from = part;
+
+ // Every gets a copy of the parser state and a lexical
+ // context assigned to it. The first is used to be able to
+ // later resume parsing from this point, the second is used
+ // for indentation.
+ part.parserFromHere = parsed.copy();
+ part.indentation = token.indentation;
+ part.dirty = false;
+
+ // If the target argument wasn't an integer, go at least
+ // until that node.
+ if (endTime == null && part == target) throw StopIteration;
+
+ // A clean line with more than one node means we are done.
+ // Throwing a StopIteration is the way to break out of a
+ // MochiKit forEach loop.
+ if ((endTime != null && time() >= endTime) || (!lineDirty && !prevLineDirty && lineNodes > 1 && !cleanLines))
+ throw StopIteration;
+ prevLineDirty = lineDirty; lineDirty = false; lineNodes = 0;
+ parts.next();
+ }
+ else {
+ if (part.nodeName != "SPAN")
+ throw "Parser out of sync. Expected SPAN.";
+ if (part.dirty)
+ lineDirty = true;
+ lineNodes++;
+
+ // If the part matches the token, we can leave it alone.
+ if (correctPart(token, part)){
+ part.dirty = false;
+ parts.next();
+ }
+ // Otherwise, we have to fix it.
+ else {
+ lineDirty = true;
+ // Insert the correct part.
+ var newPart = tokenPart(token);
+ container.insertBefore(newPart, part);
+ if (active) active(newPart, token, self);
+ var tokensize = token.value.length;
+ var offset = 0;
+ // Eat up parts until the text for this token has been
+ // removed, adjusting the stored selection info (see
+ // select.js) in the process.
+ while (tokensize > 0) {
+ part = parts.get();
+ var partsize = part.currentText.length;
+ select.snapshotReplaceNode(part.firstChild, newPart.firstChild, tokensize, offset);
+ if (partsize > tokensize){
+ shortenPart(part, tokensize);
+ tokensize = 0;
+ }
+ else {
+ tokensize -= partsize;
+ offset += partsize;
+ parts.remove();
+ }
+ }
+ }
+ }
+ });
+ maybeTouch(from);
+ webkitLastLineHack(this.container);
+
+ // The function returns some status information that is used by
+ // hightlightDirty to determine whether and where it has to
+ // continue.
+ return {node: parts.getNonEmpty(),
+ dirty: lineDirty};
+ }
+ };
+
+ return Editor;
+})();
+
+addEventHandler(window, "load", function() {
+ var CodeMirror = window.frameElement.CodeMirror;
+ CodeMirror.editor = new Editor(CodeMirror.options);
+ this.parent.setTimeout(method(CodeMirror, "init"), 0);
+});
diff --git a/media/CodeMirror-0.62/js/highlight.js b/media/CodeMirror-0.62/js/highlight.js
new file mode 100644
index 0000000..f0de59c
--- /dev/null
+++ b/media/CodeMirror-0.62/js/highlight.js
@@ -0,0 +1,68 @@
+// Minimal framing needed to use CodeMirror-style parsers to highlight
+// code. Load this along with tokenize.js, stringstream.js, and your
+// parser. Then call highlightText, passing a string as the first
+// argument, and as the second argument either a callback function
+// that will be called with an array of SPAN nodes for every line in
+// the code, or a DOM node to which to append these spans, and
+// optionally (not needed if you only loaded one parser) a parser
+// object.
+
+// Stuff from util.js that the parsers are using.
+var StopIteration = {toString: function() {return "StopIteration"}};
+
+var Editor = {};
+var indentUnit = 2;
+
+(function(){
+ function normaliseString(string) {
+ var tab = "";
+ for (var i = 0; i < indentUnit; i++) tab += " ";
+
+ string = string.replace(/\t/g, tab).replace(/\u00a0/g, " ").replace(/\r\n?/g, "\n");
+ var pos = 0, parts = [], lines = string.split("\n");
+ for (var line = 0; line < lines.length; line++) {
+ if (line != 0) parts.push("\n");
+ parts.push(lines[line]);
+ }
+
+ return {
+ next: function() {
+ if (pos < parts.length) return parts[pos++];
+ else throw StopIteration;
+ }
+ };
+ }
+
+ window.highlightText = function(string, callback, parser) {
+ var parser = (parser || Editor.Parser).make(stringStream(normaliseString(string)));
+ var line = [];
+ if (callback.nodeType == 1) {
+ var node = callback;
+ callback = function(line) {
+ for (var i = 0; i < line.length; i++)
+ node.appendChild(line[i]);
+ node.appendChild(document.createElement("BR"));
+ };
+ }
+
+ try {
+ while (true) {
+ var token = parser.next();
+ if (token.value == "\n") {
+ callback(line);
+ line = [];
+ }
+ else {
+ var span = document.createElement("SPAN");
+ span.className = token.style;
+ span.appendChild(document.createTextNode(token.value));
+ line.push(span);
+ }
+ }
+ }
+ catch (e) {
+ if (e != StopIteration) throw e;
+ }
+ if (line.length) callback(line);
+ }
+})();
diff --git a/media/CodeMirror-0.62/js/mirrorframe.js b/media/CodeMirror-0.62/js/mirrorframe.js
new file mode 100644
index 0000000..7f6ad1a
--- /dev/null
+++ b/media/CodeMirror-0.62/js/mirrorframe.js
@@ -0,0 +1,81 @@
+/* Demonstration of embedding CodeMirror in a bigger application. The
+ * interface defined here is a mess of prompts and confirms, and
+ * should probably not be used in a real project.
+ */
+
+function MirrorFrame(place, options) {
+ this.home = document.createElement("DIV");
+ if (place.appendChild)
+ place.appendChild(this.home);
+ else
+ place(this.home);
+
+ var self = this;
+ function makeButton(name, action) {
+ var button = document.createElement("INPUT");
+ button.type = "button";
+ button.value = name;
+ self.home.appendChild(button);
+ button.onclick = function(){self[action].call(self);};
+ }
+
+ makeButton("Search", "search");
+ makeButton("Replace", "replace");
+ makeButton("Current line", "line");
+ makeButton("Jump to line", "jump");
+ makeButton("Insert constructor", "macro");
+ makeButton("Indent all", "reindent");
+
+ this.mirror = new CodeMirror(this.home, options);
+}
+
+MirrorFrame.prototype = {
+ search: function() {
+ var text = prompt("Enter search term:", "");
+ if (!text) return;
+
+ var first = true;
+ do {
+ var cursor = this.mirror.getSearchCursor(text, first);
+ first = false;
+ while (cursor.findNext()) {
+ cursor.select();
+ if (!confirm("Search again?"))
+ return;
+ }
+ } while (confirm("End of document reached. Start over?"));
+ },
+
+ replace: function() {
+ // This is a replace-all, but it is possible to implement a
+ // prompting replace.
+ var from = prompt("Enter search string:", ""), to;
+ if (from) to = prompt("What should it be replaced with?", "");
+ if (to == null) return;
+
+ var cursor = this.mirror.getSearchCursor(from, false);
+ while (cursor.findNext())
+ cursor.replace(to);
+ },
+
+ jump: function() {
+ var line = prompt("Jump to line:", "");
+ if (line && !isNaN(Number(line)))
+ this.mirror.jumpToLine(Number(line));
+ },
+
+ line: function() {
+ alert("The cursor is currently at line " + this.mirror.currentLine());
+ this.mirror.focus();
+ },
+
+ macro: function() {
+ var name = prompt("Name your constructor:", "");
+ if (name)
+ this.mirror.replaceSelection("function " + name + "() {\n \n}\n\n" + name + ".prototype = {\n \n};\n");
+ },
+
+ reindent: function() {
+ this.mirror.reindent();
+ }
+};
diff --git a/media/CodeMirror-0.62/js/parsecss.js b/media/CodeMirror-0.62/js/parsecss.js
new file mode 100644
index 0000000..4f90d59
--- /dev/null
+++ b/media/CodeMirror-0.62/js/parsecss.js
@@ -0,0 +1,155 @@
+/* Simple parser for CSS */
+
+var CSSParser = Editor.Parser = (function() {
+ var tokenizeCSS = (function() {
+ function normal(source, setState) {
+ var ch = source.next();
+ if (ch == "@") {
+ source.nextWhileMatches(/\w/);
+ return "css-at";
+ }
+ else if (ch == "/" && source.equals("*")) {
+ setState(inCComment);
+ return null;
+ }
+ else if (ch == "<" && source.equals("!")) {
+ setState(inSGMLComment);
+ return null;
+ }
+ else if (ch == "=") {
+ return "css-compare";
+ }
+ else if (source.equals("=") && (ch == "~" || ch == "|")) {
+ source.next();
+ return "css-compare";
+ }
+ else if (ch == "\"" || ch == "'") {
+ setState(inString(ch));
+ return null;
+ }
+ else if (ch == "#") {
+ source.nextWhileMatches(/\w/);
+ return "css-hash";
+ }
+ else if (ch == "!") {
+ source.nextWhileMatches(/[ \t]/);
+ source.nextWhileMatches(/\w/);
+ return "css-important";
+ }
+ else if (/\d/.test(ch)) {
+ source.nextWhileMatches(/[\w.%]/);
+ return "css-unit";
+ }
+ else if (/[,.+>*\/]/.test(ch)) {
+ return "css-select-op";
+ }
+ else if (/[;{}:\[\]]/.test(ch)) {
+ return "css-punctuation";
+ }
+ else {
+ source.nextWhileMatches(/[\w\\\-_]/);
+ return "css-identifier";
+ }
+ }
+
+ function inCComment(source, setState) {
+ var maybeEnd = false;
+ while (!source.endOfLine()) {
+ var ch = source.next();
+ if (maybeEnd && ch == "/") {
+ setState(normal);
+ break;
+ }
+ maybeEnd = (ch == "*");
+ }
+ return "css-comment";
+ }
+
+ function inSGMLComment(source, setState) {
+ var dashes = 0;
+ while (!source.endOfLine()) {
+ var ch = source.next();
+ if (dashes >= 2 && ch == ">") {
+ setState(normal);
+ break;
+ }
+ dashes = (ch == "-") ? dashes + 1 : 0;
+ }
+ return "css-comment";
+ }
+
+ function inString(quote) {
+ return function(source, setState) {
+ var escaped = false;
+ while (!source.endOfLine()) {
+ var ch = source.next();
+ if (ch == quote && !escaped)
+ break;
+ escaped = !escaped && ch == "\\";
+ }
+ if (!escaped)
+ setState(normal);
+ return "css-string";
+ };
+ }
+
+ return function(source, startState) {
+ return tokenizer(source, startState || normal);
+ };
+ })();
+
+ function indentCSS(inBraces, inRule, base) {
+ return function(nextChars) {
+ if (!inBraces || /^\}/.test(nextChars)) return base;
+ else if (inRule) return base + indentUnit * 2;
+ else return base + indentUnit;
+ };
+ }
+
+ // This is a very simplistic parser -- since CSS does not really
+ // nest, it works acceptably well, but some nicer colouroing could
+ // be provided with a more complicated parser.
+ function parseCSS(source, basecolumn) {
+ basecolumn = basecolumn || 0;
+ var tokens = tokenizeCSS(source);
+ var inBraces = false, inRule = false;
+
+ var iter = {
+ next: function() {
+ var token = tokens.next(), style = token.style, content = token.content;
+
+ if (style == "css-identifier" && inRule)
+ token.style = "css-value";
+ if (style == "css-hash")
+ token.style = inRule ? "css-colorcode" : "css-identifier";
+
+ if (content == "\n")
+ token.indentation = indentCSS(inBraces, inRule, basecolumn);
+
+ if (content == "{")
+ inBraces = true;
+ else if (content == "}")
+ inBraces = inRule = false;
+ else if (inBraces && content == ";")
+ inRule = false;
+ else if (inBraces && style != "css-comment" && style != "whitespace")
+ inRule = true;
+
+ return token;
+ },
+
+ copy: function() {
+ var _inBraces = inBraces, _inRule = inRule, _tokenState = tokens.state;
+ return function(source) {
+ tokens = tokenizeCSS(source, _tokenState);
+ inBraces = _inBraces;
+ inRule = _inRule;
+ return iter;
+ };
+ }
+ };
+ return iter;
+ }
+
+ return {make: parseCSS, electricChars: "}"};
+})();
diff --git a/media/CodeMirror-0.62/js/parsedummy.js b/media/CodeMirror-0.62/js/parsedummy.js
new file mode 100644
index 0000000..9e63caa
--- /dev/null
+++ b/media/CodeMirror-0.62/js/parsedummy.js
@@ -0,0 +1,32 @@
+var DummyParser = Editor.Parser = (function() {
+ function tokenizeDummy(source) {
+ while (!source.endOfLine()) source.next();
+ return "text";
+ }
+ function parseDummy(source) {
+ function indentTo(n) {return function() {return n;}}
+ source = tokenizer(source, tokenizeDummy);
+ var space = 0;
+
+ var iter = {
+ next: function() {
+ var tok = source.next();
+ if (tok.type == "whitespace") {
+ if (tok.value == "\n") tok.indentation = indentTo(space);
+ else space = tok.value.length;
+ }
+ return tok;
+ },
+ copy: function() {
+ var _space = space;
+ return function(_source) {
+ space = _space;
+ source = tokenizer(_source, tokenizeDummy);
+ return iter;
+ };
+ }
+ };
+ return iter;
+ }
+ return {make: parseDummy};
+})();
diff --git a/media/CodeMirror-0.62/js/parsehtmlmixed.js b/media/CodeMirror-0.62/js/parsehtmlmixed.js
new file mode 100644
index 0000000..ed1a608
--- /dev/null
+++ b/media/CodeMirror-0.62/js/parsehtmlmixed.js
@@ -0,0 +1,74 @@
+var HTMLMixedParser = Editor.Parser = (function() {
+ if (!(CSSParser && JSParser && XMLParser))
+ throw new Error("CSS, JS, and XML parsers must be loaded for HTML mixed mode to work.");
+ XMLParser.configure({useHTMLKludges: true});
+
+ function parseMixed(stream) {
+ var htmlParser = XMLParser.make(stream), localParser = null, inTag = false;
+ var iter = {next: top, copy: copy};
+
+ function top() {
+ var token = htmlParser.next();
+ if (token.content == "<")
+ inTag = true;
+ else if (token.style == "xml-tagname" && inTag === true)
+ inTag = token.content.toLowerCase();
+ else if (token.content == ">") {
+ if (inTag == "script")
+ iter.next = local(JSParser, "= 0; i--)
+ cc.push(fs[i]);
+ }
+ // cont and pass are used by the action functions to add other
+ // actions to the stack. cont will cause the current token to be
+ // consumed, pass will leave it for the next action.
+ function cont(){
+ push(arguments);
+ consume = true;
+ }
+ function pass(){
+ push(arguments);
+ consume = false;
+ }
+ // Used to change the style of the current token.
+ function mark(style){
+ marked = style;
+ }
+
+ // Push a new scope. Will automatically link the current scope.
+ function pushcontext(){
+ context = {prev: context, vars: {"this": true, "arguments": true}};
+ }
+ // Pop off the current scope.
+ function popcontext(){
+ context = context.prev;
+ }
+ // Register a variable in the current scope.
+ function register(varname){
+ if (context){
+ mark("js-variabledef");
+ context.vars[varname] = true;
+ }
+ }
+ // Check whether a variable is defined in the current scope.
+ function inScope(varname){
+ var cursor = context;
+ while (cursor) {
+ if (cursor.vars[varname])
+ return true;
+ cursor = cursor.prev;
+ }
+ return false;
+ }
+
+ // Push a new lexical context of the given type.
+ function pushlex(type, info) {
+ var result = function(){
+ lexical = new JSLexical(indented, column, type, null, lexical, info)
+ };
+ result.lex = true;
+ return result;
+ }
+ // Pop off the current lexical context.
+ function poplex(){
+ lexical = lexical.prev;
+ }
+ poplex.lex = true;
+ // The 'lex' flag on these actions is used by the 'next' function
+ // to know they can (and have to) be ran before moving on to the
+ // next token.
+
+ // Creates an action that discards tokens until it finds one of
+ // the given type.
+ function expect(wanted){
+ return function expecting(type){
+ if (type == wanted) cont();
+ else cont(arguments.callee);
+ };
+ }
+
+ // Looks for a statement, and then calls itself.
+ function statements(type){
+ return pass(statement, statements);
+ }
+ // Dispatches various types of statements based on the type of the
+ // current token.
+ function statement(type){
+ if (type == "var") cont(pushlex("vardef"), vardef1, expect(";"), poplex);
+ else if (type == "keyword a") cont(pushlex("form"), expression, statement, poplex);
+ else if (type == "keyword b") cont(pushlex("form"), statement, poplex);
+ else if (type == "{") cont(pushlex("}"), block, poplex);
+ else if (type == "function") cont(functiondef);
+ else if (type == "for") cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), poplex, statement, poplex);
+ else if (type == "variable") cont(pushlex("stat"), maybelabel);
+ else if (type == "switch") cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), block, poplex, poplex);
+ else if (type == "case") cont(expression, expect(":"));
+ else if (type == "default") cont(expect(":"));
+ else if (type == "catch") cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext);
+ else pass(pushlex("stat"), expression, expect(";"), poplex);
+ }
+ // Dispatch expression types.
+ function expression(type){
+ if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator);
+ else if (type == "function") cont(functiondef);
+ else if (type == "keyword c") cont(expression);
+ else if (type == "(") cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator);
+ else if (type == "operator") cont(expression);
+ else if (type == "[") cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
+ else if (type == "{") cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
+ }
+ // Called for places where operators, function calls, or
+ // subscripts are valid. Will skip on to the next action if none
+ // is found.
+ function maybeoperator(type){
+ if (type == "operator") cont(expression);
+ else if (type == "(") cont(pushlex(")"), expression, commasep(expression, ")"), poplex, maybeoperator);
+ else if (type == ".") cont(property, maybeoperator);
+ else if (type == "[") cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
+ }
+ // When a statement starts with a variable name, it might be a
+ // label. If no colon follows, it's a regular statement.
+ function maybelabel(type){
+ if (type == ":") cont(poplex, statement);
+ else pass(maybeoperator, expect(";"), poplex);
+ }
+ // Property names need to have their style adjusted -- the
+ // tokenizer thinks they are variables.
+ function property(type){
+ if (type == "variable") {mark("js-property"); cont();}
+ }
+ // This parses a property and its value in an object literal.
+ function objprop(type){
+ if (type == "variable") mark("js-property");
+ if (atomicTypes.hasOwnProperty(type)) cont(expect(":"), expression);
+ }
+ // Parses a comma-separated list of the things that are recognized
+ // by the 'what' argument.
+ function commasep(what, end){
+ function proceed(type) {
+ if (type == ",") cont(what, proceed);
+ else if (type == end) cont();
+ else cont(expect(end));
+ };
+ return function commaSeparated(type) {
+ if (type == end) cont();
+ else pass(what, proceed);
+ };
+ }
+ // Look for statements until a closing brace is found.
+ function block(type){
+ if (type == "}") cont();
+ else pass(statement, block);
+ }
+ // Variable definitions are split into two actions -- 1 looks for
+ // a name or the end of the definition, 2 looks for an '=' sign or
+ // a comma.
+ function vardef1(type, value){
+ if (type == "variable"){register(value); cont(vardef2);}
+ else cont();
+ }
+ function vardef2(type, value){
+ if (value == "=") cont(expression, vardef2);
+ else if (type == ",") cont(vardef1);
+ }
+ // For loops.
+ function forspec1(type){
+ if (type == "var") cont(vardef1, forspec2);
+ else if (type == ";") pass(forspec2);
+ else if (type == "variable") cont(formaybein);
+ else pass(forspec2);
+ }
+ function formaybein(type, value){
+ if (value == "in") cont(expression);
+ else cont(maybeoperator, forspec2);
+ }
+ function forspec2(type, value){
+ if (type == ";") cont(forspec3);
+ else if (value == "in") cont(expression);
+ else cont(expression, expect(";"), forspec3);
+ }
+ function forspec3(type) {
+ if (type == ")") pass();
+ else cont(expression);
+ }
+ // A function definition creates a new context, and the variables
+ // in its argument list have to be added to this context.
+ function functiondef(type, value){
+ if (type == "variable"){register(value); cont(functiondef);}
+ else if (type == "(") cont(pushcontext, commasep(funarg, ")"), statement, popcontext);
+ }
+ function funarg(type, value){
+ if (type == "variable"){register(value); cont();}
+ }
+
+ return parser;
+ }
+
+ return {make: parseJS, electricChars: "{}:"};
+})();
diff --git a/media/CodeMirror-0.62/js/parsesparql.js b/media/CodeMirror-0.62/js/parsesparql.js
new file mode 100644
index 0000000..4b1dcaf
--- /dev/null
+++ b/media/CodeMirror-0.62/js/parsesparql.js
@@ -0,0 +1,162 @@
+var SparqlParser = Editor.Parser = (function() {
+ function wordRegexp(words) {
+ return new RegExp("^(?:" + words.join("|") + ")$", "i");
+ }
+ var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri",
+ "isblank", "isliteral", "union", "a"]);
+ var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe",
+ "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional",
+ "graph", "by", "asc", "desc", ]);
+ var operatorChars = /[*+\-<>=&|]/;
+
+ var tokenizeSparql = (function() {
+ function normal(source, setState) {
+ var ch = source.next();
+ if (ch == "$" || ch == "?") {
+ source.nextWhileMatches(/[\w\d]/);
+ return "sp-var";
+ }
+ else if (ch == "<" && !source.matches(/[\s\u00a0=]/)) {
+ source.nextWhileMatches(/[^\s\u00a0>]/);
+ if (source.equals(">")) source.next();
+ return "sp-uri";
+ }
+ else if (ch == "\"" || ch == "'") {
+ setState(inLiteral(ch));
+ return null;
+ }
+ else if (/[{}\(\),\.;\[\]]/.test(ch)) {
+ return "sp-punc";
+ }
+ else if (ch == "#") {
+ while (!source.endOfLine()) source.next();
+ return "sp-comment";
+ }
+ else if (operatorChars.test(ch)) {
+ source.nextWhileMatches(operatorChars);
+ return "sp-operator";
+ }
+ else if (ch == ":") {
+ source.nextWhileMatches(/[\w\d\._\-]/);
+ return "sp-prefixed";
+ }
+ else {
+ source.nextWhileMatches(/[_\w\d]/);
+ if (source.equals(":")) {
+ source.next();
+ source.nextWhileMatches(/[\w\d_\-]/);
+ return "sp-prefixed";
+ }
+ var word = source.get(), type;
+ if (ops.test(word))
+ type = "sp-operator";
+ else if (keywords.test(word))
+ type = "sp-keyword";
+ else
+ type = "sp-word";
+ return {style: type, content: word};
+ }
+ }
+
+ function inLiteral(quote) {
+ return function(source, setState) {
+ var escaped = false;
+ while (!source.endOfLine()) {
+ var ch = source.next();
+ if (ch == quote && !escaped) {
+ setState(normal);
+ break;
+ }
+ escaped = !escaped && ch == "\\";
+ }
+ return "sp-literal";
+ };
+ }
+
+ return function(source, startState) {
+ return tokenizer(source, startState || normal);
+ };
+ })();
+
+ function indentSparql(context) {
+ return function(nextChars) {
+ var firstChar = nextChars && nextChars.charAt(0);
+ if (/[\]\}]/.test(firstChar))
+ while (context && context.type == "pattern") context = context.prev;
+
+ var closing = context && firstChar == matching[context.type];
+ if (!context)
+ return 0;
+ else if (context.type == "pattern")
+ return context.col;
+ else if (context.align)
+ return context.col - (closing ? context.width : 0);
+ else
+ return context.indent + (closing ? 0 : indentUnit);
+ }
+ }
+
+ function parseSparql(source) {
+ var tokens = tokenizeSparql(source);
+ var context = null, indent = 0, col = 0;
+ function pushContext(type, width) {
+ context = {prev: context, indent: indent, col: col, type: type, width: width};
+ }
+ function popContext() {
+ context = context.prev;
+ }
+
+ var iter = {
+ next: function() {
+ var token = tokens.next(), type = token.style, content = token.content, width = token.value.length;
+
+ if (content == "\n") {
+ token.indentation = indentSparql(context);
+ indent = col = 0;
+ if (context && context.align == null) context.align = false;
+ }
+ else if (type == "whitespace" && col == 0) {
+ indent = width;
+ }
+ else if (type != "sp-comment" && context && context.align == null) {
+ context.align = true;
+ }
+
+ if (content != "\n") col += width;
+
+ if (/[\[\{\(]/.test(content)) {
+ pushContext(content, width);
+ }
+ else if (/[\]\}\)]/.test(content)) {
+ while (context && context.type == "pattern")
+ popContext();
+ if (context && content == matching[context.type])
+ popContext();
+ }
+ else if (content == "." && context && context.type == "pattern") {
+ popContext();
+ }
+ else if ((type == "sp-word" || type == "sp-prefixed" || type == "sp-uri" || type == "sp-var" || type == "sp-literal") &&
+ context && /[\{\[]/.test(context.type)) {
+ pushContext("pattern", width);
+ }
+
+ return token;
+ },
+
+ copy: function() {
+ var _context = context, _indent = indent, _col = col, _tokenState = tokens.state;
+ return function(source) {
+ tokens = tokenizeSparql(source, _tokenState);
+ context = _context;
+ indent = _indent;
+ col = _col;
+ return iter;
+ };
+ }
+ };
+ return iter;
+ }
+
+ return {make: parseSparql, electricChars: "}]"};
+})();
diff --git a/media/CodeMirror-0.62/js/parsesurvex.js b/media/CodeMirror-0.62/js/parsesurvex.js
new file mode 100644
index 0000000..941b347
--- /dev/null
+++ b/media/CodeMirror-0.62/js/parsesurvex.js
@@ -0,0 +1,107 @@
+/* Simple parser for Survex files (based on the CSS example) */
+
+// The tokenizer breaks up the text into convincing chunks (I think the white-space is parser automatically)
+var SVXParser = Editor.Parser = (function() {
+ var tokenizeSVX = (function() {
+ function normal(source, setState) {
+ var ch = source.next();
+
+ if (ch == ";") {
+ source.nextWhile(matcher(/[^\n]/));
+ return "svx-comment";
+ }
+ else if (ch == "*") {
+ source.nextWhile(matcher(/\w/));
+ return "svx-command";
+ }
+ else if (ch == "\"" || ch == "'") {
+ var escaped = false;
+ while (!source.endOfLine()) {
+ var nch = source.next();
+ if (nch == ch && !escaped)
+ break;
+ escaped = !escaped && nch == "\\";
+ }
+ return "svx-string";
+ }
+ else if (/[\d\-+.]/.test(ch)) {
+ source.nextWhile(matcher(/[\d.]/));
+ return "svx-measure";
+ }
+ else {
+ source.nextWhile(matcher(/\S/));
+ return "svx-word";
+ }
+ }
+
+ return function(source, startState) {
+ return tokenizer(source, startState || normal);
+ };
+ })();
+
+ // survex doesn't have indentation; but you get double linefeeds if you leave this out.
+ function indentSVX() {
+ return function(nextChars) {
+ return 0;
+ };
+ }
+
+ // Then this simple parser fixes up the obvious errors made by the tokenizer (which could only operate on characters)
+ // A very fancy upgrade could make it capable of handling the *data commands which make it accept different orderings of
+ // the parameters -- though this may be a challenge because the whole file needs reparsing when that happens -- don't
+ // know how optimized the basic code is to be able to call for such to happen when a formatting command like this changes.
+ function parseSVX(source, basecolumn) {
+ basecolumn = basecolumn || 0;
+ var tokens = tokenizeSVX(source);
+ var inCommand = false;
+ var ntokeninline = -1;
+
+ var iter = {
+ next: function() {
+ var token = tokens.next(), style = token.style, content = token.content;
+
+ if (content == "\n") {
+ ntokeninline = -1;
+ inCommand = false;
+ token.indentation = indentSVX();
+ }
+ else if (style != "whitespace")
+ ntokeninline += 1;
+
+ if (style == "svx-command") {
+ inCommand = (ntokeninline == 0);
+ if (!inCommand)
+ token.style = "svx-word";
+ else if (content == "*begin")
+ token.style = "svx-begin";
+ else if (content == "*end")
+ token.style = "svx-end";
+ }
+
+ if (!inCommand && style == "svx-measure") {
+ if (ntokeninline < 2)
+ token.style = "svx-word";
+ }
+ if (!inCommand && style == "svx-word" && (ntokeninline == 4)) {
+ if (content == "down" || content == "up")
+ token.style = "svx-measure";
+ }
+
+ return token;
+ },
+
+ copy: function() {
+ var _inCommand = inCommand, _tokenState = tokens.state, _ntokeninline = ntokeninline;
+ return function(source) {
+ tokens = tokenizeSVX(source, _tokenState);
+ inCommand = _inCommand;
+ ntokeninline = _ntokeninline;
+ return iter;
+ };
+ }
+ };
+ return iter;
+ }
+
+ return {make: parseSVX};
+})();
diff --git a/media/CodeMirror-0.62/js/parsexml.js b/media/CodeMirror-0.62/js/parsexml.js
new file mode 100644
index 0000000..95a8099
--- /dev/null
+++ b/media/CodeMirror-0.62/js/parsexml.js
@@ -0,0 +1,292 @@
+/* This file defines an XML parser, with a few kludges to make it
+ * useable for HTML. autoSelfClosers defines a set of tag names that
+ * are expected to not have a closing tag, and doNotIndent specifies
+ * the tags inside of which no indentation should happen (see Config
+ * object). These can be disabled by passing the editor an object like
+ * {useHTMLKludges: false} as parserConfig option.
+ */
+
+var XMLParser = Editor.Parser = (function() {
+ var Kludges = {
+ autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
+ "meta": true, "col": true, "frame": true, "base": true, "area": true},
+ doNotIndent: {"pre": true, "!cdata": true}
+ };
+ var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}};
+ var UseKludges = Kludges;
+ var alignCDATA = false;
+
+ // Simple stateful tokenizer for XML documents. Returns a
+ // MochiKit-style iterator, with a state property that contains a
+ // function encapsulating the current state. See tokenize.js.
+ var tokenizeXML = (function() {
+ function inText(source, setState) {
+ var ch = source.next();
+ if (ch == "<") {
+ if (source.equals("!")) {
+ source.next();
+ if (source.equals("[")) {
+ if (source.lookAhead("[CDATA[", true)) {
+ setState(inBlock("xml-cdata", "]]>"));
+ return null;
+ }
+ else {
+ return "xml-text";
+ }
+ }
+ else if (source.lookAhead("--", true)) {
+ setState(inBlock("xml-comment", "-->"));
+ return null;
+ }
+ else {
+ return "xml-text";
+ }
+ }
+ else if (source.equals("?")) {
+ source.next();
+ source.nextWhileMatches(/[\w\._\-]/);
+ setState(inBlock("xml-processing", "?>"));
+ return "xml-processing";
+ }
+ else {
+ if (source.equals("/")) source.next();
+ setState(inTag);
+ return "xml-punctuation";
+ }
+ }
+ else if (ch == "&") {
+ while (!source.endOfLine()) {
+ if (source.next() == ";")
+ break;
+ }
+ return "xml-entity";
+ }
+ else {
+ source.nextWhileMatches(/[^&<\n]/);
+ return "xml-text";
+ }
+ }
+
+ function inTag(source, setState) {
+ var ch = source.next();
+ if (ch == ">") {
+ setState(inText);
+ return "xml-punctuation";
+ }
+ else if (/[?\/]/.test(ch) && source.equals(">")) {
+ source.next();
+ setState(inText);
+ return "xml-punctuation";
+ }
+ else if (ch == "=") {
+ return "xml-punctuation";
+ }
+ else if (/[\'\"]/.test(ch)) {
+ setState(inAttribute(ch));
+ return null;
+ }
+ else {
+ source.nextWhileMatches(/[^\s\u00a0=<>\"\'\/?]/);
+ return "xml-name";
+ }
+ }
+
+ function inAttribute(quote) {
+ return function(source, setState) {
+ while (!source.endOfLine()) {
+ if (source.next() == quote) {
+ setState(inTag);
+ break;
+ }
+ }
+ return "xml-attribute";
+ };
+ }
+
+ function inBlock(style, terminator) {
+ return function(source, setState) {
+ while (!source.endOfLine()) {
+ if (source.lookAhead(terminator, true)) {
+ setState(inText);
+ break;
+ }
+ source.next();
+ }
+ return style;
+ };
+ }
+
+ return function(source, startState) {
+ return tokenizer(source, startState || inText);
+ };
+ })();
+
+ // The parser. The structure of this function largely follows that of
+ // parseJavaScript in parsejavascript.js (there is actually a bit more
+ // shared code than I'd like), but it is quite a bit simpler.
+ function parseXML(source) {
+ var tokens = tokenizeXML(source);
+ var cc = [base];
+ var tokenNr = 0, indented = 0;
+ var currentTag = null, context = null;
+ var consume, marked;
+
+ function push(fs) {
+ for (var i = fs.length - 1; i >= 0; i--)
+ cc.push(fs[i]);
+ }
+ function cont() {
+ push(arguments);
+ consume = true;
+ }
+ function pass() {
+ push(arguments);
+ consume = false;
+ }
+
+ function mark(style) {
+ marked = style;
+ }
+ function expect(text) {
+ return function(style, content) {
+ if (content == text) cont();
+ else mark("xml-error") || cont(arguments.callee);
+ };
+ }
+
+ function pushContext(tagname, startOfLine) {
+ var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent);
+ context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent};
+ }
+ function popContext() {
+ context = context.prev;
+ }
+ function computeIndentation(baseContext) {
+ return function(nextChars, current) {
+ var context = baseContext;
+ if (context && context.noIndent)
+ return current;
+ if (alignCDATA && /"));
+ else if (style == "xml-cdata") {
+ if (!context || context.name != "!cdata") pushContext("!cdata");
+ if (/\]\]>$/.test(content)) popContext();
+ cont();
+ }
+ else if (harmlessTokens.hasOwnProperty(style)) cont();
+ else mark("xml-error") || cont();
+ }
+ function tagname(style, content) {
+ if (style == "xml-name") {
+ currentTag = content.toLowerCase();
+ mark("xml-tagname");
+ cont();
+ }
+ else {
+ currentTag = null;
+ pass();
+ }
+ }
+ function closetagname(style, content) {
+ if (style == "xml-name" && context && content.toLowerCase() == context.name) {
+ popContext();
+ mark("xml-tagname");
+ }
+ else {
+ mark("xml-error");
+ }
+ cont();
+ }
+ function endtag(startOfLine) {
+ return function(style, content) {
+ if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont();
+ else if (content == ">") pushContext(currentTag, startOfLine) || cont();
+ else mark("xml-error") || cont(arguments.callee);
+ };
+ }
+ function attributes(style) {
+ if (style == "xml-name") mark("xml-attname") || cont(attribute, attributes);
+ else pass();
+ }
+ function attribute(style, content) {
+ if (content == "=") cont(value);
+ else if (content == ">" || content == "/>") pass(endtag);
+ else pass();
+ }
+ function value(style) {
+ if (style == "xml-attribute") cont(value);
+ else pass();
+ }
+
+ return {
+ indentation: function() {return indented;},
+
+ next: function(){
+ var token = tokens.next();
+ if (token.style == "whitespace" && tokenNr == 0)
+ indented = token.value.length;
+ else
+ tokenNr++;
+ if (token.content == "\n") {
+ indented = tokenNr = 0;
+ token.indentation = computeIndentation(context);
+ }
+
+ if (token.style == "whitespace" || token.type == "xml-comment")
+ return token;
+
+ while(true){
+ consume = marked = false;
+ cc.pop()(token.style, token.content);
+ if (consume){
+ if (marked)
+ token.style = marked;
+ return token;
+ }
+ }
+ },
+
+ copy: function(){
+ var _cc = cc.concat([]), _tokenState = tokens.state, _context = context;
+ var parser = this;
+
+ return function(input){
+ cc = _cc.concat([]);
+ tokenNr = indented = 0;
+ context = _context;
+ tokens = tokenizeXML(input, _tokenState);
+ return parser;
+ };
+ }
+ };
+ }
+
+ return {
+ make: parseXML,
+ electricChars: "/",
+ configure: function(config) {
+ if (config.useHTMLKludges != null)
+ UseKludges = config.useHTMLKludges ? Kludges : NoKludges;
+ if (config.alignCDATA)
+ alignCDATA = config.alignCDATA;
+ }
+ };
+})();
diff --git a/media/CodeMirror-0.62/js/select.js b/media/CodeMirror-0.62/js/select.js
new file mode 100644
index 0000000..4a11c54
--- /dev/null
+++ b/media/CodeMirror-0.62/js/select.js
@@ -0,0 +1,583 @@
+/* Functionality for finding, storing, and restoring selections
+ *
+ * This does not provide a generic API, just the minimal functionality
+ * required by the CodeMirror system.
+ */
+
+// Namespace object.
+var select = {};
+
+(function() {
+ select.ie_selection = document.selection && document.selection.createRangeCollection;
+
+ // Find the 'top-level' (defined as 'a direct child of the node
+ // passed as the top argument') node that the given node is
+ // contained in. Return null if the given node is not inside the top
+ // node.
+ function topLevelNodeAt(node, top) {
+ while (node && node.parentNode != top)
+ node = node.parentNode;
+ return node;
+ }
+
+ // Find the top-level node that contains the node before this one.
+ function topLevelNodeBefore(node, top) {
+ while (!node.previousSibling && node.parentNode != top)
+ node = node.parentNode;
+ return topLevelNodeAt(node.previousSibling, top);
+ }
+
+ // Used to prevent restoring a selection when we do not need to.
+ var currentSelection = null;
+
+ var fourSpaces = "\u00a0\u00a0\u00a0\u00a0";
+
+ select.snapshotChanged = function() {
+ if (currentSelection) currentSelection.changed = true;
+ };
+
+ // This is called by the code in editor.js whenever it is replacing
+ // a text node. The function sees whether the given oldNode is part
+ // of the current selection, and updates this selection if it is.
+ // Because nodes are often only partially replaced, the length of
+ // the part that gets replaced has to be taken into account -- the
+ // selection might stay in the oldNode if the newNode is smaller
+ // than the selection's offset. The offset argument is needed in
+ // case the selection does move to the new object, and the given
+ // length is not the whole length of the new node (part of it might
+ // have been used to replace another node).
+ select.snapshotReplaceNode = function(from, to, length, offset) {
+ if (!currentSelection) return;
+ currentSelection.changed = true;
+
+ function replace(point) {
+ if (from == point.node) {
+ if (length && point.offset > length) {
+ point.offset -= length;
+ }
+ else {
+ point.node = to;
+ point.offset += (offset || 0);
+ }
+ }
+ }
+ replace(currentSelection.start);
+ replace(currentSelection.end);
+ };
+
+ select.snapshotMove = function(from, to, distance, relative, ifAtStart) {
+ if (!currentSelection) return;
+ currentSelection.changed = true;
+
+ function move(point) {
+ if (from == point.node && (!ifAtStart || point.offset == 0)) {
+ point.node = to;
+ if (relative) point.offset = Math.max(0, point.offset + distance);
+ else point.offset = distance;
+ }
+ }
+ move(currentSelection.start);
+ move(currentSelection.end);
+ };
+
+ // Most functions are defined in two ways, one for the IE selection
+ // model, one for the W3C one.
+ if (select.ie_selection) {
+ function selectionNode(win, start) {
+ var range = win.document.selection.createRange();
+ range.collapse(start);
+
+ function nodeAfter(node) {
+ var found = null;
+ while (!found && node) {
+ found = node.nextSibling;
+ node = node.parentNode;
+ }
+ return nodeAtStartOf(found);
+ }
+
+ function nodeAtStartOf(node) {
+ while (node && node.firstChild) node = node.firstChild;
+ return {node: node, offset: 0};
+ }
+
+ var containing = range.parentElement();
+ if (!isAncestor(win.document.body, containing)) return null;
+ if (!containing.firstChild) return nodeAtStartOf(containing);
+
+ var working = range.duplicate();
+ working.moveToElementText(containing);
+ working.collapse(true);
+ for (var cur = containing.firstChild; cur; cur = cur.nextSibling) {
+ if (cur.nodeType == 3) {
+ var size = cur.nodeValue.length;
+ working.move("character", size);
+ }
+ else {
+ working.moveToElementText(cur);
+ working.collapse(false);
+ }
+
+ var dir = range.compareEndPoints("StartToStart", working);
+ if (dir == 0) return nodeAfter(cur);
+ if (dir == 1) continue;
+ if (cur.nodeType != 3) return nodeAtStartOf(cur);
+
+ working.setEndPoint("StartToEnd", range);
+ return {node: cur, offset: size - working.text.length};
+ }
+ return nodeAfter(containing);
+ }
+
+ select.markSelection = function(win) {
+ currentSelection = null;
+ var sel = win.document.selection;
+ if (!sel) return;
+ var start = selectionNode(win, true),
+ end = selectionNode(win, false);
+ if (!start || !end) return;
+ currentSelection = {start: start, end: end, window: win, changed: false};
+ };
+
+ select.selectMarked = function() {
+ if (!currentSelection || !currentSelection.changed) return;
+
+ function makeRange(point) {
+ var range = currentSelection.window.document.body.createTextRange();
+ var node = point.node;
+ if (!node) {
+ range.moveToElementText(currentSelection.window.document.body);
+ range.collapse(false);
+ }
+ else if (node.nodeType == 3) {
+ range.moveToElementText(node.parentNode);
+ var offset = point.offset;
+ while (node.previousSibling) {
+ node = node.previousSibling;
+ offset += (node.innerText || "").length;
+ }
+ range.move("character", offset);
+ }
+ else {
+ range.moveToElementText(node);
+ range.collapse(true);
+ }
+ return range;
+ }
+
+ var start = makeRange(currentSelection.start), end = makeRange(currentSelection.end);
+ start.setEndPoint("StartToEnd", end);
+ start.select();
+ };
+
+ // Get the top-level node that one end of the cursor is inside or
+ // after. Note that this returns false for 'no cursor', and null
+ // for 'start of document'.
+ select.selectionTopNode = function(container, start) {
+ var selection = container.ownerDocument.selection;
+ if (!selection) return false;
+
+ var range = selection.createRange();
+ range.collapse(start);
+ var around = range.parentElement();
+ if (around && isAncestor(container, around)) {
+ // Only use this node if the selection is not at its start.
+ var range2 = range.duplicate();
+ range2.moveToElementText(around);
+ if (range.compareEndPoints("StartToStart", range2) == -1)
+ return topLevelNodeAt(around, container);
+ }
+ // Fall-back hack
+ try {range.pasteHTML("");}
+ catch (e) {return false;}
+
+ var temp = container.ownerDocument.getElementById("xxx-temp-xxx");
+ if (temp) {
+ var result = topLevelNodeBefore(temp, container);
+ removeElement(temp);
+ return result;
+ }
+ return false;
+ };
+
+ // Place the cursor after this.start. This is only useful when
+ // manually moving the cursor instead of restoring it to its old
+ // position.
+ select.focusAfterNode = function(node, container) {
+ var range = container.ownerDocument.body.createTextRange();
+ range.moveToElementText(node || container);
+ range.collapse(!node);
+ range.select();
+ };
+
+ select.somethingSelected = function(win) {
+ var sel = win.document.selection;
+ return sel && (sel.createRange().text != "");
+ };
+
+ function insertAtCursor(window, html) {
+ var selection = window.document.selection;
+ if (selection) {
+ var range = selection.createRange();
+ range.pasteHTML(html);
+ range.collapse(false);
+ range.select();
+ }
+ }
+
+ // Used to normalize the effect of the enter key, since browsers
+ // do widely different things when pressing enter in designMode.
+ select.insertNewlineAtCursor = function(window) {
+ insertAtCursor(window, " ");
+ };
+
+ select.insertTabAtCursor = function(window) {
+ insertAtCursor(window, fourSpaces);
+ };
+
+ // Get the BR node at the start of the line on which the cursor
+ // currently is, and the offset into the line. Returns null as
+ // node if cursor is on first line.
+ select.cursorPos = function(container, start) {
+ var selection = container.ownerDocument.selection;
+ if (!selection) return null;
+
+ var topNode = select.selectionTopNode(container, start);
+ while (topNode && topNode.nodeName != "BR")
+ topNode = topNode.previousSibling;
+
+ var range = selection.createRange(), range2 = range.duplicate();
+ range.collapse(start);
+ if (topNode) {
+ range2.moveToElementText(topNode);
+ range2.collapse(false);
+ }
+ else {
+ // When nothing is selected, we can get all kinds of funky errors here.
+ try { range2.moveToElementText(container); }
+ catch (e) { return null; }
+ range2.collapse(true);
+ }
+ range.setEndPoint("StartToStart", range2);
+
+ return {node: topNode, offset: range.text.length};
+ };
+
+ select.setCursorPos = function(container, from, to) {
+ function rangeAt(pos) {
+ var range = container.ownerDocument.body.createTextRange();
+ if (!pos.node) {
+ range.moveToElementText(container);
+ range.collapse(true);
+ }
+ else {
+ range.moveToElementText(pos.node);
+ range.collapse(false);
+ }
+ range.move("character", pos.offset);
+ return range;
+ }
+
+ var range = rangeAt(from);
+ if (to && to != from)
+ range.setEndPoint("EndToEnd", rangeAt(to));
+ range.select();
+ }
+
+ // Make sure the cursor is visible.
+ select.scrollToCursor = function(container) {
+ var selection = container.ownerDocument.selection;
+ if (!selection) return null;
+ selection.createRange().scrollIntoView();
+ };
+
+ select.scrollToNode = function(node) {
+ if (!node) return;
+ node.scrollIntoView();
+ };
+
+ // Some hacks for storing and re-storing the selection when the editor loses and regains focus.
+ select.selectionCoords = function (win) {
+ var selection = win.document.selection;
+ if (!selection) return null;
+ var start = selection.createRange(), end = start.duplicate();
+ start.collapse(true);
+ end.collapse(false);
+
+ var body = win.document.body;
+ return {start: {x: start.boundingLeft + body.scrollLeft - 1,
+ y: start.boundingTop + body.scrollTop},
+ end: {x: end.boundingLeft + body.scrollLeft - 1,
+ y: end.boundingTop + body.scrollTop}};
+ };
+
+ // Restore a stored selection.
+ select.selectCoords = function(win, coords) {
+ if (!coords) return;
+
+ var range1 = win.document.body.createTextRange(), range2 = range1.duplicate();
+ // This can fail for various hard-to-handle reasons.
+ try {
+ range1.moveToPoint(coords.start.x, coords.start.y);
+ range2.moveToPoint(coords.end.x, coords.end.y);
+ range1.setEndPoint("EndToStart", range2);
+ range1.select();
+ } catch(e) {alert(e.message);}
+ };
+ }
+ // W3C model
+ else {
+ // Store start and end nodes, and offsets within these, and refer
+ // back to the selection object from those nodes, so that this
+ // object can be updated when the nodes are replaced before the
+ // selection is restored.
+ select.markSelection = function (win) {
+ var selection = win.getSelection();
+ if (!selection || selection.rangeCount == 0)
+ return (currentSelection = null);
+ var range = selection.getRangeAt(0);
+
+ currentSelection = {
+ start: {node: range.startContainer, offset: range.startOffset},
+ end: {node: range.endContainer, offset: range.endOffset},
+ window: win,
+ changed: false
+ };
+
+ // We want the nodes right at the cursor, not one of their
+ // ancestors with a suitable offset. This goes down the DOM tree
+ // until a 'leaf' is reached (or is it *up* the DOM tree?).
+ function normalize(point){
+ while (point.node.nodeType != 3 && point.node.nodeName != "BR") {
+ var newNode = point.node.childNodes[point.offset] || point.node.nextSibling;
+ point.offset = 0;
+ while (!newNode && point.node.parentNode) {
+ point.node = point.node.parentNode;
+ newNode = point.node.nextSibling;
+ }
+ point.node = newNode;
+ if (!newNode)
+ break;
+ }
+ }
+
+ normalize(currentSelection.start);
+ normalize(currentSelection.end);
+ };
+
+ select.selectMarked = function () {
+ if (!currentSelection || !currentSelection.changed) return;
+ var win = currentSelection.window, range = win.document.createRange();
+
+ function setPoint(point, which) {
+ if (point.node) {
+ // Some magic to generalize the setting of the start and end
+ // of a range.
+ if (point.offset == 0)
+ range["set" + which + "Before"](point.node);
+ else
+ range["set" + which](point.node, point.offset);
+ }
+ else {
+ range.setStartAfter(win.document.body.lastChild || win.document.body);
+ }
+ }
+
+ setPoint(currentSelection.end, "End");
+ setPoint(currentSelection.start, "Start");
+ selectRange(range, win);
+ };
+
+ // Helper for selecting a range object.
+ function selectRange(range, window) {
+ var selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(range);
+ };
+ function selectionRange(window) {
+ var selection = window.getSelection();
+ if (!selection || selection.rangeCount == 0)
+ return false;
+ else
+ return selection.getRangeAt(0);
+ }
+
+ // Finding the top-level node at the cursor in the W3C is, as you
+ // can see, quite an involved process.
+ select.selectionTopNode = function(container, start) {
+ var range = selectionRange(container.ownerDocument.defaultView);
+ if (!range) return false;
+
+ var node = start ? range.startContainer : range.endContainer;
+ var offset = start ? range.startOffset : range.endOffset;
+ // Work around (yet another) bug in Opera's selection model.
+ if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 &&
+ container.childNodes[range.startOffset] && container.childNodes[range.startOffset].nodeName == "BR")
+ offset--;
+
+ // For text nodes, we look at the node itself if the cursor is
+ // inside, or at the node before it if the cursor is at the
+ // start.
+ if (node.nodeType == 3){
+ if (offset > 0)
+ return topLevelNodeAt(node, container);
+ else
+ return topLevelNodeBefore(node, container);
+ }
+ // Occasionally, browsers will return the HTML node as
+ // selection. If the offset is 0, we take the start of the frame
+ // ('after null'), otherwise, we take the last node.
+ else if (node.nodeName == "HTML") {
+ return (offset == 1 ? null : container.lastChild);
+ }
+ // If the given node is our 'container', we just look up the
+ // correct node by using the offset.
+ else if (node == container) {
+ return (offset == 0) ? null : node.childNodes[offset - 1];
+ }
+ // In any other case, we have a regular node. If the cursor is
+ // at the end of the node, we use the node itself, if it is at
+ // the start, we use the node before it, and in any other
+ // case, we look up the child before the cursor and use that.
+ else {
+ if (offset == node.childNodes.length)
+ return topLevelNodeAt(node, container);
+ else if (offset == 0)
+ return topLevelNodeBefore(node, container);
+ else
+ return topLevelNodeAt(node.childNodes[offset - 1], container);
+ }
+ };
+
+ select.focusAfterNode = function(node, container) {
+ var win = container.ownerDocument.defaultView,
+ range = win.document.createRange();
+ range.setStartBefore(container.firstChild || container);
+ // In Opera, setting the end of a range at the end of a line
+ // (before a BR) will cause the cursor to appear on the next
+ // line, so we set the end inside of the start node when
+ // possible.
+ if (node && !node.firstChild)
+ range.setEndAfter(node);
+ else if (node)
+ range.setEnd(node, node.childNodes.length);
+ else
+ range.setEndBefore(container.firstChild || container);
+ range.collapse(false);
+ selectRange(range, win);
+ };
+
+ select.somethingSelected = function(win) {
+ var range = selectionRange(win);
+ return range && !range.collapsed;
+ };
+
+ function insertNodeAtCursor(window, node) {
+ var range = selectionRange(window);
+ if (!range) return;
+
+ range.deleteContents();
+ range.insertNode(node);
+ webkitLastLineHack(window.document.body);
+ range = window.document.createRange();
+ range.selectNode(node);
+ range.collapse(false);
+ selectRange(range, window);
+ }
+
+ select.insertNewlineAtCursor = function(window) {
+ insertNodeAtCursor(window, window.document.createElement("BR"));
+ };
+
+ select.insertTabAtCursor = function(window) {
+ insertNodeAtCursor(window, window.document.createTextNode(fourSpaces));
+ };
+
+ select.cursorPos = function(container, start) {
+ var range = selectionRange(window);
+ if (!range) return;
+
+ var topNode = select.selectionTopNode(container, start);
+ while (topNode && topNode.nodeName != "BR")
+ topNode = topNode.previousSibling;
+
+ range = range.cloneRange();
+ range.collapse(start);
+ if (topNode)
+ range.setStartAfter(topNode);
+ else
+ range.setStartBefore(container);
+ return {node: topNode, offset: range.toString().length};
+ };
+
+ select.setCursorPos = function(container, from, to) {
+ var win = container.ownerDocument.defaultView,
+ range = win.document.createRange();
+
+ function setPoint(node, offset, side) {
+ if (!node)
+ node = container.firstChild;
+ else
+ node = node.nextSibling;
+
+ if (!node)
+ return;
+
+ if (offset == 0) {
+ range["set" + side + "Before"](node);
+ return true;
+ }
+
+ var backlog = []
+ function decompose(node) {
+ if (node.nodeType == 3)
+ backlog.push(node);
+ else
+ forEach(node.childNodes, decompose);
+ }
+ while (true) {
+ while (node && !backlog.length) {
+ decompose(node);
+ node = node.nextSibling;
+ }
+ var cur = backlog.shift();
+ if (!cur) return false;
+
+ var length = cur.nodeValue.length;
+ if (length >= offset) {
+ range["set" + side](cur, offset);
+ return true;
+ }
+ offset -= length;
+ }
+ }
+
+ to = to || from;
+ if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start"))
+ selectRange(range, win);
+ };
+
+ select.scrollToNode = function(element) {
+ if (!element) return;
+ var doc = element.ownerDocument, body = doc.body, win = doc.defaultView, html = doc.documentElement;
+
+ // In Opera, BR elements *always* have a scrollTop property of zero. Go Opera.
+ while (element && !element.offsetTop)
+ element = element.previousSibling;
+
+ var y = 0, pos = element;
+ while (pos && pos.offsetParent) {
+ y += pos.offsetTop;
+ pos = pos.offsetParent;
+ }
+
+ var screen_y = y - (body.scrollTop || html.scrollTop || 0);
+ if (screen_y < 0 || screen_y > win.innerHeight - 30)
+ win.scrollTo(body.scrollLeft || html.scrollLeft || 0, y);
+ };
+
+ select.scrollToCursor = function(container) {
+ select.scrollToNode(select.selectionTopNode(container, true) || container.firstChild);
+ };
+ }
+})();
diff --git a/media/CodeMirror-0.62/js/stringstream.js b/media/CodeMirror-0.62/js/stringstream.js
new file mode 100644
index 0000000..6d9355f
--- /dev/null
+++ b/media/CodeMirror-0.62/js/stringstream.js
@@ -0,0 +1,140 @@
+/* String streams are the things fed to parsers (which can feed them
+ * to a tokenizer if they want). They provide peek and next methods
+ * for looking at the current character (next 'consumes' this
+ * character, peek does not), and a get method for retrieving all the
+ * text that was consumed since the last time get was called.
+ *
+ * An easy mistake to make is to let a StopIteration exception finish
+ * the token stream while there are still characters pending in the
+ * string stream (hitting the end of the buffer while parsing a
+ * token). To make it easier to detect such errors, the strings throw
+ * an exception when this happens.
+ */
+
+// Make a string stream out of an iterator that returns strings. This
+// is applied to the result of traverseDOM (see codemirror.js), and
+// the resulting stream is fed to the parser.
+window.stringStream = function(source){
+ // String that's currently being iterated over.
+ var current = "";
+ // Position in that string.
+ var pos = 0;
+ // Accumulator for strings that have been iterated over but not
+ // get()-ed yet.
+ var accum = "";
+ // Make sure there are more characters ready, or throw
+ // StopIteration.
+ function ensureChars() {
+ while (pos == current.length) {
+ accum += current;
+ current = ""; // In case source.next() throws
+ pos = 0;
+ try {current = source.next();}
+ catch (e) {
+ if (e != StopIteration) throw e;
+ else return false;
+ }
+ }
+ return true;
+ }
+
+ return {
+ // Return the next character in the stream.
+ peek: function() {
+ if (!ensureChars()) return null;
+ return current.charAt(pos);
+ },
+ // Get the next character, throw StopIteration if at end, check
+ // for unused content.
+ next: function() {
+ if (!ensureChars()) {
+ if (accum.length > 0)
+ throw "End of stringstream reached without emptying buffer ('" + accum + "').";
+ else
+ throw StopIteration;
+ }
+ return current.charAt(pos++);
+ },
+ // Return the characters iterated over since the last call to
+ // .get().
+ get: function() {
+ var temp = accum;
+ accum = "";
+ if (pos > 0){
+ temp += current.slice(0, pos);
+ current = current.slice(pos);
+ pos = 0;
+ }
+ return temp;
+ },
+ // Push a string back into the stream.
+ push: function(str) {
+ current = current.slice(0, pos) + str + current.slice(pos);
+ },
+ lookAhead: function(str, consume, skipSpaces, caseInsensitive) {
+ function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
+ str = cased(str);
+ var found = false;
+
+ var _accum = accum, _pos = pos;
+ if (skipSpaces) this.nextWhileMatches(/[\s\u00a0]/);
+
+ while (true) {
+ var end = pos + str.length, left = current.length - pos;
+ if (end <= current.length) {
+ found = str == cased(current.slice(pos, end));
+ pos = end;
+ break;
+ }
+ else if (str.slice(0, left) == cased(current.slice(pos))) {
+ accum += current; current = "";
+ try {current = source.next();}
+ catch (e) {break;}
+ pos = 0;
+ str = str.slice(left);
+ }
+ else {
+ break;
+ }
+ }
+
+ if (!(found && consume)) {
+ current = accum.slice(_accum.length) + current;
+ pos = _pos;
+ accum = _accum;
+ }
+
+ return found;
+ },
+
+ // Utils built on top of the above
+ more: function() {
+ return this.peek() !== null;
+ },
+ applies: function(test) {
+ var next = this.peek();
+ return (next !== null && test(next));
+ },
+ nextWhile: function(test) {
+ var next;
+ while ((next = this.peek()) !== null && test(next))
+ this.next();
+ },
+ matches: function(re) {
+ var next = this.peek();
+ return (next !== null && re.test(next));
+ },
+ nextWhileMatches: function(re) {
+ var next;
+ while ((next = this.peek()) !== null && re.test(next))
+ this.next();
+ },
+ equals: function(ch) {
+ return ch === this.peek();
+ },
+ endOfLine: function() {
+ var next = this.peek();
+ return next == null || next == "\n";
+ }
+ };
+};
diff --git a/media/CodeMirror-0.62/js/tokenize.js b/media/CodeMirror-0.62/js/tokenize.js
new file mode 100644
index 0000000..071970c
--- /dev/null
+++ b/media/CodeMirror-0.62/js/tokenize.js
@@ -0,0 +1,57 @@
+// A framework for simple tokenizers. Takes care of newlines and
+// white-space, and of getting the text from the source stream into
+// the token object. A state is a function of two arguments -- a
+// string stream and a setState function. The second can be used to
+// change the tokenizer's state, and can be ignored for stateless
+// tokenizers. This function should advance the stream over a token
+// and return a string or object containing information about the next
+// token, or null to pass and have the (new) state be called to finish
+// the token. When a string is given, it is wrapped in a {style, type}
+// object. In the resulting object, the characters consumed are stored
+// under the content property. Any whitespace following them is also
+// automatically consumed, and added to the value property. (Thus,
+// content is the actual meaningful part of the token, while value
+// contains all the text it spans.)
+
+function tokenizer(source, state) {
+ // Newlines are always a separate token.
+ function isWhiteSpace(ch) {
+ // The messy regexp is because IE's regexp matcher is of the
+ // opinion that non-breaking spaces are no whitespace.
+ return ch != "\n" && /^[\s\u00a0]*$/.test(ch);
+ }
+
+ var tokenizer = {
+ state: state,
+
+ take: function(type) {
+ if (typeof(type) == "string")
+ type = {style: type, type: type};
+
+ type.content = (type.content || "") + source.get();
+ if (!/\n$/.test(type.content))
+ source.nextWhile(isWhiteSpace);
+ type.value = type.content + source.get();
+ return type;
+ },
+
+ next: function () {
+ if (!source.more()) throw StopIteration;
+
+ var type;
+ if (source.equals("\n")) {
+ source.next();
+ return this.take("whitespace");
+ }
+
+ if (source.applies(isWhiteSpace))
+ type = "whitespace";
+ else
+ while (!type)
+ type = this.state(source, function(s) {tokenizer.state = s;});
+
+ return this.take(type);
+ }
+ };
+ return tokenizer;
+}
diff --git a/media/CodeMirror-0.62/js/tokenizejavascript.js b/media/CodeMirror-0.62/js/tokenizejavascript.js
new file mode 100644
index 0000000..f55dfce
--- /dev/null
+++ b/media/CodeMirror-0.62/js/tokenizejavascript.js
@@ -0,0 +1,175 @@
+/* Tokenizer for JavaScript code */
+
+var tokenizeJavaScript = (function() {
+ // Advance the stream until the given character (not preceded by a
+ // backslash) is encountered, or the end of the line is reached.
+ function nextUntilUnescaped(source, end) {
+ var escaped = false;
+ var next;
+ while (!source.endOfLine()) {
+ var next = source.next();
+ if (next == end && !escaped)
+ return false;
+ escaped = !escaped && next == "\\";
+ }
+ return escaped;
+ }
+
+ // A map of JavaScript's keywords. The a/b/c keyword distinction is
+ // very rough, but it gives the parser enough information to parse
+ // correct code correctly (we don't care that much how we parse
+ // incorrect code). The style information included in these objects
+ // is used by the highlighter to pick the correct CSS style for a
+ // token.
+ var keywords = function(){
+ function result(type, style){
+ return {type: type, style: "js-" + style};
+ }
+ // keywords that take a parenthised expression, and then a
+ // statement (if)
+ var keywordA = result("keyword a", "keyword");
+ // keywords that take just a statement (else)
+ var keywordB = result("keyword b", "keyword");
+ // keywords that optionally take an expression, and form a
+ // statement (return)
+ var keywordC = result("keyword c", "keyword");
+ var operator = result("operator", "keyword");
+ var atom = result("atom", "atom");
+ return {
+ "if": keywordA, "while": keywordA, "with": keywordA,
+ "else": keywordB, "do": keywordB, "try": keywordB, "finally": keywordB,
+ "return": keywordC, "break": keywordC, "continue": keywordC, "new": keywordC, "delete": keywordC, "throw": keywordC,
+ "in": operator, "typeof": operator, "instanceof": operator,
+ "var": result("var", "keyword"), "function": result("function", "keyword"), "catch": result("catch", "keyword"),
+ "for": result("for", "keyword"), "switch": result("switch", "keyword"),
+ "case": result("case", "keyword"), "default": result("default", "keyword"),
+ "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
+ };
+ }();
+
+ // Some helper regexps
+ var isOperatorChar = /[+\-*&%\/=<>!?|]/;
+ var isHexDigit = /[0-9A-Fa-f]/;
+ var isWordChar = /[\w\$_]/;
+
+ // Wrapper around jsToken that helps maintain parser state (whether
+ // we are inside of a multi-line comment and whether the next token
+ // could be a regular expression).
+ function jsTokenState(inside, regexp) {
+ return function(source, setState) {
+ var newInside = inside;
+ var type = jsToken(inside, regexp, source, function(c) {newInside = c;});
+ var newRegexp = type.type == "operator" || type.type == "keyword c" || type.type.match(/^[\[{}\(,;:]$/);
+ if (newRegexp != regexp || newInside != inside)
+ setState(jsTokenState(newInside, newRegexp));
+ return type;
+ };
+ }
+
+ // The token reader, inteded to be used by the tokenizer from
+ // tokenize.js (through jsTokenState). Advances the source stream
+ // over a token, and returns an object containing the type and style
+ // of that token.
+ function jsToken(inside, regexp, source, setInside) {
+ function readHexNumber(){
+ source.next(); // skip the 'x'
+ source.nextWhileMatches(isHexDigit);
+ return {type: "number", style: "js-atom"};
+ }
+
+ function readNumber() {
+ source.nextWhileMatches(/[0-9]/);
+ if (source.equals(".")){
+ source.next();
+ source.nextWhileMatches(/[0-9]/);
+ }
+ if (source.equals("e") || source.equals("E")){
+ source.next();
+ if (source.equals("-"))
+ source.next();
+ source.nextWhileMatches(/[0-9]/);
+ }
+ return {type: "number", style: "js-atom"};
+ }
+ // Read a word, look it up in keywords. If not found, it is a
+ // variable, otherwise it is a keyword of the type found.
+ function readWord() {
+ source.nextWhileMatches(isWordChar);
+ var word = source.get();
+ var known = keywords.hasOwnProperty(word) && keywords.propertyIsEnumerable(word) && keywords[word];
+ return known ? {type: known.type, style: known.style, content: word} :
+ {type: "variable", style: "js-variable", content: word};
+ }
+ function readRegexp() {
+ nextUntilUnescaped(source, "/");
+ source.nextWhileMatches(/[gi]/);
+ return {type: "regexp", style: "js-string"};
+ }
+ // Mutli-line comments are tricky. We want to return the newlines
+ // embedded in them as regular newline tokens, and then continue
+ // returning a comment token for every line of the comment. So
+ // some state has to be saved (inside) to indicate whether we are
+ // inside a /* */ sequence.
+ function readMultilineComment(start){
+ var newInside = "/*";
+ var maybeEnd = (start == "*");
+ while (true) {
+ if (source.endOfLine())
+ break;
+ var next = source.next();
+ if (next == "/" && maybeEnd){
+ newInside = null;
+ break;
+ }
+ maybeEnd = (next == "*");
+ }
+ setInside(newInside);
+ return {type: "comment", style: "js-comment"};
+ }
+ function readOperator() {
+ source.nextWhileMatches(isOperatorChar);
+ return {type: "operator", style: "js-operator"};
+ }
+ function readString(quote) {
+ var endBackSlash = nextUntilUnescaped(source, quote);
+ setInside(endBackSlash ? quote : null);
+ return {type: "string", style: "js-string"};
+ }
+
+ // Fetch the next token. Dispatches on first character in the
+ // stream, or first two characters when the first is a slash.
+ if (inside == "\"" || inside == "'")
+ return readString(inside);
+ var ch = source.next();
+ if (inside == "/*")
+ return readMultilineComment(ch);
+ else if (ch == "\"" || ch == "'")
+ return readString(ch);
+ // with punctuation, the type of the token is the symbol itself
+ else if (/[\[\]{}\(\),;\:\.]/.test(ch))
+ return {type: ch, style: "js-punctuation"};
+ else if (ch == "0" && (source.equals("x") || source.equals("X")))
+ return readHexNumber();
+ else if (/[0-9]/.test(ch))
+ return readNumber();
+ else if (ch == "/"){
+ if (source.equals("*"))
+ { source.next(); return readMultilineComment(ch); }
+ else if (source.equals("/"))
+ { nextUntilUnescaped(source, null); return {type: "comment", style: "js-comment"};}
+ else if (regexp)
+ return readRegexp();
+ else
+ return readOperator();
+ }
+ else if (isOperatorChar.test(ch))
+ return readOperator();
+ else
+ return readWord();
+ }
+
+ // The external interface to the tokenizer.
+ return function(source, startState) {
+ return tokenizer(source, startState || jsTokenState(false, true));
+ };
+})();
diff --git a/media/CodeMirror-0.62/js/undo.js b/media/CodeMirror-0.62/js/undo.js
new file mode 100644
index 0000000..1930cfb
--- /dev/null
+++ b/media/CodeMirror-0.62/js/undo.js
@@ -0,0 +1,403 @@
+/**
+ * Storage and control for undo information within a CodeMirror
+ * editor. 'Why on earth is such a complicated mess required for
+ * that?', I hear you ask. The goal, in implementing this, was to make
+ * the complexity of storing and reverting undo information depend
+ * only on the size of the edited or restored content, not on the size
+ * of the whole document. This makes it necessary to use a kind of
+ * 'diff' system, which, when applied to a DOM tree, causes some
+ * complexity and hackery.
+ *
+ * In short, the editor 'touches' BR elements as it parses them, and
+ * the History stores these. When nothing is touched in commitDelay
+ * milliseconds, the changes are committed: It goes over all touched
+ * nodes, throws out the ones that did not change since last commit or
+ * are no longer in the document, and assembles the rest into zero or
+ * more 'chains' -- arrays of adjacent lines. Links back to these
+ * chains are added to the BR nodes, while the chain that previously
+ * spanned these nodes is added to the undo history. Undoing a change
+ * means taking such a chain off the undo history, restoring its
+ * content (text is saved per line) and linking it back into the
+ * document.
+ */
+
+// A history object needs to know about the DOM container holding the
+// document, the maximum amount of undo levels it should store, the
+// delay (of no input) after which it commits a set of changes, and,
+// unfortunately, the 'parent' window -- a window that is not in
+// designMode, and on which setTimeout works in every browser.
+function History(container, maxDepth, commitDelay, editor, onChange) {
+ this.container = container;
+ this.maxDepth = maxDepth; this.commitDelay = commitDelay;
+ this.editor = editor; this.parent = editor.parent;
+ this.onChange = onChange;
+ // This line object represents the initial, empty editor.
+ var initial = {text: "", from: null, to: null};
+ // As the borders between lines are represented by BR elements, the
+ // start of the first line and the end of the last one are
+ // represented by null. Since you can not store any properties
+ // (links to line objects) in null, these properties are used in
+ // those cases.
+ this.first = initial; this.last = initial;
+ // Similarly, a 'historyTouched' property is added to the BR in
+ // front of lines that have already been touched, and 'firstTouched'
+ // is used for the first line.
+ this.firstTouched = false;
+ // History is the set of committed changes, touched is the set of
+ // nodes touched since the last commit.
+ this.history = []; this.redoHistory = []; this.touched = [];
+}
+
+History.prototype = {
+ // Schedule a commit (if no other touches come in for commitDelay
+ // milliseconds).
+ scheduleCommit: function() {
+ var self = this;
+ this.parent.clearTimeout(this.commitTimeout);
+ this.commitTimeout = this.parent.setTimeout(function(){self.tryCommit();}, this.commitDelay);
+ },
+
+ // Mark a node as touched. Null is a valid argument.
+ touch: function(node) {
+ this.setTouched(node);
+ this.scheduleCommit();
+ },
+
+ // Undo the last change.
+ undo: function() {
+ // Make sure pending changes have been committed.
+ this.commit();
+
+ if (this.history.length) {
+ // Take the top diff from the history, apply it, and store its
+ // shadow in the redo history.
+ var item = this.history.pop();
+ this.redoHistory.push(this.updateTo(item, "applyChain"));
+ if (this.onChange) this.onChange();
+ return this.chainNode(item);
+ }
+ },
+
+ // Redo the last undone change.
+ redo: function() {
+ this.commit();
+ if (this.redoHistory.length) {
+ // The inverse of undo, basically.
+ var item = this.redoHistory.pop();
+ this.addUndoLevel(this.updateTo(item, "applyChain"));
+ if (this.onChange) this.onChange();
+ return this.chainNode(item);
+ }
+ },
+
+ clear: function() {
+ this.history = [];
+ this.redoHistory = [];
+ },
+
+ // Ask for the size of the un/redo histories.
+ historySize: function() {
+ return {undo: this.history.length, redo: this.redoHistory.length};
+ },
+
+ // Push a changeset into the document.
+ push: function(from, to, lines) {
+ var chain = [];
+ for (var i = 0; i < lines.length; i++) {
+ var end = (i == lines.length - 1) ? to : this.container.ownerDocument.createElement("BR");
+ chain.push({from: from, to: end, text: cleanText(lines[i])});
+ from = end;
+ }
+ this.pushChains([chain], from == null && to == null);
+ },
+
+ pushChains: function(chains, doNotHighlight) {
+ this.commit(doNotHighlight);
+ this.addUndoLevel(this.updateTo(chains, "applyChain"));
+ this.redoHistory = [];
+ },
+
+ // Retrieve a DOM node from a chain (for scrolling to it after undo/redo).
+ chainNode: function(chains) {
+ for (var i = 0; i < chains.length; i++) {
+ var start = chains[i][0], node = start && (start.from || start.to);
+ if (node) return node;
+ }
+ },
+
+ // Clear the undo history, make the current document the start
+ // position.
+ reset: function() {
+ this.history = []; this.redoHistory = [];
+ },
+
+ textAfter: function(br) {
+ return this.after(br).text;
+ },
+
+ nodeAfter: function(br) {
+ return this.after(br).to;
+ },
+
+ nodeBefore: function(br) {
+ return this.before(br).from;
+ },
+
+ // Commit unless there are pending dirty nodes.
+ tryCommit: function() {
+ if (!window.History) return; // Stop when frame has been unloaded
+ if (this.editor.highlightDirty()) this.commit();
+ else this.scheduleCommit();
+ },
+
+ // Check whether the touched nodes hold any changes, if so, commit
+ // them.
+ commit: function(doNotHighlight) {
+ this.parent.clearTimeout(this.commitTimeout);
+ // Make sure there are no pending dirty nodes.
+ if (!doNotHighlight) this.editor.highlightDirty(true);
+ // Build set of chains.
+ var chains = this.touchedChains(), self = this;
+
+ if (chains.length) {
+ this.addUndoLevel(this.updateTo(chains, "linkChain"));
+ this.redoHistory = [];
+ if (this.onChange) this.onChange();
+ }
+ },
+
+ // [ end of public interface ]
+
+ // Update the document with a given set of chains, return its
+ // shadow. updateFunc should be "applyChain" or "linkChain". In the
+ // second case, the chains are taken to correspond the the current
+ // document, and only the state of the line data is updated. In the
+ // first case, the content of the chains is also pushed iinto the
+ // document.
+ updateTo: function(chains, updateFunc) {
+ var shadows = [], dirty = [];
+ for (var i = 0; i < chains.length; i++) {
+ shadows.push(this.shadowChain(chains[i]));
+ dirty.push(this[updateFunc](chains[i]));
+ }
+ if (updateFunc == "applyChain")
+ this.notifyDirty(dirty);
+ return shadows;
+ },
+
+ // Notify the editor that some nodes have changed.
+ notifyDirty: function(nodes) {
+ forEach(nodes, method(this.editor, "addDirtyNode"))
+ this.editor.scheduleHighlight();
+ },
+
+ // Link a chain into the DOM nodes (or the first/last links for null
+ // nodes).
+ linkChain: function(chain) {
+ for (var i = 0; i < chain.length; i++) {
+ var line = chain[i];
+ if (line.from) line.from.historyAfter = line;
+ else this.first = line;
+ if (line.to) line.to.historyBefore = line;
+ else this.last = line;
+ }
+ },
+
+ // Get the line object after/before a given node.
+ after: function(node) {
+ return node ? node.historyAfter : this.first;
+ },
+ before: function(node) {
+ return node ? node.historyBefore : this.last;
+ },
+
+ // Mark a node as touched if it has not already been marked.
+ setTouched: function(node) {
+ if (node) {
+ if (!node.historyTouched) {
+ this.touched.push(node);
+ node.historyTouched = true;
+ }
+ }
+ else {
+ this.firstTouched = true;
+ }
+ },
+
+ // Store a new set of undo info, throw away info if there is more of
+ // it than allowed.
+ addUndoLevel: function(diffs) {
+ this.history.push(diffs);
+ if (this.history.length > this.maxDepth)
+ this.history.shift();
+ },
+
+ // Build chains from a set of touched nodes.
+ touchedChains: function() {
+ var self = this;
+
+ // The temp system is a crummy hack to speed up determining
+ // whether a (currently touched) node has a line object associated
+ // with it. nullTemp is used to store the object for the first
+ // line, other nodes get it stored in their historyTemp property.
+ var nullTemp = null;
+ function temp(node) {return node ? node.historyTemp : nullTemp;}
+ function setTemp(node, line) {
+ if (node) node.historyTemp = line;
+ else nullTemp = line;
+ }
+
+ function buildLine(node) {
+ var text = [];
+ for (var cur = node ? node.nextSibling : self.container.firstChild;
+ cur && cur.nodeName != "BR"; cur = cur.nextSibling)
+ if (cur.currentText) text.push(cur.currentText);
+ return {from: node, to: cur, text: cleanText(text.join(""))};
+ }
+
+ // Filter out unchanged lines and nodes that are no longer in the
+ // document. Build up line objects for remaining nodes.
+ var lines = [];
+ if (self.firstTouched) self.touched.push(null);
+ forEach(self.touched, function(node) {
+ if (node && node.parentNode != self.container) return;
+
+ if (node) node.historyTouched = false;
+ else self.firstTouched = false;
+
+ var line = buildLine(node), shadow = self.after(node);
+ if (!shadow || shadow.text != line.text || shadow.to != line.to) {
+ lines.push(line);
+ setTemp(node, line);
+ }
+ });
+
+ // Get the BR element after/before the given node.
+ function nextBR(node, dir) {
+ var link = dir + "Sibling", search = node[link];
+ while (search && search.nodeName != "BR")
+ search = search[link];
+ return search;
+ }
+
+ // Assemble line objects into chains by scanning the DOM tree
+ // around them.
+ var chains = []; self.touched = [];
+ forEach(lines, function(line) {
+ // Note that this makes the loop skip line objects that have
+ // been pulled into chains by lines before them.
+ if (!temp(line.from)) return;
+
+ var chain = [], curNode = line.from, safe = true;
+ // Put any line objects (referred to by temp info) before this
+ // one on the front of the array.
+ while (true) {
+ var curLine = temp(curNode);
+ if (!curLine) {
+ if (safe) break;
+ else curLine = buildLine(curNode);
+ }
+ chain.unshift(curLine);
+ setTemp(curNode, null);
+ if (!curNode) break;
+ safe = self.after(curNode);
+ curNode = nextBR(curNode, "previous");
+ }
+ curNode = line.to; safe = self.before(line.from);
+ // Add lines after this one at end of array.
+ while (true) {
+ if (!curNode) break;
+ var curLine = temp(curNode);
+ if (!curLine) {
+ if (safe) break;
+ else curLine = buildLine(curNode);
+ }
+ chain.push(curLine);
+ setTemp(curNode, null);
+ safe = self.before(curNode);
+ curNode = nextBR(curNode, "next");
+ }
+ chains.push(chain);
+ });
+
+ return chains;
+ },
+
+ // Find the 'shadow' of a given chain by following the links in the
+ // DOM nodes at its start and end.
+ shadowChain: function(chain) {
+ var shadows = [], next = this.after(chain[0].from), end = chain[chain.length - 1].to;
+ while (true) {
+ shadows.push(next);
+ var nextNode = next.to;
+ if (!nextNode || nextNode == end)
+ break;
+ else
+ next = nextNode.historyAfter || this.before(end);
+ // (The this.before(end) is a hack -- FF sometimes removes
+ // properties from BR nodes, in which case the best we can hope
+ // for is to not break.)
+ }
+ return shadows;
+ },
+
+ // Update the DOM tree to contain the lines specified in a given
+ // chain, link this chain into the DOM nodes.
+ applyChain: function(chain) {
+ // Some attempt is made to prevent the cursor from jumping
+ // randomly when an undo or redo happens. It still behaves a bit
+ // strange sometimes.
+ var cursor = select.cursorPos(this.container, false), self = this;
+
+ // Remove all nodes in the DOM tree between from and to (null for
+ // start/end of container).
+ function removeRange(from, to) {
+ var pos = from ? from.nextSibling : self.container.firstChild;
+ while (pos != to) {
+ var temp = pos.nextSibling;
+ removeElement(pos);
+ pos = temp;
+ }
+ }
+
+ var start = chain[0].from, end = chain[chain.length - 1].to;
+ // Clear the space where this change has to be made.
+ removeRange(start, end);
+
+ // Insert the content specified by the chain into the DOM tree.
+ for (var i = 0; i < chain.length; i++) {
+ var line = chain[i];
+ // The start and end of the space are already correct, but BR
+ // tags inside it have to be put back.
+ if (i > 0)
+ self.container.insertBefore(line.from, end);
+
+ // Add the text.
+ var node = makePartSpan(fixSpaces(line.text), this.container.ownerDocument);
+ self.container.insertBefore(node, end);
+ // See if the cursor was on this line. Put it back, adjusting
+ // for changed line length, if it was.
+ if (cursor && cursor.node == line.from) {
+ var cursordiff = 0;
+ var prev = this.after(line.from);
+ if (prev && i == chain.length - 1) {
+ // Only adjust if the cursor is after the unchanged part of
+ // the line.
+ for (var match = 0; match < cursor.offset &&
+ line.text.charAt(match) == prev.text.charAt(match); match++);
+ if (cursor.offset > match)
+ cursordiff = line.text.length - prev.text.length;
+ }
+ select.setCursorPos(this.container, {node: line.from, offset: Math.max(0, cursor.offset + cursordiff)});
+ }
+ // Cursor was in removed line, this is last new line.
+ else if (cursor && (i == chain.length - 1) && cursor.node && cursor.node.parentNode != this.container) {
+ select.setCursorPos(this.container, {node: line.from, offset: line.text.length});
+ }
+ }
+
+ // Anchor the chain in the DOM tree.
+ this.linkChain(chain);
+ return start;
+ }
+};
diff --git a/media/CodeMirror-0.62/js/util.js b/media/CodeMirror-0.62/js/util.js
new file mode 100644
index 0000000..f552767
--- /dev/null
+++ b/media/CodeMirror-0.62/js/util.js
@@ -0,0 +1,115 @@
+/* A few useful utility functions. */
+
+var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent);
+var webkit = /AppleWebKit/.test(navigator.userAgent);
+
+// Capture a method on an object.
+function method(obj, name) {
+ return function() {obj[name].apply(obj, arguments);};
+}
+
+// The value used to signal the end of a sequence in iterators.
+var StopIteration = {toString: function() {return "StopIteration"}};
+
+// Apply a function to each element in a sequence.
+function forEach(iter, f) {
+ if (iter.next) {
+ try {while (true) f(iter.next());}
+ catch (e) {if (e != StopIteration) throw e;}
+ }
+ else {
+ for (var i = 0; i < iter.length; i++)
+ f(iter[i]);
+ }
+}
+
+// Map a function over a sequence, producing an array of results.
+function map(iter, f) {
+ var accum = [];
+ forEach(iter, function(val) {accum.push(f(val));});
+ return accum;
+}
+
+// Create a predicate function that tests a string againsts a given
+// regular expression. No longer used but might be used by 3rd party
+// parsers.
+function matcher(regexp){
+ return function(value){return regexp.test(value);};
+}
+
+// Test whether a DOM node has a certain CSS class. Much faster than
+// the MochiKit equivalent, for some reason.
+function hasClass(element, className){
+ var classes = element.className;
+ return classes && new RegExp("(^| )" + className + "($| )").test(classes);
+}
+
+// Insert a DOM node after another node.
+function insertAfter(newNode, oldNode) {
+ var parent = oldNode.parentNode;
+ parent.insertBefore(newNode, oldNode.nextSibling);
+ return newNode;
+}
+
+function removeElement(node) {
+ if (node.parentNode)
+ node.parentNode.removeChild(node);
+}
+
+function clearElement(node) {
+ while (node.firstChild)
+ node.removeChild(node.firstChild);
+}
+
+// Check whether a node is contained in another one.
+function isAncestor(node, child) {
+ while (child = child.parentNode) {
+ if (node == child)
+ return true;
+ }
+ return false;
+}
+
+// The non-breaking space character.
+var nbsp = "\u00a0";
+var matching = {"{": "}", "[": "]", "(": ")",
+ "}": "{", "]": "[", ")": "("};
+
+// Standardize a few unportable event properties.
+function normalizeEvent(event) {
+ if (!event.stopPropagation) {
+ event.stopPropagation = function() {this.cancelBubble = true;};
+ event.preventDefault = function() {this.returnValue = false;};
+ }
+ if (!event.stop) {
+ event.stop = function() {
+ this.stopPropagation();
+ this.preventDefault();
+ };
+ }
+
+ if (event.type == "keypress") {
+ event.code = (event.charCode == null) ? event.keyCode : event.charCode;
+ event.character = String.fromCharCode(event.code);
+ }
+ return event;
+}
+
+// Portably register event handlers.
+function addEventHandler(node, type, handler, removeFunc) {
+ function wrapHandler(event) {
+ handler(normalizeEvent(event || window.event));
+ }
+ if (typeof node.addEventListener == "function") {
+ node.addEventListener(type, wrapHandler, false);
+ if (removeFunc) return function() {node.removeEventListener(type, wrapHandler, false);};
+ }
+ else {
+ node.attachEvent("on" + type, wrapHandler);
+ if (removeFunc) return function() {node.detachEvent("on" + type, wrapHandler);};
+ }
+}
+
+function nodeText(node) {
+ return node.innerText || node.textContent || node.nodeValue || "";
+}
diff --git a/media/CodeMirror-0.62/jstest.html b/media/CodeMirror-0.62/jstest.html
new file mode 100644
index 0000000..13879a7
--- /dev/null
+++ b/media/CodeMirror-0.62/jstest.html
@@ -0,0 +1,56 @@
+
+
+
+
+ CodeMirror: JavaScript demonstration
+
+
+
+
+
This page demonstrates CodeMirror's
+JavaScript parser. Note that the ugly buttons at the top are not are
+not part of CodeMirror proper -- they demonstrate the way it can be
+embedded in a web-application.
This is a simple demonstration of the HTML mixed-mode indentation
+module for CodeMirror. Script tags use the JS
+parser, style tags use the CSS parser.
Note: some of the details given here no
+ longer apply to the current CodeMirror
+ codebase, which has evolved quite a bit in the meantime.
+
+
In one of his (very informative) video
+ lectures, Douglas Crockford remarks that writing JavaScript
+ for the web is 'programming in a hostile environment'. I had done
+ my fair share of weird workarounds, and even occasonally gave up
+ an on idea entirely because browsers just wouldn't support it, but
+ before this project I never really realized just how powerless a
+ programmer can be in the face of buggy, incompatible, and poorly
+ designed platforms.
+
+
The plan was not ridiculously ambitious. I wanted to 'enhance' a
+ textarea to the point where writing code in it is pleasant. This meant
+ automatic indentation and, if possible at all, syntax highlighting.
+
+
In this document I describe the story of implementing this, for your
+ education and amusement. A demonstration of the resulting program,
+ along with the source code, can be found at my website.
+
+
Take one: Only indentation
+
+
The very first attempt merely added auto-indentation to a textarea
+ element. It would scan backwards through the content of the area,
+ starting from the cursor, until it had enough information to decide
+ how to indent the current line. It took me a while to figure out a
+ decent model for indenting JavaScript code, but in the end this seems
+ to work:
+
+
+
Code that sits inside a block is indented one unit (generally two
+ spaces) more than the statement or brace that opened the block.
+
A statement that is continued to the next line is indented one unit
+ more than the line that starts the statement.
+
When dealing with lists of arguments or the content of array and
+ object literals there are two possible models. If there is any text
+ directly after the opening brace, bracket, or parenthesis,
+ subsequent lines are aligned with this opening character. If the
+ opening character is followed by a newline (optionally with whitespace
+ or comments before it), the next line is indented one unit further
+ than the line that started the list.
+
And, obviously, if a statement follows another statement it is
+ indented the same amount as the one before it.
+
+
+
When scanning backwards through code one has to take string values,
+ comments, and regular expressions (which are delimited by slashes)
+ into account, because braces and semicolons and such are not
+ significant when they appear inside them. Single-line ('//') comments
+ turned out to be rather inefficient to check for when doing a
+ backwards scan, since every time you encounter a newline you have to
+ go on to the next newline to determine whether this line ends in a
+ comment or not. Regular expressions are even worse ― without
+ contextual information they are impossible to distinguish from the
+ division operator, and I didn't get them working in this first
+ version.
+
+
To find out which line to indent, and to make sure that adding or
+ removing whitespace doesn't cause the cursor to jump in strange ways,
+ it is necessary to determine which text the user has selected. Even
+ though I was working with just a simple textarea at this point, this
+ was already a bit of a headache.
+
+
On W3C-standards-respecting browsers, textarea nodes have
+ selectionStart and selectionEnd
+ properties which nicely give you the amount of characters before
+ the start and end of the selection. Great!
+
+
Then, there is Internet Explorer. Internet Explorer also has an API
+ for looking at and manipulating selections. It gives you information
+ such as a detailed map of the space the selected lines take up on the
+ screen, in pixels, and of course the text inside the selection. It
+ does, however, not give you much of a clue on where the selection is
+ located in the document.
+
+
After some experimentation I managed to work out an elaborate
+ method for getting something similar to the
+ selectionStart and selectionEnd values
+ in other browsers. It worked like this:
+
+
+
Get the TextRange object corresponding to the selection.
+
Record the length of the text inside it.
+
Make another TextRange that covers the whole textarea element.
+
Set the start of the first TextRange to the start of the second one.
+
Again get the length of the text in the first object.
+
Now selectionEnd is the second length, and selectionStart is
+ the second minus the first one.
+
+
+
That seemed to work, but when resetting the selection after modifying
+ the content of the textarea I ran into another interesting feature of
+ these TextRanges: You can move their endpoints by a given number of
+ characters, which is useful when trying to set a cursor at the Nth
+ character of a textarea, but in this context, newlines are not
+ considered to be characters, so you'll always end up one character too
+ far for every newline you passed. Of course, you can count newlines
+ and compensate for this (though it is still not possible to position
+ the cursor right in front of a newline). Sheesh.
+
+
After ragging on Internet Explorer for a while, let us move on and rag
+ on Firefox a bit. It turns out that, in Firefox, getting and setting
+ the text content of a DOM element is unexplainably expensive,
+ especially when there is a lot of text involved. As soon as I tried to
+ use my indentation code to indent itself (some 400 lines), I found
+ myself waiting for over four seconds every time I pressed enter. That
+ seemed a little slow.
+
+
designMode it is
+
+
The solution was obvious: Since the text inside a textarea can only be
+ manipulated as one single big string, I had to spread it out over
+ multiple nodes. How do you spread editable content over multiple
+ nodes? Right! designMode or contentEditable.
+
+
Now I wasn't entirely naive about designMode, I had been looking
+ into writing a non-messy WYSIWYG editor before, and at that time I had
+ concluded two things:
+
+
+
It is impossible to prevent the user from inserting whichever HTML
+ junk he wants into the document.
+
In Internet Explorer, it is extemely hard to get a good view
+ on what nodes the user has selected.
+
+
+
Basically, the good folks at Microsoft designed a really bad interface
+ for putting editable documents in pages, and the other browsers, not
+ wanting to be left behind, more or less copied that. And there isn't
+ much hope for a better way to do this appearing anytime soon. Wise
+ people probably use a Flash movie or (God forbid) a Java applet for
+ these kind of things, though those are not without drawbacks either.
+
+
Anyway, seeing how using an editable document would also make syntax
+ highlighting possible, I foolishly went ahead. There is something
+ perversely fascinating about trying to build a complicated system on a
+ lousy, unsuitable platform.
+
+
A parser
+
+
How does one do decent syntax highlighting? A very simple scanning can
+ tell the difference between strings, comments, keywords, and other
+ code. But this time I wanted to actually be able to recognize regular
+ expressions, so that I didn't have any blatant incorrect behaviour
+ anymore.
+
+
That brought me to the idea of doing a serious parse on the code. This
+ would not only make detecting regular expressions much easier, it
+ would also give me detailed information about the code, which can be
+ used to determine proper indentation levels, and to make subtle
+ distinctions in colouring, for example the difference between variable
+ names and property names.
+
+
And hey, when we're parsing the whole thing, it would even be possible
+ to make a distinction between local and global variables, and colour
+ them differently. If you've ever programmed JavaScript you can
+ probably imagine how useful this would be ― it is ridiculously easy
+ to accidentally create global instead of local variables. I don't
+ consider myself a JavaScript rookie anymore, but it was (embarrasingly
+ enough) only this week that I realized that my habit of typing for
+ (name in object) ... was creating a global variable name, and that
+ I should be typing for (var name in object) ... instead.
+
+
Re-parsing all the code the user has typed in every time he hits a key
+ is obviously not feasible. So how does one combine on-the-fly
+ highlighting with a serious parser? One option would be to split the
+ code into top-level statements (functions, variable definitions, etc.)
+ and parse these separately. This is horribly clunky though, especially
+ considering the fact that modern JavaScripters often put all the code
+ in a file in a single big object or function to prevent namespace
+ pollution.
+
+
I have always liked continuation-passing style and generators. So the
+ idea I came up with is this: An interruptable, resumable parser. This
+ is a parser that does not run through a whole document at once, but
+ parses on-demand, a little bit at a time. At any moment you can create
+ a copy of its current state, which can be resumed later. You start
+ parsing at the top of the code, and keep going as long as you like,
+ but throughout the document, for example at every end of line, you
+ store a copy of the current parser state. Later on, when line 106
+ changes, you grab the interrupted parser that was stored at the end of
+ line 105, and use it to re-parse line 106. It still knows exactly what
+ the context was at that point, which local variables were defined,
+ which unfinished statements were encountered, and so on.
+
+
But that, unfortunately, turned out to be not quite as easy as it
+ sounds.
+
+
The DOM nodes underfoot
+
+
Of course, when working inside an editable frame we don't just
+ have to deal with text. The code will be represented by some kind
+ of DOM tree. My first idea was to set the white-space:
+ pre style for the frame and try to work with mostly text,
+ with the occasional coloured span element. It turned
+ out that support for white-space: pre in browsers,
+ especially in editable frames, is so hopelessly glitchy that this
+ was unworkable.
+
+
Next I tried a series of div elements, one per
+ line, with span elements inside them. This seemed to
+ nicely reflect the structure of the code in a shallowly
+ hierarchical way. I soon realized, however, that my code would be
+ much more straightfoward when using no hierarchy whatsoever
+ ― a series of spans, with br tags
+ at the end of every line. This way, the DOM nodes form a flat
+ sequence that corresponds to the sequence of the text ―
+ just extract text from span nodes and substitute
+ newlines for br nodes.
+
+
It would be a shame if the editor would fall apart as soon as
+ someone pastes some complicated HTML into it. I wanted it to be
+ able to deal with whatever mess it finds. This means using some
+ kind of HTML-normalizer that takes arbitrary HTML and flattens it
+ into a series of brs and span elements
+ that contain a single text node. Just like the parsing process, it
+ would be best if this did not have to done to the entire buffer
+ every time something changes.
+
+
It took some banging my head against my keyboard, but I found a very
+ nice way to model this. It makes heavy use of generators, for which I
+ used MochiKit's iterator
+ framework. Bob Ippolito explains the concepts in this library very
+ well in his blog
+ post about it. (Also notice some of the dismissive comments at the
+ bottom of that post. They say "I don't think I really want to learn
+ this, so I'll make up some silly reason to condemn it.")
+
+
The highlighting process consists of the following elements:
+ normalizing the DOM tree, extracting the text from the DOM tree,
+ tokenizing this text, parsing the tokens, and finally adjusting the
+ DOM nodes to reflect the structure of the code.
+
+
The first two, I put into a single generator. It scans the DOM
+ tree, fixing anything that is not a simple top-level
+ span or br, and it produces the text
+ content of the nodes (or a newline in case of a br)
+ as its output ― each time it is called, it yields a string.
+ Continuation passing style was a good way to model this process in
+ an iterator, which has to be processed one step at a time. Look at
+ this simplified version:
+
+
functiontraverseDOM(start){
+ varcc = function(){scanNode(start, stop);};
+ functionstop(){
+ cc = stop;
+ throwStopIteration;
+ }
+ functionyield(value, c){
+ cc = c;
+ returnvalue;
+ }
+
+ functionscanNode(node, c){
+ if (node.nextSibling)
+ varnextc = function(){scanNode(node.nextSibling, c);};
+ else
+ varnextc = c;
+
+ if (/* node is proper span element */)
+ returnyield(node.firstChild.nodeValue, nextc);
+ elseif (/* node is proper br element */)
+ returnyield("\n", nextc);
+ else
+ /* flatten node, yield its textual content */;
+ }
+
+ return {next: function(){returncc();}};
+}
+
+
The variable c stands for 'continuation', and cc for 'current
+ continuation' ― that last variable is used to store the function to
+ continue with, when yielding a value to the outside world. Every time
+ control leaves this function, it has to make sure that cc is set to
+ a suitable value, which is what yield and stop take care of.
+
+
The object that is returned contains a next method, which is
+ MochiKit's idea of an iterator, and the initial continuation just
+ throws a StopIteration, which is how MochiKit signals that an
+ iterator has reached its end.
+
+
The first lines of scanNode extend the continuation with the task of
+ scanning the next node, if there is a next node. The rest of the
+ function decides what kind of value to yield. Note that this is a
+ rather trivial example of this technique, since the process of going
+ through these nodes is basically linear (it was much, much more
+ complex in earlier versions), but still the trick with the
+ continuations makes the code shorter and, for those in the know,
+ clearer than the equivalent 'storing the iterator state in variables'
+ approach.
+
+
The next iterator that the input passes through is the
+ tokenizer. Well, actually, there is another iterator in between
+ that isolates the tokenizer from the fact that the DOM traversal
+ yields a bunch of separate strings, and presents them as a single
+ character stream (with a convenient peek operation),
+ but this is not a very interesting one. What the tokenizer returns
+ is a stream of token objects, each of which has a
+ value, its textual content, a type, like
+ "variable", "operator", or just itself,
+ "{" for example, in the case of significant
+ punctuation or special keywords. They also have a
+ style, which is used later by the highlighter to give
+ their span elements a class name (the parser will
+ still adjust this in some cases).
+
+
At first I assumed the parser would have to talk back to the
+ tokenizer about the current context, in order to be able to
+ distinguish those accursed regular expressions from divisions, but
+ it seems that regular expressions are only allowed if the previous
+ (non-whitespace, non-comment) token was either an operator, a
+ keyword like new or throw, or a specific
+ kind of punctuation ("[{}(,;:") that indicates a new
+ expression can be started here. This made things considerably
+ easier, since the 'regexp or no regexp' question could stay
+ entirely within the tokenizer.
+
+
The next step, then, is the parser. It does not do a very
+ thorough job because, firstly, it has to be fast, and secondly, it
+ should not go to pieces when fed an incorrect program. So only
+ superficial constructs are recognized, keywords that resemble each
+ other in syntax, such as while and if,
+ are treated in precisely the same way, as are try and
+ else ― the parser doesn't mind if an
+ else appears without an if. Stuff that
+ binds variables, var, function, and
+ catch to be precise, is treated with more care,
+ because the parser wants to know about local variables.
+
+
Inside the parser, three kinds of context are stored. Firstly, a set
+ of known local variables, which is used to adjust the style of
+ variable tokens. Every time the parser enters a function, a new set of
+ variables is created. If there was already such a set (entering an
+ inner function), a pointer to the old one is stored in the new one. At
+ the end of the function, the current variable set is 'popped' off and
+ the previous one is restored.
+
+
The second kind of context is the lexical context, this keeps track of
+ whether we are inside a statement, block, or list. Like the variable
+ context, it also forms a stack of contexts, with each one containing a
+ pointer to the previous ones so that they can be popped off again when
+ they are finished. This information is used for indentation. Every
+ time the parser encounters a newline token, it attaches the current
+ lexical context and a 'copy' of itself (more about that later) to this
+ token.
+
+
The third context is a continuation context. This parser does not use
+ straight continuation style, instead it uses a stack of actions that
+ have to be performed. These actions are simple functions, a kind of
+ minilanguage, they act on tokens, and decide what kind of new actions
+ should be pushed onto the stack. Here are some examples:
The function cont (for continue), will push the actions it is given
+ onto the stack (in reverse order, so that the first one will be popped
+ first). Actions such as pushlex and poplex merely adjust the
+ lexical environment, while others, such as expression itself, do
+ actual parsing. pass, as seen in block, is similar to cont, but
+ it does not 'consume' the current token, so the next action will again
+ see this same token. In block, this happens when the function
+ determines that we are not at the end of the block yet, so it pushes
+ the statement function which will interpret the current token as the
+ start of a statement.
+
+
These actions are called by a 'driver' function, which filters out the
+ whitespace and comments, so that the parser actions do not have to
+ think about those, and keeps track of some things like the indentation
+ of the current line and the column at which the current token ends,
+ which are stored in the lexical context and used for indentation.
+ After calling an action, if the action called cont, this driver
+ function will return the current token, if pass (or nothing) was
+ called, it will immediately continue with the next action.
+
+
This goes to show that it is viable to write a quite elaborate
+ minilanguage in a macro-less language like JavaScript. I don't think
+ it would be possible to do something like this without closures (or
+ similarly powerful abstraction) though, I've certainly never seen
+ anything like it in Java code.
+
+
The way a 'copy' of the parser was produced shows a nice usage
+ of closures. Like with the DOM transformer shown above, most of
+ the local state of the parser is held in a closure produced by
+ calling parse(stream). The function
+ copy, which is local to the parser function, produces
+ a new closure, with copies of all the relevant variables:
Where parser is the object that contains the next (driver)
+ function, and a reference to this copy function. When the function
+ that copy produces is called with a token stream as argument, it
+ updates the local variables in the parser closure, and returns the
+ corresponding iterator object.
+
+
Moving on, we get to the last stop in this chain of generators, the
+ actual highlighter. You can view this one as taking two streams as
+ input, on the one hand there is the stream of tokens from the parser,
+ and on the other hand there is the DOM tree as left by the DOM
+ transformer. If everything went correctly, these two should be
+ synchronized. The highlighter can look at the current token, see if
+ the span in the DOM tree corresponds to it (has the same text
+ content, and the correct class), and if not it can chop up the DOM
+ nodes to conform to the tokens.
+
+
Every time the parser yields a newline token, the highligher
+ encounters a br element in the DOM stream. It takes the copy of the
+ parser and the lexical context from this token and attaches them to
+ the DOM node. This way, a new highlighting process can be started from
+ that node by re-starting the copy of the parser with a new token
+ stream, which reads tokens from the DOM nodes starting at that br
+ element, and the indentation code can use the lexical context
+ information to determine the correct indentation at that point.
+
+
Selection woes
+
+
All the above can be done using the DOM interface that all major
+ browsers have in common, and which is relatively free of weird bugs
+ and abberrations. However, when the user is typing in new code, this
+ must also be highlighted. For this to happen, the program must know
+ where the cursor currently is, and because it mucks up the DOM tree,
+ it has to restore this cursor position after doing the highlighting.
+
+
Re-highlighting always happens per line, because the copy of the
+ parser is stored only at the end of lines. Doing this every time the
+ user presses a key is terribly slow and obnoxious, so what I did was
+ keep a list of 'dirty' nodes, and as soon as the user didn't type
+ anyting for 300 milliseconds the program starts re-highlighting these
+ nodes. If it finds more than ten lines must be re-parsed, it does only
+ ten and waits another 300 milliseconds before it continues, this way
+ the browser never freezes up entirely.
+
+
As mentioned earlier, Internet Explorer's selection model is not the
+ most practical one. My attempts to build a wrapper that makes it look
+ like the W3C model all stranded. In the end I came to the conclusion
+ that I only needed two operations:
+
+
+
Creating a selection 'snapshot' that can be restored after
+ highlighting, in such a way that it still works if some of the nodes
+ that were selected are replaced by other nodes with the same
+ size but a different structure.
+
Finding the top-level node around or before the cursor, to mark it
+ dirty or to insert indentation whitespace at the start of that line.
+
+
+
It turns out that the pixel-based selection model that Internet
+ Explorer uses, which always seemed completely ludricrous to me, is
+ perfect for the first case. Since the DOM transformation (generally)
+ does not change the position of things, storing the pixel offsets of
+ the selection makes it possible to restore that same selection, never
+ mind what happened to the underlying DOM structure.
+
+
[Later addition: Note that this, due to the very random design
+ of the TextRange
+ interface, only really works when the whole selection falls
+ within the visible part of the document.]
+
+
Doing the same with the W3C selection model is a lot harder. What I
+ ended up with was this:
+
+
+
Create an object pointing to the nodes at the start and end of the
+ selection, and the offset within those nodes. This is basically the
+ information that the Range object gives you.
+
Make references from these nodes back to that object.
+
When replacing (part of) a node with another one, check for such a
+ reference, and when it is present, check whether this new node will
+ get the selection. If it does, move the reference from the old to the
+ new node, if it does not, adjust the offset in the selection object to
+ reflect the fact that part of the old node has been replaced.
+
+
+
Now in the second case (getting the top-level node at the
+ cursor) the Internet Explorer cheat does not work. In the W3C
+ model this is rather easy, you have to do some creative parent-
+ and sibling-pointer following to arrive at the correct top-level
+ node, but nothing weird. In Internet Explorer, all we have to go
+ on is the parentElement method on a
+ TextRange, which gives the first element that
+ completely envelops the selection. If the cursor is inside a text
+ node, this is good, that text node tells us where we are. If the
+ cursor is between nodes, for example between two br
+ nodes, you get to top-level node itself back, which is remarkably
+ useless. In cases like this I stoop to a rather ugly hack (which
+ fortunately turned out to be acceptably fast) ― I create a
+ temporary empty span with an ID inside the selection,
+ get a reference to this span by ID, take its
+ previousSibling, and remove it again.
+
+
Unfortunately, Opera's selection implementation is buggy, and it
+ will give wildly incorrect Range objects when the cursor
+ is between two nodes. This is a bit of a showstopper, and until I find
+ a workaround for that or it gets fixed, the highlighter doesn't work
+ properly in Opera.
+
+
Also, when one presses enter in a designMode
+ document in Firefox or Opera, a br tag is inserted.
+ In Internet Explorer, pressing enter causes some maniacal gnome to
+ come out and start wrapping all the content before and after the
+ cursor in p tags. I suppose there is something to be
+ said for that, in principle, though if you saw the tag soup of
+ fonts and nested paragraphs Internet Explorer
+ generates you would soon enough forget all about principle.
+ Anyway, getting unwanted p tags slowed the
+ highlighter down terribly ― it had to overhaul the whole
+ DOM tree to remove them again, every time the user pressed enter.
+ Fortunately I could fix this by capturing the enter presses and
+ manually inserting a br tag at the cursor.
+
+
On the subject of Internet Explorer's tag soup, here is an interesting
+ anecdote: One time, when testing the effect that modifying the content
+ of a selection had, I inspected the DOM tree and found a "/B"
+ element. This was not a closing tag, there are no closing tags in the
+ DOM tree, just elements. The nodeName of this element was actually
+ "/B". That was when I gave up any notions of ever understanding the
+ profound mystery that is Internet Explorer.
+
+
Closing thoughts
+
+
Well, I despaired at times, but I did end up with a working JavaScript
+ editor. I did not keep track of the amount of time I wasted on this,
+ but I would estimate it to be around fifty hours. Finding workarounds
+ for browser bugs can be a terribly nonlinear process. I just spent
+ half a day working on a weird glitch in Firefox that caused the cursor
+ in the editable frame to be displayed 3/4 line too high when it was at
+ the very end of the document. Then I found out that setting the
+ style.display of the iframe to "block" fixed this (why not?). I'm
+ amazed how often issues that seem hopeless do turn out to be
+ avoidable, even if it takes hours of screwing around and some truly
+ non-obvious ideas.
+
+
For a lot of things, JavaScript + DOM elements are a surprisingly
+ powerful platform. Simple interactive documents and forms can be
+ written in browsers with very little effort, generally less than with
+ most 'traditional' platforms (Java, Win32, things like WxWidgets).
+ Libraries like Dojo (and a similar monster I once wrote myself) even
+ make complex, composite widgets workable. However, when applications
+ go sufficiently beyond the things that browsers were designed for, the
+ available APIs do not give enough control, are nonstandard and buggy,
+ and are often poorly designed. Because of this, writing such
+ applications, when it is even possible, is painful process.
+
+
And who likes pain? Sure, when finding that crazy workaround,
+ subdueing the damn browser, and getting everything to work, there
+ is a certain macho thrill. But one can't help wondering how much
+ easier things like preventing the user from pasting pictures in
+ his source code would be on another platform. Maybe something like
+ Silverlight or whatever other new browser plugin gizmos people are
+ pushing these days will become the way to solve things like this
+ in the future. But, personally, I would prefer for those browser
+ companies to put some real effort into things like cleaning up and
+ standardising shady things like designMode, fixing
+ their bugs, and getting serious about ECMAScript 4.
+
+
Which is probably not realistically going to happen anytime soon.
If you have any remarks, criticism, or hints related to the
+ above, drop me an e-mail at marijnh@gmail.com. If you say
+ something generally interesting, I'll include your reaction here
+ at the bottom of this page.