├── .gitignore ├── README.md ├── TODO └── site ├── app.yaml ├── index.yaml ├── legacy.py ├── main.py ├── models.py ├── static ├── .DS_Store ├── contrib │ ├── php │ │ ├── LICENSE │ │ ├── css │ │ │ └── phpcolors.css │ │ ├── index.html │ │ └── js │ │ │ ├── parsephp.js │ │ │ ├── parsephphtmlmixed.js │ │ │ └── tokenizephp.js │ └── python │ │ ├── LICENSE │ │ ├── css │ │ └── pythoncolors.css │ │ ├── index.html │ │ └── js │ │ └── parsepython.js ├── css │ ├── csscolors.css │ ├── docs.css │ ├── jscolors.css │ ├── people.jpg │ ├── sparqlcolors.css │ └── xmlcolors.css ├── favicon.ico ├── js │ ├── codemirror.js │ ├── editor.js │ ├── mirrorframe.js │ ├── parsecss.js │ ├── parsedummy.js │ ├── parsehtmlmixed.js │ ├── parsejavascript.js │ ├── parsesparql.js │ ├── parsexml.js │ ├── select.js │ ├── stringstream.js │ ├── tokenize.js │ ├── tokenizejavascript.js │ ├── undo.js │ └── util.js ├── prototype.js ├── robots.txt └── style.css └── templates ├── base.html ├── new.html ├── not_authorized.html ├── not_found.html └── script.html /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progrium/scriptlets/ea59e09cc7bd07ee72102e0266662e1770e9af64/README.md -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /site/app.yaml: -------------------------------------------------------------------------------- 1 | application: scriptletsapp 2 | version: 2 3 | runtime: python 4 | api_version: 1 5 | 6 | handlers: 7 | - url: /favicon.ico 8 | static_files: static/favicon.ico 9 | upload: static/favicon.ico 10 | - url: /robots.txt 11 | static_files: static/robots.txt 12 | upload: static/robots.txt 13 | - url: /static 14 | static_dir: static 15 | - url: /view.* 16 | script: legacy.py 17 | - url: /code.* 18 | script: legacy.py 19 | - url: /run.* 20 | script: legacy.py 21 | - url: /.* 22 | script: main.py 23 | -------------------------------------------------------------------------------- /site/index.yaml: -------------------------------------------------------------------------------- 1 | indexes: 2 | 3 | # AUTOGENERATED 4 | 5 | # This index.yaml is automatically updated whenever the dev_appserver 6 | # detects that a new type of query is run. If you want to manage the 7 | # index.yaml file manually, remove the above marker line (the line 8 | # saying "# AUTOGENERATED"). If you want to manage some indexes 9 | # manually, move them above the marker line. The index.yaml file is 10 | # automatically uploaded to the admin console when you next deploy 11 | # your application using appcfg.py. 12 | -------------------------------------------------------------------------------- /site/legacy.py: -------------------------------------------------------------------------------- 1 | import wsgiref.handlers 2 | from google.appengine.ext import webapp 3 | from google.appengine.api import urlfetch 4 | import urllib 5 | 6 | LEGACY_URL = "http://1.latest.scriptletsapp.appspot.com" 7 | 8 | class LegacyAdaptor(webapp.RequestHandler): 9 | def get(self): 10 | self.redirect('%s%s' % (LEGACY_URL, self.request.path)) 11 | 12 | def post(self): 13 | payload = dict(self.request.POST) 14 | headers = dict(self.request.headers) 15 | self.response.out.write(urlfetch.fetch( 16 | url='%s%s' % (LEGACY_URL, self.request.path), 17 | payload=urllib.urlencode(payload) if len(payload) else None, 18 | method=self.request.method, 19 | headers=headers, 20 | deadline=10).content) 21 | 22 | def main(): 23 | application = webapp.WSGIApplication([ 24 | ('/view/.*', LegacyAdaptor), 25 | ('/code/.*', LegacyAdaptor), 26 | ('/run/.*', LegacyAdaptor), 27 | ], debug=True) 28 | wsgiref.handlers.CGIHandler().run(application) 29 | 30 | 31 | if __name__ == '__main__': 32 | main() -------------------------------------------------------------------------------- /site/main.py: -------------------------------------------------------------------------------- 1 | import wsgiref.handlers 2 | import urllib, cgi 3 | 4 | from google.appengine.ext import webapp 5 | from google.appengine.api import users 6 | from google.appengine.api import urlfetch 7 | from google.appengine.ext.webapp import template 8 | from models import Script, Version 9 | 10 | class NewHandler(webapp.RequestHandler): 11 | def get(self): 12 | user = users.get_current_user() 13 | if user: 14 | logout_url = users.create_logout_url('/') 15 | else: 16 | login_url = users.create_login_url('/') 17 | self.response.out.write(template.render('templates/new.html', locals())) 18 | 19 | def post(self): 20 | user = users.get_current_user() 21 | body = self.request.get('script') 22 | if body: 23 | script = Script() 24 | script.put() 25 | version = Version(script=script, body=body) 26 | version.put() 27 | script.latest_version = version 28 | script.put() 29 | self.redirect('/%s' % script.id) 30 | else: 31 | self.redirect('/new') 32 | 33 | def script_routing(f): 34 | def wrap(self): 35 | parts = self.request.path.split('/')[1:] 36 | if parts[-1] == '': 37 | parts.pop() 38 | if '.' in parts[-1]: 39 | part, ext = parts[-1].split('.') 40 | parts[-1] = part 41 | else: 42 | ext = None 43 | script = Script.all().filter('id =', parts[0]).get() 44 | version = None 45 | if script: 46 | if len(parts) > 1: 47 | if parts[1] == 'latest': 48 | return self.redirect('/%s/%s' % (script.id, script.latest_version.id)) 49 | else: 50 | version = script.version_set.filter('id =', parts[1]).get() 51 | else: 52 | version = script.latest_version 53 | if not version: 54 | self.not_found() 55 | elif self.should_run(): 56 | self.run(script, version) 57 | else: 58 | user = users.get_current_user() 59 | if script.private and script.user.key() != user.key(): 60 | return self.not_authorized() 61 | if ext == 'js': 62 | self.response.headers['Content-Type'] = 'text/javascript' 63 | self.response.out.write(version.body) 64 | else: 65 | f(self, script, version, user) 66 | return wrap 67 | 68 | class ScriptHandler(webapp.RequestHandler): 69 | def run(self, script, version): 70 | self.response.out.write(urlfetch.fetch( 71 | url='http://eval.progrium.com', 72 | payload=urllib.urlencode({'script': script.latest_version.body}), 73 | method='POST', 74 | deadline=10).content) 75 | 76 | def should_run(self): 77 | return self.request.host.startswith('run.') or self.request.path.split('/')[-1] == 'run' 78 | 79 | def not_found(self): 80 | self.error(404) 81 | self.response.out.write(template.render('templates/not_found.html', locals())) 82 | 83 | def not_authorized(self): 84 | self.error(403) 85 | self.response.out.write(template.render('templates/not_authorized.html', locals())) 86 | 87 | @script_routing 88 | def get(self, script, version, user=None): 89 | if not script.user: 90 | if user: 91 | script.user = user 92 | script.put() 93 | else: 94 | self.redirect(users.create_login_url('/%s' % script_id)) 95 | self.response.out.write(template.render('templates/script.html', locals())) 96 | 97 | @script_routing 98 | def post(self, script, version, user=None): 99 | if script.user.key() != user.key(): 100 | return self.not_authorized() 101 | version = Version(script=script, body=self.request.get('script')) 102 | version.put() 103 | script.latest_version = version 104 | script.put() 105 | self.redirect('/%s' % script.id) 106 | 107 | class RootHandler(webapp.RequestHandler): 108 | def get(self): 109 | self.redirect('/new') 110 | 111 | def main(): 112 | application = webapp.WSGIApplication([ 113 | ('/', RootHandler), 114 | ('/new', NewHandler), 115 | ('/.*', ScriptHandler), 116 | ], debug=True) 117 | wsgiref.handlers.CGIHandler().run(application) 118 | 119 | 120 | if __name__ == '__main__': 121 | main() -------------------------------------------------------------------------------- /site/models.py: -------------------------------------------------------------------------------- 1 | import time 2 | from google.appengine.ext import db 3 | 4 | def baseN(num,b=62,numerals="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"): 5 | return ((num == 0) and "0" ) or (baseN(num // b, b).lstrip("0") + numerals[num % b]) 6 | 7 | class Script(db.Model): 8 | user = db.UserProperty(auto_current_user_add=True) 9 | id = db.StringProperty(required=True) 10 | name = db.StringProperty() 11 | private = db.BooleanProperty(default=False) 12 | latest_version = db.ReferenceProperty() 13 | 14 | created = db.DateTimeProperty(auto_now_add=True) 15 | edited = db.DateTimeProperty() 16 | executed = db.DateTimeProperty() 17 | run_count = db.IntegerProperty(default=0) 18 | 19 | # Legacy fields 20 | name = db.StringProperty() 21 | code = db.TextProperty() 22 | language = db.StringProperty() 23 | 24 | def __init__(self, *args, **kwargs): 25 | kwargs['id'] = kwargs.get('id', baseN(abs(hash(time.time())))) 26 | super(Script, self).__init__(*args, **kwargs) 27 | 28 | class Version(db.Model): 29 | script = db.ReferenceProperty(Script) 30 | id = db.StringProperty(required=True) 31 | body = db.TextProperty() 32 | 33 | created = db.DateTimeProperty(auto_now_add=True) 34 | executed = db.DateTimeProperty() 35 | run_count = db.IntegerProperty(default=0) 36 | 37 | def __init__(self, *args, **kwargs): 38 | kwargs['id'] = kwargs.get('id', str(abs(hash(time.time())))) 39 | super(Version, self).__init__(*args, **kwargs) -------------------------------------------------------------------------------- /site/static/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progrium/scriptlets/ea59e09cc7bd07ee72102e0266662e1770e9af64/site/static/.DS_Store -------------------------------------------------------------------------------- /site/static/contrib/php/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2009, Yahoo! Inc. 2 | All rights reserved. 3 | 4 | This software is provided for use in connection with the 5 | CodeMirror suite of modules and utilities, hosted and maintained 6 | at http://marijn.haverbeke.nl/codemirror/. 7 | 8 | Redistribution and use of this software in source and binary forms, 9 | with or without modification, are permitted provided that the 10 | following conditions are met: 11 | 12 | * Redistributions of source code must retain the above 13 | copyright notice, this list of conditions and the 14 | following disclaimer. 15 | 16 | * Redistributions in binary form must reproduce the above 17 | copyright notice, this list of conditions and the 18 | following disclaimer in the documentation and/or other 19 | materials provided with the distribution. 20 | 21 | * Neither the name of Yahoo! Inc. nor the names of its 22 | contributors may be used to endorse or promote products 23 | derived from this software without specific prior 24 | written permission of Yahoo! Inc. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 29 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 30 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 31 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 32 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 33 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 35 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 36 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 37 | POSSIBILITY OF SUCH DAMAGE. 38 | -------------------------------------------------------------------------------- /site/static/contrib/php/css/phpcolors.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2009 Yahoo! Inc. All rights reserved. 3 | The copyrights embodied in the content of this file are licensed by 4 | Yahoo! Inc. under the BSD (revised) open source license 5 | 6 | @author Dan Vlad Dascalescu 7 | */ 8 | 9 | .editbox { 10 | margin: .4em; 11 | padding: 0; 12 | font-family: monospace; 13 | font-size: 10pt; 14 | } 15 | 16 | /*We should define specific styles for every element of the syntax. 17 | the setting below will cause some annoying color to show through if we missed 18 | defining a style for a token. This is also the "color" of the whitespace and 19 | of the cursor. 20 | */ 21 | pre.code, .editbox { 22 | color: red; 23 | } 24 | 25 | .editbox p { 26 | margin: 0; 27 | } 28 | 29 | span.php-punctuation { 30 | color: blue; 31 | } 32 | 33 | span.php-keyword { 34 | color: #770088; 35 | font-weight: bold; 36 | } 37 | 38 | span.php-operator { 39 | color: blue; 40 | } 41 | 42 | /* __FILE__ etc.; http://php.net/manual/en/reserved.php */ 43 | span.php-compile-time-constant { 44 | color: #776088; 45 | font-weight: bold; 46 | } 47 | 48 | /* output of get_defined_constants(). Differs from http://php.net/manual/en/reserved.constants.php */ 49 | span.php-predefined-constant { 50 | color: darkgreen; 51 | font-weight: bold; 52 | } 53 | 54 | /* PHP reserved "language constructs"... echo() etc.; http://php.net/manual/en/reserved.php */ 55 | span.php-reserved-language-construct { 56 | color: green; 57 | font-weight: bold; 58 | } 59 | 60 | /* PHP built-in functions: glob(), chr() etc.; output of get_defined_functions()["internal"] */ 61 | span.php-predefined-function { 62 | color: green; 63 | } 64 | 65 | /* PHP predefined classes: PDO, Exception etc.; output of get_declared_classes() and different from http://php.net/manual/en/reserved.classes.php */ 66 | span.php-predefined-class { 67 | color: green; 68 | } 69 | 70 | span.php-atom { 71 | color: #228811; 72 | } 73 | 74 | /* class, interface, namespace or function names, but not $variables */ 75 | span.php-t_string { 76 | color: black; 77 | } 78 | 79 | span.php-variable { 80 | color: black; 81 | font-weight: bold; 82 | } 83 | 84 | 85 | span.js-localvariable { 86 | color: #004499; 87 | } 88 | 89 | span.php-comment { 90 | color: #AA7700; 91 | font-stretch: condensed; 92 | /* font-style: italic; This causes line height to slightly change, getting line numbers out of sync */ 93 | } 94 | 95 | span.php-string-single-quoted { 96 | color: #AA2222; 97 | } 98 | /* double quoted strings allow interpolation */ 99 | span.php-string-double-quoted { 100 | color: #AA2222; 101 | font-weight: bold; 102 | } 103 | 104 | span.syntax-error { 105 | background-color: red; 106 | } 107 | 108 | span.deprecated { 109 | font-size: smaller; 110 | } 111 | -------------------------------------------------------------------------------- /site/static/contrib/php/index.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | CodeMirror: PHP+HTML+JavaScript+CSS mixed-mode demonstration 15 | 16 | 17 | 18 | 19 |

This is a complex demonstration of the PHP+HTML+JavaScript+CSS mixed-mode 20 | syntax highlight capabilities of CodeMirror. 21 | <?php ... ?> tags use the PHP parser, <script> tags use the JavaScript 22 | parser, and <style> tags use the CSS parser. The rest of the content is 23 | parsed using the XML parser in HTML mode.

24 | 25 |

Features of the PHP parser: 26 |

39 |

40 | 41 |
42 | 276 |
277 | 278 | 289 | 290 | 291 | 292 | 293 | -------------------------------------------------------------------------------- /site/static/contrib/php/js/parsephp.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2009 Yahoo! Inc. All rights reserved. 3 | The copyrights embodied in the content of this file are licensed by 4 | Yahoo! Inc. under the BSD (revised) open source license 5 | 6 | @author Dan Vlad Dascalescu 7 | 8 | 9 | Parse function for PHP. Makes use of the tokenizer from tokenizephp.js. 10 | Based on parsejavascript.js by Marijn Haverbeke. 11 | 12 | 13 | Features: 14 | + special "deprecated" style for PHP4 keywords like 'var' 15 | + support for PHP 5.3 keywords: 'namespace', 'use' 16 | + 911 predefined constants, 1301 predefined functions, 105 predeclared classes 17 | from a typical PHP installation in a LAMP environment 18 | + new feature: syntax error flagging, thus enabling strict parsing of: 19 | + function definitions with explicitly or implicitly typed arguments and default values 20 | + modifiers (public, static etc.) applied to method and member definitions 21 | + foreach(array_expression as $key [=> $value]) loops 22 | + differentiation between single-quoted strings and double-quoted interpolating strings 23 | 24 | */ 25 | 26 | 27 | // add the Array.indexOf method for JS engines that don't support it (e.g. IE) 28 | // code from https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Global_Objects/Array/IndexOf 29 | if (!Array.prototype.indexOf) 30 | { 31 | Array.prototype.indexOf = function(elt /*, from*/) 32 | { 33 | var len = this.length; 34 | 35 | var from = Number(arguments[1]) || 0; 36 | from = (from < 0) 37 | ? Math.ceil(from) 38 | : Math.floor(from); 39 | if (from < 0) 40 | from += len; 41 | 42 | for (; from < len; from++) 43 | { 44 | if (from in this && 45 | this[from] === elt) 46 | return from; 47 | } 48 | return -1; 49 | }; 50 | }; 51 | 52 | 53 | var PHPParser = Editor.Parser = (function() { 54 | // Token types that can be considered to be atoms, part of operator expressions 55 | var atomicTypes = { 56 | "atom": true, "number": true, "variable": true, "string": true 57 | }; 58 | // Constructor for the lexical context objects. 59 | function PHPLexical(indented, column, type, align, prev, info) { 60 | // indentation at start of this line 61 | this.indented = indented; 62 | // column at which this scope was opened 63 | this.column = column; 64 | // type of scope ('stat' (statement), 'form' (special form), '[', '{', or '(') 65 | this.type = type; 66 | // '[', '{', or '(' blocks that have any text after their opening 67 | // character are said to be 'aligned' -- any lines below are 68 | // indented all the way to the opening character. 69 | if (align != null) 70 | this.align = align; 71 | // Parent scope, if any. 72 | this.prev = prev; 73 | this.info = info; 74 | }; 75 | 76 | // PHP indentation rules 77 | function indentPHP(lexical) { 78 | return function(firstChars) { 79 | var firstChar = firstChars && firstChars.charAt(0), type = lexical.type; 80 | var closing = firstChar == type; 81 | if (type == "form" && firstChar == "{") 82 | return lexical.indented; 83 | else if (type == "stat" || type == "form") 84 | return lexical.indented + indentUnit; 85 | else if (lexical.info == "switch" && !closing) 86 | return lexical.indented + (/^(?:case|default)\b/.test(firstChars) ? indentUnit : 2 * indentUnit); 87 | else if (lexical.align) 88 | return lexical.column - (closing ? 1 : 0); 89 | else 90 | return lexical.indented + (closing ? 0 : indentUnit); 91 | }; 92 | }; 93 | 94 | // The parser-iterator-producing function itself. 95 | function parsePHP(input, basecolumn) { 96 | // Wrap the input in a token stream 97 | var tokens = tokenizePHP(input); 98 | // The parser state. cc is a stack of actions that have to be 99 | // performed to finish the current statement. For example we might 100 | // know that we still need to find a closing parenthesis and a 101 | // semicolon. Actions at the end of the stack go first. It is 102 | // initialized with an infinitely looping action that consumes 103 | // whole statements. 104 | var cc = [statements]; 105 | // The lexical scope, used mostly for indentation. 106 | var lexical = new PHPLexical((basecolumn || 0) - indentUnit, 0, "block", false); 107 | // Current column, and the indentation at the start of the current 108 | // line. Used to create lexical scope objects. 109 | var column = 0; 110 | var indented = 0; 111 | // Variables which are used by the mark, cont, and pass functions 112 | // below to communicate with the driver loop in the 'next' function. 113 | var consume, marked; 114 | 115 | // The iterator object. 116 | var parser = {next: next, copy: copy}; 117 | 118 | // parsing is accomplished by calling next() repeatedly 119 | function next(){ 120 | // Start by performing any 'lexical' actions (adjusting the 121 | // lexical variable), or the operations below will be working 122 | // with the wrong lexical state. 123 | while(cc[cc.length - 1].lex) 124 | cc.pop()(); 125 | 126 | // Fetch the next token. 127 | var token = tokens.next(); 128 | 129 | // Adjust column and indented. 130 | if (token.type == "whitespace" && column == 0) 131 | indented = token.value.length; 132 | column += token.value.length; 133 | if (token.content == "\n"){ 134 | indented = column = 0; 135 | // If the lexical scope's align property is still undefined at 136 | // the end of the line, it is an un-aligned scope. 137 | if (!("align" in lexical)) 138 | lexical.align = false; 139 | // Newline tokens get an indentation function associated with 140 | // them. 141 | token.indentation = indentPHP(lexical); 142 | } 143 | // No more processing for meaningless tokens. 144 | if (token.type == "whitespace" || token.type == "comment" 145 | || token.type == "string_not_terminated" ) 146 | return token; 147 | // When a meaningful token is found and the lexical scope's 148 | // align is undefined, it is an aligned scope. 149 | if (!("align" in lexical)) 150 | lexical.align = true; 151 | 152 | // Execute actions until one 'consumes' the token and we can 153 | // return it. 'marked' is used to change the style of the current token. 154 | while(true) { 155 | consume = marked = false; 156 | // Take and execute the topmost action. 157 | var action = cc.pop(); 158 | action(token); 159 | 160 | if (consume){ 161 | if (marked) 162 | token.style = marked; 163 | // Here we differentiate between local and global variables. 164 | return token; 165 | } 166 | } 167 | return 1; // Firebug workaround for http://code.google.com/p/fbug/issues/detail?id=1239#c1 168 | } 169 | 170 | // This makes a copy of the parser state. It stores all the 171 | // stateful variables in a closure, and returns a function that 172 | // will restore them when called with a new input stream. Note 173 | // that the cc array has to be copied, because it is contantly 174 | // being modified. Lexical objects are not mutated, so they can 175 | // be shared between runs of the parser. 176 | function copy(){ 177 | var _lexical = lexical, _cc = cc.concat([]), _tokenState = tokens.state; 178 | 179 | return function copyParser(input){ 180 | lexical = _lexical; 181 | cc = _cc.concat([]); // copies the array 182 | column = indented = 0; 183 | tokens = tokenizePHP(input, _tokenState); 184 | return parser; 185 | }; 186 | } 187 | 188 | // Helper function for pushing a number of actions onto the cc 189 | // stack in reverse order. 190 | function push(fs){ 191 | for (var i = fs.length - 1; i >= 0; i--) 192 | cc.push(fs[i]); 193 | } 194 | // cont and pass are used by the action functions to add other 195 | // actions to the stack. cont will cause the current token to be 196 | // consumed, pass will leave it for the next action. 197 | function cont(){ 198 | push(arguments); 199 | consume = true; 200 | } 201 | function pass(){ 202 | push(arguments); 203 | consume = false; 204 | } 205 | // Used to change the style of the current token. 206 | function mark(style){ 207 | marked = style; 208 | } 209 | // Add a lyer of style to the current token, for example syntax-error 210 | function mark_add(style){ 211 | marked = marked + ' ' + style; 212 | } 213 | 214 | // Push a new lexical context of the given type. 215 | function pushlex(type, info) { 216 | var result = function pushlexing() { 217 | lexical = new PHPLexical(indented, column, type, null, lexical, info) 218 | }; 219 | result.lex = true; 220 | return result; 221 | } 222 | // Pop off the current lexical context. 223 | function poplex(){ 224 | lexical = lexical.prev; 225 | } 226 | poplex.lex = true; 227 | // The 'lex' flag on these actions is used by the 'next' function 228 | // to know they can (and have to) be ran before moving on to the 229 | // next token. 230 | 231 | // Creates an action that discards tokens until it finds one of 232 | // the given type. This will ignore (and recover from) syntax errors. 233 | function expect(wanted){ 234 | return function expecting(token){ 235 | if (token.type == wanted) cont(); // consume the token 236 | else { 237 | cont(arguments.callee); // continue expecting() - call itself 238 | } 239 | }; 240 | } 241 | 242 | // Require a specific token type, or one of the tokens passed in the 'wanted' array 243 | // Used to detect blatant syntax errors. 'execute' is used to pass extra code 244 | // to be executed if the token is matched. For example, a '(' match could 245 | // 'execute' a cont( compasep(funcarg), require(")") ) 246 | function require(wanted, execute){ 247 | return function requiring(token){ 248 | var ok; 249 | var type = token.type; 250 | if (typeof(wanted) == "string") 251 | ok = (type == wanted) -1; 252 | else 253 | ok = wanted.indexOf(type); 254 | if (ok >= 0) { 255 | if (execute && typeof(execute[ok]) == "function") 256 | execute[ok](token); 257 | cont(); // just consume the token 258 | } 259 | else { 260 | if (!marked) mark(token.style); 261 | mark_add("syntax-error"); 262 | cont(arguments.callee); 263 | } 264 | }; 265 | } 266 | 267 | // Looks for a statement, and then calls itself. 268 | function statements(token){ 269 | return pass(statement, statements); 270 | } 271 | // Dispatches various types of statements based on the type of the current token. 272 | function statement(token){ 273 | var type = token.type; 274 | if (type == "keyword a") cont(pushlex("form"), expression, statement, poplex); 275 | else if (type == "keyword b") cont(pushlex("form"), statement, poplex); 276 | else if (type == "{") cont(pushlex("}"), block, poplex); 277 | else if (type == "function") funcdef(); 278 | // technically, "class implode {...}" is correct, but we'll flag that as an error because it overrides a predefined function 279 | else if (type == "class") cont(require("t_string"), expect("{"), pushlex("}"), block, poplex); 280 | else if (type == "foreach") cont(pushlex("form"), require("("), pushlex(")"), expression, require("as"), require("variable"), /* => $value */ expect(")"), poplex, statement, poplex); 281 | else if (type == "for") cont(pushlex("form"), require("("), pushlex(")"), expression, require(";"), expression, require(";"), expression, require(")"), poplex, statement, poplex); 282 | // public final function foo(), protected static $bar; 283 | else if (type == "modifier") cont(require(["modifier", "variable", "function"], [null, null, funcdef])); 284 | else if (type == "switch") cont(pushlex("form"), require("("), expression, require(")"), pushlex("}", "switch"), require([":", "{"]), block, poplex, poplex); 285 | else if (type == "case") cont(expression, require(":")); 286 | else if (type == "default") cont(require(":")); 287 | else if (type == "catch") cont(pushlex("form"), require("("), require("t_string"), require("variable"), require(")"), statement, poplex); 288 | else if (type == "const") cont(require("t_string")); // 'const static x=5' is a syntax error 289 | // technically, "namespace implode {...}" is correct, but we'll flag that as an error because it overrides a predefined function 290 | else if (type == "namespace") cont(namespacedef, require(";")); 291 | // $variables may be followed by operators, () for variable function calls, or [] subscripts 292 | else pass(pushlex("stat"), expression, require(";"), poplex); 293 | } 294 | // Dispatch expression types. 295 | function expression(token){ 296 | var type = token.type; 297 | if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator); 298 | else if (type == "<<<") cont(require("string"), maybeoperator); // heredoc/nowdoc 299 | else if (type == "t_string") cont(maybe_double_colon, maybeoperator); 300 | else if (type == "keyword c") cont(expression); 301 | // function call or parenthesized expression: $a = ($b + 1) * 2; 302 | else if (type == "(") cont(pushlex(")"), commasep(expression), require(")"), poplex, maybeoperator); 303 | else if (type == "operator") cont(expression); 304 | } 305 | // Called for places where operators, function calls, or subscripts are 306 | // valid. Will skip on to the next action if none is found. 307 | function maybeoperator(token){ 308 | var type = token.type; 309 | if (type == "operator") { 310 | if (token.content == "?") cont(expression, require(":"), expression); // ternary operator 311 | else cont(expression); 312 | } 313 | else if (type == "(") cont(pushlex(")"), expression, commasep(expression), require(")"), poplex, maybeoperator /* $varfunc() + 3 */); 314 | else if (type == "[") cont(pushlex("]"), expression, require("]"), maybeoperator /* for multidimensional arrays, or $func[$i]() */, poplex); 315 | } 316 | // A regular use of the double colon to specify a class, as in self::func() or myclass::$var; 317 | // Differs from `namespace` or `use` in that only one class can be the parent; chains (A::B::$var) are a syntax error. 318 | function maybe_double_colon(token) { 319 | if (token.type == "t_double_colon") 320 | // A::$var, A::func(), A::const 321 | cont(require(["t_string", "variable"]), maybeoperator); 322 | else { 323 | // a t_string wasn't followed by ::, such as in a function call: foo() 324 | pass(expression) 325 | } 326 | } 327 | // the declaration or definition of a function 328 | function funcdef() { 329 | cont(require("t_string"), require("("), pushlex(")"), commasep(funcarg), require(")"), poplex, block); 330 | } 331 | // Parses a comma-separated list of the things that are recognized 332 | // by the 'what' argument. 333 | function commasep(what){ 334 | function proceed(token) { 335 | if (token.type == ",") cont(what, proceed); 336 | }; 337 | return function commaSeparated() { 338 | pass(what, proceed); 339 | }; 340 | } 341 | // Look for statements until a closing brace is found. 342 | function block(token) { 343 | if (token.type == "}") cont(); 344 | else pass(statement, block); 345 | } 346 | function maybedefaultparameter(token){ 347 | if (token.content == "=") cont(expression); 348 | } 349 | // support for default arguments: http://us.php.net/manual/en/functions.arguments.php#functions.arguments.default 350 | function funcarg(token){ 351 | // function foo(myclass $obj) {...} 352 | if (token.type == "t_string") cont(require("variable"), maybedefaultparameter); 353 | // function foo($string) {...} 354 | else if (token.type == "variable") cont(maybedefaultparameter); 355 | } 356 | 357 | // A namespace definition or use 358 | function maybe_double_colon_def(token) { 359 | if (token.type == "t_double_colon") 360 | cont(namespacedef); 361 | } 362 | function namespacedef(token) { 363 | pass(require("t_string"), maybe_double_colon_def); 364 | } 365 | 366 | return parser; 367 | } 368 | 369 | return {make: parsePHP, electricChars: "{}:"}; 370 | 371 | })(); 372 | -------------------------------------------------------------------------------- /site/static/contrib/php/js/parsephphtmlmixed.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2009 Yahoo! Inc. All rights reserved. 3 | The copyrights embodied in the content of this file are licensed by 4 | Yahoo! Inc. under the BSD (revised) open source license 5 | 6 | @author Dan Vlad Dascalescu 7 | 8 | Based on parsehtmlmixed.js by Marijn Haverbeke. 9 | */ 10 | 11 | var PHPHTMLMixedParser = Editor.Parser = (function() { 12 | if (!(PHPParser && CSSParser && JSParser && XMLParser)) 13 | throw new Error("PHP, CSS, JS, and XML parsers must be loaded for PHP+HTML mixed mode to work."); 14 | XMLParser.configure({useHTMLKludges: true}); 15 | 16 | function parseMixed(stream) { 17 | var htmlParser = XMLParser.make(stream), localParser = null, inTag = false; 18 | var iter = {next: top, copy: copy}; 19 | 20 | function top() { 21 | var token = htmlParser.next(); 22 | if (token.content == "<") 23 | inTag = true; 24 | else if (token.style == "xml-tagname" && inTag === true) 25 | inTag = token.content.toLowerCase(); 26 | else if (token.type == "xml-processing") { 27 | // dispatch on PHP 28 | if (token.content == ""); 30 | } 31 | // "xml-processing" tokens are ignored, because they should be handled by a specific local parser 32 | else if (token.content == ">") { 33 | if (inTag == "script") 34 | iter.next = local(JSParser, " 2 | 3 | 4 | CodeMirror: Python demonstration 5 | 18 | 19 | 20 |

21 | This is a simple demonstration of the Python syntax highlighting module 22 | for CodeMirror. 23 |

24 |

25 | Features of this parser include: 26 |

27 |
    28 |
  • Token-based syntax highlighting - currently very little lexical analysis happens. Few lexical errors will be detected.
  • 29 |
  • Use the normal indentation mode to enforce regular indentation, otherwise the "shift" indentation mode will give you more flexibility.
  • 30 |
  • Parser Options: 31 |
      32 |
    • pythonVersion (Integer) - 2 or 3 to indicate which version of Python to parse. Default = 2
    • 33 |
    • strictErrors (Bool) - true to highlight errors that may not be Python errors but cause confusion for this parser. Default = true
    • 34 |
    35 |
  • 36 |
37 |

Written by Timothy Farrell (license). Special 38 | thanks to Adam Brand and Marijn Haverbeke for their help in debugging 39 | and providing for this parser.

40 | 41 |
42 | 127 |
128 | 129 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /site/static/contrib/python/js/parsepython.js: -------------------------------------------------------------------------------- 1 | Editor.Parser = (function() { 2 | function wordRegexp(words) { 3 | return new RegExp("^(?:" + words.join("|") + ")$"); 4 | } 5 | var DELIMITERCLASS = 'py-delimiter'; 6 | var LITERALCLASS = 'py-literal'; 7 | var ERRORCLASS = 'py-error'; 8 | var OPERATORCLASS = 'py-operator'; 9 | var IDENTIFIERCLASS = 'py-identifier'; 10 | var STRINGCLASS = 'py-string'; 11 | var BYTESCLASS = 'py-bytes'; 12 | var UNICODECLASS = 'py-unicode'; 13 | var RAWCLASS = 'py-raw'; 14 | var NORMALCONTEXT = 'normal'; 15 | var STRINGCONTEXT = 'string'; 16 | var singleOperators = '+-*/%&|^~<>'; 17 | var doubleOperators = wordRegexp(['==', '!=', '\\<=', '\\>=', '\\<\\>', 18 | '\\<\\<', '\\>\\>', '\\/\\/', '\\*\\*']); 19 | var singleDelimiters = '()[]{}@,:.`=;'; 20 | var doubleDelimiters = ['\\+=', '\\-=', '\\*=', '/=', '%=', '&=', '\\|=', 21 | '\\^=']; 22 | var tripleDelimiters = wordRegexp(['//=','\\>\\>=','\\<\\<=','\\*\\*=']); 23 | var singleStarters = singleOperators + singleDelimiters + '=!'; 24 | var doubleStarters = '=<>*/'; 25 | var identifierStarters = /[_A-Za-z]/; 26 | 27 | var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']); 28 | var commonkeywords = ['as', 'assert', 'break', 'class', 'continue', 29 | 'def', 'del', 'elif', 'else', 'except', 'finally', 30 | 'for', 'from', 'global', 'if', 'import', 31 | 'lambda', 'pass', 'raise', 'return', 32 | 'try', 'while', 'with', 'yield']; 33 | var commontypes = ['bool', 'classmethod', 'complex', 'dict', 'enumerate', 34 | 'float', 'frozenset', 'int', 'list', 'object', 35 | 'property', 'reversed', 'set', 'slice', 'staticmethod', 36 | 'str', 'super', 'tuple', 'type']; 37 | var py2 = {'types': ['basestring', 'buffer', 'file', 'long', 'unicode', 38 | 'xrange'], 39 | 'keywords': ['exec', 'print'], 40 | 'version': 2 }; 41 | var py3 = {'types': ['bytearray', 'bytes', 'filter', 'map', 'memoryview', 42 | 'open', 'range', 'zip'], 43 | 'keywords': ['nonlocal'], 44 | 'version': 3}; 45 | 46 | var py, keywords, types, stringStarters, stringTypes, config; 47 | 48 | function configure(conf) { 49 | if (!conf.hasOwnProperty('pythonVersion')) { 50 | conf.pythonVersion = 2; 51 | } 52 | if (!conf.hasOwnProperty('strictErrors')) { 53 | conf.strictErrors = true; 54 | } 55 | if (conf.pythonVersion != 2 && conf.pythonVersion != 3) { 56 | alert('CodeMirror: Unknown Python Version "' + 57 | conf.pythonVersion + 58 | '", defaulting to Python 2.x.'); 59 | conf.pythonVersion = 2; 60 | } 61 | if (conf.pythonVersion == 3) { 62 | py = py3; 63 | stringStarters = /['"rbRB]/; 64 | stringTypes = /[rb]/; 65 | doubleDelimiters.push('\\-\\>'); 66 | } else { 67 | py = py2; 68 | stringStarters = /['"RUru]/; 69 | stringTypes = /[ru]/; 70 | } 71 | config = conf; 72 | keywords = wordRegexp(commonkeywords.concat(py.keywords)); 73 | types = wordRegexp(commontypes.concat(py.types)); 74 | doubleDelimiters = wordRegexp(doubleDelimiters); 75 | } 76 | 77 | var tokenizePython = (function() { 78 | function normal(source, setState) { 79 | var stringDelim, threeStr, temp, type, word, possible = {}; 80 | var ch = source.next(); 81 | 82 | function filterPossible(token, styleIfPossible) { 83 | if (!possible.style && !possible.content) { 84 | return token; 85 | } else if (typeof(token) == STRINGCONTEXT) { 86 | token = {content: source.get(), style: token}; 87 | } 88 | if (possible.style || styleIfPossible) { 89 | token.style = styleIfPossible ? styleIfPossible : possible.style; 90 | } 91 | if (possible.content) { 92 | token.content = possible.content + token.content; 93 | } 94 | possible = {}; 95 | return token; 96 | } 97 | 98 | // Handle comments 99 | if (ch == '#') { 100 | while (!source.endOfLine()) { 101 | source.next(); 102 | } 103 | return 'py-comment'; 104 | } 105 | // Handle special chars 106 | if (ch == '\\') { 107 | if (source.peek() != '\n') { 108 | var whitespace = true; 109 | while (!source.endOfLine()) { 110 | if(!(/\s/.test(source.next()))) { 111 | whitespace = false; 112 | } 113 | } 114 | if (!whitespace) { 115 | return ERRORCLASS; 116 | } 117 | } 118 | return 'py-special'; 119 | } 120 | // Handle operators and delimiters 121 | if (singleStarters.indexOf(ch) != -1) { 122 | if (doubleStarters.indexOf(source.peek()) != -1) { 123 | temp = ch + source.peek(); 124 | // It must be a double delimiter or operator or triple delimiter 125 | if (doubleOperators.test(temp)) { 126 | source.next(); 127 | if (tripleDelimiters.test(temp + source.peek())) { 128 | source.next(); 129 | return DELIMITERCLASS; 130 | } else { 131 | return OPERATORCLASS; 132 | } 133 | } else if (doubleDelimiters.test(temp)) { 134 | source.next(); 135 | return DELIMITERCLASS; 136 | } 137 | } 138 | // It must be a single delimiter or operator 139 | if (singleOperators.indexOf(ch) != -1) { 140 | return OPERATORCLASS; 141 | } else if (singleDelimiters.indexOf(ch) != -1) { 142 | if (ch == '@' && /\w/.test(source.peek())) { 143 | possible = {style:'py-decorator', 144 | content: source.get()}; 145 | ch = source.next(); 146 | } else if (ch == '.' && /\d/.test(source.peek())) { 147 | possible = {style:LITERALCLASS, 148 | content: source.get()}; 149 | ch = source.next(); 150 | } else { 151 | return DELIMITERCLASS; 152 | } 153 | } else { 154 | return ERRORCLASS; 155 | } 156 | } 157 | // Handle number literals 158 | if (/\d/.test(ch)) { 159 | if (ch === '0' && !source.endOfLine()) { 160 | switch (source.peek()) { 161 | case 'o': 162 | case 'O': 163 | source.next(); 164 | source.nextWhile(matcher(/[0-7]/)); 165 | return filterPossible(LITERALCLASS, ERRORCLASS); 166 | case 'x': 167 | case 'X': 168 | source.next(); 169 | source.nextWhile(matcher(/[0-9A-Fa-f]/)); 170 | return filterPossible(LITERALCLASS, ERRORCLASS); 171 | case 'b': 172 | case 'B': 173 | source.next(); 174 | source.nextWhile(matcher(/[01]/)); 175 | return filterPossible(LITERALCLASS, ERRORCLASS); 176 | } 177 | } 178 | source.nextWhile(matcher(/\d/)); 179 | if (source.peek() == '.') { 180 | source.next(); 181 | source.nextWhile(matcher(/\d/)); 182 | } 183 | // Grab an exponent 184 | if (source.peek().toLowerCase() == 'e') { 185 | source.next(); 186 | if (source.peek() == '+' || source.peek() == '-') { 187 | source.next(); 188 | } 189 | if (/\d/.test(source.peek())) { 190 | source.nextWhile(matcher(/\d/)); 191 | } else { 192 | return filterPossible(ERRORCLASS); 193 | } 194 | } 195 | // Grab a complex number 196 | if (source.peek().toLowerCase() == 'j') { 197 | source.next(); 198 | } 199 | 200 | return filterPossible(LITERALCLASS); 201 | } 202 | // Handle strings 203 | if (stringStarters.test(ch)) { 204 | var peek = source.peek(); 205 | var stringType = STRINGCLASS; 206 | if ((stringTypes.test(ch)) && (peek == '"' || peek == "'")) { 207 | switch (ch.toLowerCase()) { 208 | case 'b': 209 | stringType = BYTESCLASS; 210 | break; 211 | case 'r': 212 | stringType = RAWCLASS; 213 | break; 214 | case 'u': 215 | stringType = UNICODECLASS; 216 | break; 217 | } 218 | ch = source.next(); 219 | stringDelim = ch; 220 | if (source.peek() != stringDelim) { 221 | setState(inString(stringType, stringDelim)); 222 | return null; 223 | } else { 224 | source.next(); 225 | if (source.peek() == stringDelim) { 226 | source.next(); 227 | threeStr = stringDelim + stringDelim + stringDelim; 228 | setState(inString(stringType, threeStr)); 229 | return null; 230 | } else { 231 | return stringType; 232 | } 233 | } 234 | } else if (ch == "'" || ch == '"') { 235 | stringDelim = ch; 236 | if (source.peek() != stringDelim) { 237 | setState(inString(stringType, stringDelim)); 238 | return null; 239 | } else { 240 | source.next(); 241 | if (source.peek() == stringDelim) { 242 | source.next(); 243 | threeStr = stringDelim + stringDelim + stringDelim; 244 | setState(inString(stringType, threeStr)); 245 | return null; 246 | } else { 247 | return stringType; 248 | } 249 | } 250 | } 251 | } 252 | // Handle Identifier 253 | if (identifierStarters.test(ch)) { 254 | source.nextWhile(matcher(/[\w\d]/)); 255 | word = source.get(); 256 | if (wordOperators.test(word)) { 257 | type = OPERATORCLASS; 258 | } else if (keywords.test(word)) { 259 | type = 'py-keyword'; 260 | } else if (types.test(word)) { 261 | type = 'py-type'; 262 | } else { 263 | type = IDENTIFIERCLASS; 264 | while (source.peek() == '.') { 265 | source.next(); 266 | if (identifierStarters.test(source.peek())) { 267 | source.nextWhile(matcher(/[\w\d]/)); 268 | } else { 269 | type = ERRORCLASS; 270 | break; 271 | } 272 | } 273 | word = word + source.get(); 274 | } 275 | return filterPossible({style: type, content: word}); 276 | } 277 | 278 | // Register Dollar sign and Question mark as errors. Always! 279 | if (/\$\?/.test(ch)) { 280 | return filterPossible(ERRORCLASS); 281 | } 282 | 283 | return filterPossible(ERRORCLASS); 284 | } 285 | 286 | function inString(style, terminator) { 287 | return function(source, setState) { 288 | var matches = []; 289 | var found = false; 290 | while (!found && !source.endOfLine()) { 291 | var ch = source.next(), newMatches = []; 292 | // Skip escaped characters 293 | if (ch == '\\') { 294 | if (source.peek() == '\n') { 295 | break; 296 | } 297 | ch = source.next(); 298 | ch = source.next(); 299 | } 300 | if (ch == terminator.charAt(0)) { 301 | matches.push(terminator); 302 | } 303 | for (var i = 0; i < matches.length; i++) { 304 | var match = matches[i]; 305 | if (match.charAt(0) == ch) { 306 | if (match.length == 1) { 307 | setState(normal); 308 | found = true; 309 | break; 310 | } else { 311 | newMatches.push(match.slice(1)); 312 | } 313 | } 314 | } 315 | matches = newMatches; 316 | } 317 | return style; 318 | }; 319 | } 320 | 321 | return function(source, startState) { 322 | return tokenizer(source, startState || normal); 323 | }; 324 | })(); 325 | 326 | function parsePython(source) { 327 | if (!keywords) { 328 | configure({}); 329 | } 330 | 331 | var tokens = tokenizePython(source); 332 | var lastToken = null; 333 | var column = 0; 334 | var context = {prev: null, 335 | endOfScope: false, 336 | startNewScope: false, 337 | level: 0, 338 | next: null, 339 | type: NORMALCONTEXT 340 | }; 341 | 342 | function pushContext(level, type) { 343 | type = type ? type : NORMALCONTEXT; 344 | context = {prev: context, 345 | endOfScope: false, 346 | startNewScope: false, 347 | level: level, 348 | next: null, 349 | type: type 350 | }; 351 | } 352 | 353 | function popContext(remove) { 354 | remove = remove ? remove : false; 355 | if (context.prev) { 356 | if (remove) { 357 | context = context.prev; 358 | context.next = null; 359 | } else { 360 | context.prev.next = context; 361 | context = context.prev; 362 | } 363 | } 364 | } 365 | 366 | function indentPython(context) { 367 | var temp; 368 | return function(nextChars, currentLevel, direction) { 369 | if (direction === null || direction === undefined) { 370 | if (nextChars) { 371 | while (context.next) { 372 | context = context.next; 373 | } 374 | } 375 | return context.level; 376 | } 377 | else if (direction === true) { 378 | if (currentLevel == context.level) { 379 | if (context.next) { 380 | return context.next.level; 381 | } else { 382 | return context.level; 383 | } 384 | } else { 385 | temp = context; 386 | while (temp.prev && temp.prev.level > currentLevel) { 387 | temp = temp.prev; 388 | } 389 | return temp.level; 390 | } 391 | } else if (direction === false) { 392 | if (currentLevel > context.level) { 393 | return context.level; 394 | } else if (context.prev) { 395 | temp = context; 396 | while (temp.prev && temp.prev.level >= currentLevel) { 397 | temp = temp.prev; 398 | } 399 | if (temp.prev) { 400 | return temp.prev.level; 401 | } else { 402 | return temp.level; 403 | } 404 | } 405 | } 406 | return context.level; 407 | }; 408 | } 409 | 410 | var iter = { 411 | next: function() { 412 | var token = tokens.next(); 413 | var type = token.style; 414 | var content = token.content; 415 | 416 | if (lastToken) { 417 | if (lastToken.content == 'def' && type == IDENTIFIERCLASS) { 418 | token.style = 'py-func'; 419 | } 420 | if (lastToken.content == '\n') { 421 | var tempCtx = context; 422 | // Check for a different scope 423 | if (type == 'whitespace' && context.type == NORMALCONTEXT) { 424 | if (token.value.length < context.level) { 425 | while (token.value.length < context.level) { 426 | popContext(); 427 | } 428 | 429 | if (token.value.length != context.level) { 430 | context = tempCtx; 431 | if (config.strictErrors) { 432 | token.style = ERRORCLASS; 433 | } 434 | } else { 435 | context.next = null; 436 | } 437 | } 438 | } else if (context.level !== 0 && 439 | context.type == NORMALCONTEXT) { 440 | while (0 !== context.level) { 441 | popContext(); 442 | } 443 | 444 | if (context.level !== 0) { 445 | context = tempCtx; 446 | if (config.strictErrors) { 447 | token.style = ERRORCLASS; 448 | } 449 | } 450 | } 451 | } 452 | } 453 | 454 | // Handle Scope Changes 455 | switch(type) { 456 | case STRINGCLASS: 457 | case BYTESCLASS: 458 | case RAWCLASS: 459 | case UNICODECLASS: 460 | if (context.type !== STRINGCONTEXT) { 461 | pushContext(context.level + 1, STRINGCONTEXT); 462 | } 463 | break; 464 | default: 465 | if (context.type === STRINGCONTEXT) { 466 | popContext(true); 467 | } 468 | break; 469 | } 470 | switch(content) { 471 | case '.': 472 | case '@': 473 | // These delimiters don't appear by themselves 474 | if (content !== token.value) { 475 | token.style = ERRORCLASS; 476 | } 477 | break; 478 | case ':': 479 | // Colons only delimit scope inside a normal scope 480 | if (context.type === NORMALCONTEXT) { 481 | context.startNewScope = context.level+indentUnit; 482 | } 483 | break; 484 | case '(': 485 | case '[': 486 | case '{': 487 | // These start a sequence scope 488 | pushContext(column + content.length, 'sequence'); 489 | break; 490 | case ')': 491 | case ']': 492 | case '}': 493 | // These end a sequence scope 494 | popContext(true); 495 | break; 496 | case 'pass': 497 | case 'return': 498 | // These end a normal scope 499 | if (context.type === NORMALCONTEXT) { 500 | context.endOfScope = true; 501 | } 502 | break; 503 | case '\n': 504 | // Reset our column 505 | column = 0; 506 | // Make any scope changes 507 | if (context.endOfScope) { 508 | context.endOfScope = false; 509 | popContext(); 510 | } else if (context.startNewScope !== false) { 511 | var temp = context.startNewScope; 512 | context.startNewScope = false; 513 | pushContext(temp, NORMALCONTEXT); 514 | } 515 | // Newlines require an indentation function wrapped in a closure for proper context. 516 | token.indentation = indentPython(context); 517 | break; 518 | } 519 | 520 | // Keep track of current column for certain scopes. 521 | if (content != '\n') { 522 | column += token.value.length; 523 | } 524 | 525 | lastToken = token; 526 | return token; 527 | }, 528 | 529 | copy: function() { 530 | var _context = context, _tokenState = tokens.state; 531 | return function(source) { 532 | tokens = tokenizePython(source, _tokenState); 533 | context = _context; 534 | return iter; 535 | }; 536 | } 537 | }; 538 | return iter; 539 | } 540 | 541 | return {make: parsePython, 542 | electricChars: "", 543 | configure: configure}; 544 | })(); 545 | -------------------------------------------------------------------------------- /site/static/css/csscolors.css: -------------------------------------------------------------------------------- 1 | .editbox { 2 | margin: .4em; 3 | padding: 0; 4 | font-family: monospace; 5 | font-size: 10pt; 6 | color: black; 7 | } 8 | 9 | pre.code, .editbox { 10 | color: #666666; 11 | } 12 | 13 | .editbox p { 14 | margin: 0; 15 | } 16 | 17 | span.css-at { 18 | color: #770088; 19 | } 20 | 21 | span.css-unit { 22 | color: #228811; 23 | } 24 | 25 | span.css-value { 26 | color: #770088; 27 | } 28 | 29 | span.css-identifier { 30 | color: black; 31 | } 32 | 33 | span.css-important { 34 | color: #0000FF; 35 | } 36 | 37 | span.css-colorcode { 38 | color: #004499; 39 | } 40 | 41 | span.css-comment { 42 | color: #AA7700; 43 | } 44 | 45 | span.css-string { 46 | color: #AA2222; 47 | } 48 | -------------------------------------------------------------------------------- /site/static/css/docs.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 3em 6em; 4 | color: black; 5 | max-width: 50em; 6 | } 7 | 8 | h1 { 9 | font-size: 22pt; 10 | } 11 | 12 | .underline { 13 | border-bottom: 3px solid #C44; 14 | } 15 | 16 | h2 { 17 | font-size: 14pt; 18 | } 19 | 20 | p.rel { 21 | padding-left: 2em; 22 | text-indent: -2em; 23 | } 24 | 25 | div.border { 26 | border: 1px solid black; 27 | padding: 3px; 28 | } 29 | 30 | code { 31 | font-family: courier, monospace; 32 | font-size: 90%; 33 | color: #144; 34 | } 35 | 36 | pre.code { 37 | margin: 1.1em 12px; 38 | border: 1px solid #CCCCCC; 39 | color: black; 40 | padding: .4em; 41 | font-family: courier, monospace; 42 | } 43 | 44 | .warn { 45 | color: #C00; 46 | } 47 | -------------------------------------------------------------------------------- /site/static/css/jscolors.css: -------------------------------------------------------------------------------- 1 | .editbox { 2 | margin: .4em; 3 | padding: 0; 4 | font-family: monospace; 5 | font-size: 10pt; 6 | color: black; 7 | } 8 | 9 | pre.code, .editbox { 10 | color: #666666; 11 | } 12 | 13 | .editbox p { 14 | margin: 0; 15 | } 16 | 17 | span.js-punctuation { 18 | color: #666666; 19 | } 20 | 21 | span.js-operator { 22 | color: #666666; 23 | } 24 | 25 | span.js-keyword { 26 | color: #770088; 27 | } 28 | 29 | span.js-atom { 30 | color: #228811; 31 | } 32 | 33 | span.js-variable { 34 | color: black; 35 | } 36 | 37 | span.js-variabledef { 38 | color: #0000FF; 39 | } 40 | 41 | span.js-localvariable { 42 | color: #004499; 43 | } 44 | 45 | span.js-property { 46 | color: black; 47 | } 48 | 49 | span.js-comment { 50 | color: #AA7700; 51 | } 52 | 53 | span.js-string { 54 | color: #AA2222; 55 | } 56 | -------------------------------------------------------------------------------- /site/static/css/people.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progrium/scriptlets/ea59e09cc7bd07ee72102e0266662e1770e9af64/site/static/css/people.jpg -------------------------------------------------------------------------------- /site/static/css/sparqlcolors.css: -------------------------------------------------------------------------------- 1 | .editbox { 2 | margin: .4em; 3 | padding: 0; 4 | font-family: monospace; 5 | font-size: 10pt; 6 | color: black; 7 | } 8 | 9 | .editbox p { 10 | margin: 0; 11 | } 12 | 13 | span.sp-keyword { 14 | color: #708; 15 | } 16 | 17 | span.sp-prefixed { 18 | color: #5d1; 19 | } 20 | 21 | span.sp-var { 22 | color: #00c; 23 | } 24 | 25 | span.sp-comment { 26 | color: #a70; 27 | } 28 | 29 | span.sp-literal { 30 | color: #a22; 31 | } 32 | 33 | span.sp-uri { 34 | color: #292; 35 | } 36 | 37 | span.sp-operator { 38 | color: #088; 39 | } 40 | -------------------------------------------------------------------------------- /site/static/css/xmlcolors.css: -------------------------------------------------------------------------------- 1 | .editbox { 2 | margin: .4em; 3 | padding: 0; 4 | font-family: monospace; 5 | font-size: 10pt; 6 | color: black; 7 | } 8 | 9 | .editbox p { 10 | margin: 0; 11 | } 12 | 13 | span.xml-tagname { 14 | color: #A0B; 15 | } 16 | 17 | span.xml-attribute { 18 | color: #281; 19 | } 20 | 21 | span.xml-punctuation { 22 | color: black; 23 | } 24 | 25 | span.xml-attname { 26 | color: #00F; 27 | } 28 | 29 | span.xml-comment { 30 | color: #A70; 31 | } 32 | 33 | span.xml-cdata { 34 | color: #48A; 35 | } 36 | 37 | span.xml-processing { 38 | color: #999; 39 | } 40 | 41 | span.xml-entity { 42 | color: #A22; 43 | } 44 | 45 | span.xml-error { 46 | color: #F00; 47 | } 48 | 49 | span.xml-text { 50 | color: black; 51 | } 52 | -------------------------------------------------------------------------------- /site/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/progrium/scriptlets/ea59e09cc7bd07ee72102e0266662e1770e9af64/site/static/favicon.ico -------------------------------------------------------------------------------- /site/static/js/codemirror.js: -------------------------------------------------------------------------------- 1 | /* CodeMirror main module 2 | * 3 | * Implements the CodeMirror constructor and prototype, which take care 4 | * of initializing the editor frame, and providing the outside interface. 5 | */ 6 | 7 | // The CodeMirrorConfig object is used to specify a default 8 | // configuration. If you specify such an object before loading this 9 | // file, the values you put into it will override the defaults given 10 | // below. You can also assign to it after loading. 11 | var CodeMirrorConfig = window.CodeMirrorConfig || {}; 12 | 13 | var CodeMirror = (function(){ 14 | function setDefaults(object, defaults) { 15 | for (var option in defaults) { 16 | if (!object.hasOwnProperty(option)) 17 | object[option] = defaults[option]; 18 | } 19 | } 20 | function forEach(array, action) { 21 | for (var i = 0; i < array.length; i++) 22 | action(array[i]); 23 | } 24 | 25 | // These default options can be overridden by passing a set of 26 | // options to a specific CodeMirror constructor. See manual.html for 27 | // their meaning. 28 | setDefaults(CodeMirrorConfig, { 29 | stylesheet: "", 30 | path: "", 31 | parserfile: [], 32 | basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"], 33 | linesPerPass: 30, 34 | passDelay: 200, 35 | continuousScanning: false, 36 | saveFunction: null, 37 | onChange: null, 38 | undoDepth: 50, 39 | undoDelay: 800, 40 | disableSpellcheck: true, 41 | textWrapping: true, 42 | readOnly: false, 43 | width: "100%", 44 | height: "300px", 45 | autoMatchParens: false, 46 | parserConfig: null, 47 | tabMode: "indent", // or "spaces", "default", "shift" 48 | activeTokens: null, 49 | cursorActivity: null, 50 | lineNumbers: false, 51 | indentUnit: 2 52 | }); 53 | 54 | function wrapLineNumberDiv(place) { 55 | return function(node) { 56 | var container = document.createElement("DIV"), 57 | nums = document.createElement("DIV"), 58 | scroller = document.createElement("DIV"); 59 | container.style.position = "relative"; 60 | nums.style.position = "absolute"; 61 | nums.style.height = "100%"; 62 | if (nums.style.setExpression) { 63 | try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");} 64 | catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions 65 | } 66 | nums.style.top = "0px"; 67 | nums.style.overflow = "hidden"; 68 | place(container); 69 | container.appendChild(node); 70 | container.appendChild(nums); 71 | scroller.className = "CodeMirror-line-numbers"; 72 | nums.appendChild(scroller); 73 | } 74 | } 75 | 76 | function applyLineNumbers(frame) { 77 | var win = frame.contentWindow, doc = win.document, 78 | nums = frame.nextSibling, scroller = nums.firstChild; 79 | 80 | var nextNum = 1, barWidth = null; 81 | function sizeBar() { 82 | if (nums.offsetWidth != barWidth) { 83 | barWidth = nums.offsetWidth; 84 | nums.style.left = "-" + (frame.parentNode.style.marginLeft = barWidth + "px"); 85 | } 86 | } 87 | function update() { 88 | var diff = 20 + Math.max(doc.body.offsetHeight, frame.offsetHeight) - scroller.offsetHeight; 89 | for (var n = Math.ceil(diff / 10); n > 0; n--) { 90 | var div = document.createElement("DIV"); 91 | div.appendChild(document.createTextNode(nextNum++)); 92 | scroller.appendChild(div); 93 | } 94 | nums.scrollTop = doc.body.scrollTop || doc.documentElement.scrollTop || 0; 95 | } 96 | sizeBar(); 97 | update(); 98 | win.addEventHandler(win, "scroll", update); 99 | setInterval(sizeBar, 500); 100 | } 101 | 102 | function CodeMirror(place, options) { 103 | // Backward compatibility for deprecated options. 104 | if (options.dumbTabs) options.tabMode = "spaces"; 105 | else if (options.normalTab) options.tabMode = "default"; 106 | 107 | // Use passed options, if any, to override defaults. 108 | this.options = options = options || {}; 109 | setDefaults(options, CodeMirrorConfig); 110 | 111 | var frame = this.frame = document.createElement("IFRAME"); 112 | frame.frameBorder = 0; 113 | frame.src = "javascript:false;"; 114 | frame.style.border = "0"; 115 | frame.style.width = options.width; 116 | frame.style.height = options.height; 117 | // display: block occasionally suppresses some Firefox bugs, so we 118 | // always add it, redundant as it sounds. 119 | frame.style.display = "block"; 120 | 121 | if (place.appendChild) { 122 | var node = place; 123 | place = function(n){node.appendChild(n);}; 124 | } 125 | if (options.lineNumbers) place = wrapLineNumberDiv(place); 126 | place(frame); 127 | 128 | // Link back to this object, so that the editor can fetch options 129 | // and add a reference to itself. 130 | frame.CodeMirror = this; 131 | this.win = frame.contentWindow; 132 | 133 | if (typeof options.parserfile == "string") 134 | options.parserfile = [options.parserfile]; 135 | if (typeof options.stylesheet == "string") 136 | options.stylesheet = [options.stylesheet]; 137 | 138 | var html = [""]; 139 | forEach(options.stylesheet, function(file) { 140 | html.push(""); 141 | }); 142 | forEach(options.basefiles.concat(options.parserfile), function(file) { 143 | html.push(""); 144 | }); 145 | html.push(""); 147 | 148 | var doc = this.win.document; 149 | doc.open(); 150 | doc.write(html.join("")); 151 | doc.close(); 152 | } 153 | 154 | CodeMirror.prototype = { 155 | init: function() { 156 | if (this.options.initCallback) this.options.initCallback(this); 157 | if (this.options.lineNumbers) applyLineNumbers(this.frame); 158 | }, 159 | 160 | getCode: function() {return this.editor.getCode();}, 161 | setCode: function(code) {this.editor.importCode(code);}, 162 | selection: function() {return this.editor.selectedText();}, 163 | reindent: function() {this.editor.reindent();}, 164 | reindentSelection: function() {this.editor.reindentSelection(null);}, 165 | 166 | focus: function() { 167 | this.win.focus(); 168 | if (this.editor.selectionSnapshot) // IE hack 169 | this.win.select.selectCoords(this.win, this.editor.selectionSnapshot); 170 | }, 171 | replaceSelection: function(text) { 172 | this.focus(); 173 | this.editor.replaceSelection(text); 174 | return true; 175 | }, 176 | replaceChars: function(text, start, end) { 177 | this.editor.replaceChars(text, start, end); 178 | }, 179 | getSearchCursor: function(string, fromCursor) { 180 | return this.editor.getSearchCursor(string, fromCursor); 181 | }, 182 | 183 | undo: function() {this.editor.history.undo();}, 184 | redo: function() {this.editor.history.redo();}, 185 | historySize: function() {return this.editor.history.historySize();}, 186 | clearHistory: function() {this.editor.history.clear();}, 187 | 188 | grabKeys: function(callback, filter) {this.editor.grabKeys(callback, filter);}, 189 | ungrabKeys: function() {this.editor.ungrabKeys();}, 190 | 191 | cursorPosition: function(start) { 192 | if (this.win.select.ie_selection) this.focus(); 193 | return this.editor.cursorPosition(start); 194 | }, 195 | firstLine: function() {return this.editor.firstLine();}, 196 | lastLine: function() {return this.editor.lastLine();}, 197 | nextLine: function(line) {return this.editor.nextLine(line);}, 198 | prevLine: function(line) {return this.editor.prevLine(line);}, 199 | lineContent: function(line) {return this.editor.lineContent(line);}, 200 | setLineContent: function(line, content) {this.editor.setLineContent(line, content);}, 201 | insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);}, 202 | selectLines: function(startLine, startOffset, endLine, endOffset) { 203 | this.win.focus(); 204 | this.editor.selectLines(startLine, startOffset, endLine, endOffset); 205 | }, 206 | nthLine: function(n) { 207 | var line = this.firstLine(); 208 | for (; n > 1 && line !== false; n--) 209 | line = this.nextLine(line); 210 | return line; 211 | }, 212 | lineNumber: function(line) { 213 | var num = 0; 214 | while (line !== false) { 215 | num++; 216 | line = this.prevLine(line); 217 | } 218 | return num; 219 | }, 220 | 221 | // Old number-based line interface 222 | jumpToLine: function(n) { 223 | this.selectLines(this.nthLine(n), 0); 224 | this.win.focus(); 225 | }, 226 | currentLine: function() { 227 | return this.lineNumber(this.cursorPosition().line); 228 | } 229 | }; 230 | 231 | CodeMirror.InvalidLineHandle = {toString: function(){return "CodeMirror.InvalidLineHandle";}}; 232 | 233 | CodeMirror.replace = function(element) { 234 | if (typeof element == "string") 235 | element = document.getElementById(element); 236 | return function(newElement) { 237 | element.parentNode.replaceChild(newElement, element); 238 | }; 239 | }; 240 | 241 | CodeMirror.fromTextArea = function(area, options) { 242 | if (typeof area == "string") 243 | area = document.getElementById(area); 244 | 245 | options = options || {}; 246 | if (area.style.width && options.width == null) 247 | options.width = area.style.width; 248 | if (area.style.height && options.height == null) 249 | options.height = area.style.height; 250 | if (options.content == null) options.content = area.value; 251 | 252 | if (area.form) { 253 | function updateField() { 254 | area.value = mirror.getCode(); 255 | } 256 | if (typeof area.form.addEventListener == "function") 257 | area.form.addEventListener("submit", updateField, false); 258 | else 259 | area.form.attachEvent("onsubmit", updateField); 260 | } 261 | 262 | function insert(frame) { 263 | if (area.nextSibling) 264 | area.parentNode.insertBefore(frame, area.nextSibling); 265 | else 266 | area.parentNode.appendChild(frame); 267 | } 268 | 269 | area.style.display = "none"; 270 | var mirror = new CodeMirror(insert, options); 271 | return mirror; 272 | }; 273 | 274 | CodeMirror.isProbablySupported = function() { 275 | // This is rather awful, but can be useful. 276 | var match; 277 | if (window.opera) 278 | return Number(window.opera.version()) >= 9.52; 279 | else if (/Apple Computers, Inc/.test(navigator.vendor) && (match = navigator.userAgent.match(/Version\/(\d+(?:\.\d+)?)\./))) 280 | return Number(match[1]) >= 3; 281 | else if (document.selection && window.ActiveXObject && (match = navigator.userAgent.match(/MSIE (\d+(?:\.\d*)?)\b/))) 282 | return Number(match[1]) >= 6; 283 | else if (match = navigator.userAgent.match(/gecko\/(\d{8})/i)) 284 | return Number(match[1]) >= 20050901; 285 | else if (match = navigator.userAgent.match(/AppleWebKit\/(\d+)/)) 286 | return Number(match[1]) >= 525; 287 | else 288 | return null; 289 | }; 290 | 291 | return CodeMirror; 292 | })(); 293 | -------------------------------------------------------------------------------- /site/static/js/mirrorframe.js: -------------------------------------------------------------------------------- 1 | /* Demonstration of embedding CodeMirror in a bigger application. The 2 | * interface defined here is a mess of prompts and confirms, and 3 | * should probably not be used in a real project. 4 | */ 5 | 6 | function MirrorFrame(place, options) { 7 | this.home = document.createElement("DIV"); 8 | if (place.appendChild) 9 | place.appendChild(this.home); 10 | else 11 | place(this.home); 12 | 13 | var self = this; 14 | function makeButton(name, action) { 15 | var button = document.createElement("INPUT"); 16 | button.type = "button"; 17 | button.value = name; 18 | self.home.appendChild(button); 19 | button.onclick = function(){self[action].call(self);}; 20 | } 21 | 22 | makeButton("Search", "search"); 23 | makeButton("Replace", "replace"); 24 | makeButton("Current line", "line"); 25 | makeButton("Jump to line", "jump"); 26 | makeButton("Insert constructor", "macro"); 27 | makeButton("Indent all", "reindent"); 28 | 29 | this.mirror = new CodeMirror(this.home, options); 30 | } 31 | 32 | MirrorFrame.prototype = { 33 | search: function() { 34 | var text = prompt("Enter search term:", ""); 35 | if (!text) return; 36 | 37 | var first = true; 38 | do { 39 | var cursor = this.mirror.getSearchCursor(text, first); 40 | first = false; 41 | while (cursor.findNext()) { 42 | cursor.select(); 43 | if (!confirm("Search again?")) 44 | return; 45 | } 46 | } while (confirm("End of document reached. Start over?")); 47 | }, 48 | 49 | replace: function() { 50 | // This is a replace-all, but it is possible to implement a 51 | // prompting replace. 52 | var from = prompt("Enter search string:", ""), to; 53 | if (from) to = prompt("What should it be replaced with?", ""); 54 | if (to == null) return; 55 | 56 | var cursor = this.mirror.getSearchCursor(from, false); 57 | while (cursor.findNext()) 58 | cursor.replace(to); 59 | }, 60 | 61 | jump: function() { 62 | var line = prompt("Jump to line:", ""); 63 | if (line && !isNaN(Number(line))) 64 | this.mirror.jumpToLine(Number(line)); 65 | }, 66 | 67 | line: function() { 68 | alert("The cursor is currently at line " + this.mirror.currentLine()); 69 | this.mirror.focus(); 70 | }, 71 | 72 | macro: function() { 73 | var name = prompt("Name your constructor:", ""); 74 | if (name) 75 | this.mirror.replaceSelection("function " + name + "() {\n \n}\n\n" + name + ".prototype = {\n \n};\n"); 76 | }, 77 | 78 | reindent: function() { 79 | this.mirror.reindent(); 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /site/static/js/parsecss.js: -------------------------------------------------------------------------------- 1 | /* Simple parser for CSS */ 2 | 3 | var CSSParser = Editor.Parser = (function() { 4 | var tokenizeCSS = (function() { 5 | function normal(source, setState) { 6 | var ch = source.next(); 7 | if (ch == "@") { 8 | source.nextWhile(matcher(/\w/)); 9 | return "css-at"; 10 | } 11 | else if (ch == "/" && source.equals("*")) { 12 | setState(inCComment); 13 | return null; 14 | } 15 | else if (ch == "<" && source.equals("!")) { 16 | setState(inSGMLComment); 17 | return null; 18 | } 19 | else if (ch == "=") { 20 | return "css-compare"; 21 | } 22 | else if (source.equals("=") && (ch == "~" || ch == "|")) { 23 | source.next(); 24 | return "css-compare"; 25 | } 26 | else if (ch == "\"" || ch == "'") { 27 | setState(inString(ch)); 28 | return null; 29 | } 30 | else if (ch == "#") { 31 | source.nextWhile(matcher(/\w/)); 32 | return "css-hash"; 33 | } 34 | else if (ch == "!") { 35 | source.nextWhile(matcher(/[ \t]/)); 36 | source.nextWhile(matcher(/\w/)); 37 | return "css-important"; 38 | } 39 | else if (/\d/.test(ch)) { 40 | source.nextWhile(matcher(/[\w.%]/)); 41 | return "css-unit"; 42 | } 43 | else if (/[,.+>*\/]/.test(ch)) { 44 | return "css-select-op"; 45 | } 46 | else if (/[;{}:\[\]]/.test(ch)) { 47 | return "css-punctuation"; 48 | } 49 | else { 50 | source.nextWhile(matcher(/[\w\\\-_]/)); 51 | return "css-identifier"; 52 | } 53 | } 54 | 55 | function inCComment(source, setState) { 56 | var maybeEnd = false; 57 | while (!source.endOfLine()) { 58 | var ch = source.next(); 59 | if (maybeEnd && ch == "/") { 60 | setState(normal); 61 | break; 62 | } 63 | maybeEnd = (ch == "*"); 64 | } 65 | return "css-comment"; 66 | } 67 | 68 | function inSGMLComment(source, setState) { 69 | var dashes = 0; 70 | while (!source.endOfLine()) { 71 | var ch = source.next(); 72 | if (dashes >= 2 && ch == ">") { 73 | setState(normal); 74 | break; 75 | } 76 | dashes = (ch == "-") ? dashes + 1 : 0; 77 | } 78 | return "css-comment"; 79 | } 80 | 81 | function inString(quote) { 82 | return function(source, setState) { 83 | var escaped = false; 84 | while (!source.endOfLine()) { 85 | var ch = source.next(); 86 | if (ch == quote && !escaped) 87 | break; 88 | escaped = !escaped && ch == "\\"; 89 | } 90 | if (!escaped) 91 | setState(normal); 92 | return "css-string"; 93 | }; 94 | } 95 | 96 | return function(source, startState) { 97 | return tokenizer(source, startState || normal); 98 | }; 99 | })(); 100 | 101 | function indentCSS(inBraces, inRule, base) { 102 | return function(nextChars) { 103 | if (!inBraces || /^\}/.test(nextChars)) return base; 104 | else if (inRule) return base + indentUnit * 2; 105 | else return base + indentUnit; 106 | }; 107 | } 108 | 109 | // This is a very simplistic parser -- since CSS does not really 110 | // nest, it works acceptably well, but some nicer colouroing could 111 | // be provided with a more complicated parser. 112 | function parseCSS(source, basecolumn) { 113 | basecolumn = basecolumn || 0; 114 | var tokens = tokenizeCSS(source); 115 | var inBraces = false, inRule = false; 116 | 117 | var iter = { 118 | next: function() { 119 | var token = tokens.next(), style = token.style, content = token.content; 120 | 121 | if (style == "css-identifier" && inRule) 122 | token.style = "css-value"; 123 | if (style == "css-hash") 124 | token.style = inRule ? "css-colorcode" : "css-identifier"; 125 | 126 | if (content == "\n") 127 | token.indentation = indentCSS(inBraces, inRule, basecolumn); 128 | 129 | if (content == "{") 130 | inBraces = true; 131 | else if (content == "}") 132 | inBraces = inRule = false; 133 | else if (inBraces && content == ";") 134 | inRule = false; 135 | else if (inBraces && style != "css-comment" && style != "whitespace") 136 | inRule = true; 137 | 138 | return token; 139 | }, 140 | 141 | copy: function() { 142 | var _inBraces = inBraces, _inRule = inRule, _tokenState = tokens.state; 143 | return function(source) { 144 | tokens = tokenizeCSS(source, _tokenState); 145 | inBraces = _inBraces; 146 | inRule = _inRule; 147 | return iter; 148 | }; 149 | } 150 | }; 151 | return iter; 152 | } 153 | 154 | return {make: parseCSS, electricChars: "}"}; 155 | })(); 156 | -------------------------------------------------------------------------------- /site/static/js/parsedummy.js: -------------------------------------------------------------------------------- 1 | var DummyParser = Editor.Parser = (function() { 2 | function tokenizeDummy(source) { 3 | while (!source.endOfLine()) source.next(); 4 | return "text"; 5 | } 6 | function parseDummy(source) { 7 | function indentTo(n) {return function() {return n;}} 8 | source = tokenizer(source, tokenizeDummy); 9 | var space = 0; 10 | 11 | var iter = { 12 | next: function() { 13 | var tok = source.next(); 14 | if (tok.type == "whitespace") { 15 | if (tok.value == "\n") tok.indentation = indentTo(space); 16 | else space = tok.value.length; 17 | } 18 | return tok; 19 | }, 20 | copy: function() { 21 | var _space = space; 22 | return function(_source) { 23 | space = _space; 24 | source = tokenizer(_source, tokenizeDummy); 25 | return iter; 26 | }; 27 | } 28 | }; 29 | return iter; 30 | } 31 | return {make: parseDummy}; 32 | })(); 33 | -------------------------------------------------------------------------------- /site/static/js/parsehtmlmixed.js: -------------------------------------------------------------------------------- 1 | var HTMLMixedParser = Editor.Parser = (function() { 2 | if (!(CSSParser && JSParser && XMLParser)) 3 | throw new Error("CSS, JS, and XML parsers must be loaded for HTML mixed mode to work."); 4 | XMLParser.configure({useHTMLKludges: true}); 5 | 6 | function parseMixed(stream) { 7 | var htmlParser = XMLParser.make(stream), localParser = null, inTag = false; 8 | var iter = {next: top, copy: copy}; 9 | 10 | function top() { 11 | var token = htmlParser.next(); 12 | if (token.content == "<") 13 | inTag = true; 14 | else if (token.style == "xml-tagname" && inTag === true) 15 | inTag = token.content.toLowerCase(); 16 | else if (token.content == ">") { 17 | if (inTag == "script") 18 | iter.next = local(JSParser, "= 0; i--) 153 | cc.push(fs[i]); 154 | } 155 | // cont and pass are used by the action functions to add other 156 | // actions to the stack. cont will cause the current token to be 157 | // consumed, pass will leave it for the next action. 158 | function cont(){ 159 | push(arguments); 160 | consume = true; 161 | } 162 | function pass(){ 163 | push(arguments); 164 | consume = false; 165 | } 166 | // Used to change the style of the current token. 167 | function mark(style){ 168 | marked = style; 169 | } 170 | 171 | // Push a new scope. Will automatically link the current scope. 172 | function pushcontext(){ 173 | context = {prev: context, vars: {"this": true, "arguments": true}}; 174 | } 175 | // Pop off the current scope. 176 | function popcontext(){ 177 | context = context.prev; 178 | } 179 | // Register a variable in the current scope. 180 | function register(varname){ 181 | if (context){ 182 | mark("js-variabledef"); 183 | context.vars[varname] = true; 184 | } 185 | } 186 | // Check whether a variable is defined in the current scope. 187 | function inScope(varname){ 188 | var cursor = context; 189 | while (cursor) { 190 | if (cursor.vars[varname]) 191 | return true; 192 | cursor = cursor.prev; 193 | } 194 | return false; 195 | } 196 | 197 | // Push a new lexical context of the given type. 198 | function pushlex(type, info) { 199 | var result = function(){ 200 | lexical = new JSLexical(indented, column, type, null, lexical, info) 201 | }; 202 | result.lex = true; 203 | return result; 204 | } 205 | // Pop off the current lexical context. 206 | function poplex(){ 207 | lexical = lexical.prev; 208 | } 209 | poplex.lex = true; 210 | // The 'lex' flag on these actions is used by the 'next' function 211 | // to know they can (and have to) be ran before moving on to the 212 | // next token. 213 | 214 | // Creates an action that discards tokens until it finds one of 215 | // the given type. 216 | function expect(wanted){ 217 | return function expecting(type){ 218 | if (type == wanted) cont(); 219 | else cont(arguments.callee); 220 | }; 221 | } 222 | 223 | // Looks for a statement, and then calls itself. 224 | function statements(type){ 225 | return pass(statement, statements); 226 | } 227 | // Dispatches various types of statements based on the type of the 228 | // current token. 229 | function statement(type){ 230 | if (type == "var") cont(pushlex("vardef"), vardef1, expect(";"), poplex); 231 | else if (type == "keyword a") cont(pushlex("form"), expression, statement, poplex); 232 | else if (type == "keyword b") cont(pushlex("form"), statement, poplex); 233 | else if (type == "{") cont(pushlex("}"), block, poplex); 234 | else if (type == "function") cont(functiondef); 235 | else if (type == "for") cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), poplex, statement, poplex); 236 | else if (type == "variable") cont(pushlex("stat"), maybelabel); 237 | else if (type == "switch") cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), block, poplex, poplex); 238 | else if (type == "case") cont(expression, expect(":")); 239 | else if (type == "default") cont(expect(":")); 240 | else if (type == "catch") cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext); 241 | else pass(pushlex("stat"), expression, expect(";"), poplex); 242 | } 243 | // Dispatch expression types. 244 | function expression(type){ 245 | if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator); 246 | else if (type == "function") cont(functiondef); 247 | else if (type == "keyword c") cont(expression); 248 | else if (type == "(") cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator); 249 | else if (type == "operator") cont(expression); 250 | else if (type == "[") cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator); 251 | else if (type == "{") cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); 252 | } 253 | // Called for places where operators, function calls, or 254 | // subscripts are valid. Will skip on to the next action if none 255 | // is found. 256 | function maybeoperator(type){ 257 | if (type == "operator") cont(expression); 258 | else if (type == "(") cont(pushlex(")"), expression, commasep(expression, ")"), poplex, maybeoperator); 259 | else if (type == ".") cont(property, maybeoperator); 260 | else if (type == "[") cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); 261 | } 262 | // When a statement starts with a variable name, it might be a 263 | // label. If no colon follows, it's a regular statement. 264 | function maybelabel(type){ 265 | if (type == ":") cont(poplex, statement); 266 | else pass(maybeoperator, expect(";"), poplex); 267 | } 268 | // Property names need to have their style adjusted -- the 269 | // tokenizer thinks they are variables. 270 | function property(type){ 271 | if (type == "variable") {mark("js-property"); cont();} 272 | } 273 | // This parses a property and its value in an object literal. 274 | function objprop(type){ 275 | if (type == "variable") mark("js-property"); 276 | if (atomicTypes.hasOwnProperty(type)) cont(expect(":"), expression); 277 | } 278 | // Parses a comma-separated list of the things that are recognized 279 | // by the 'what' argument. 280 | function commasep(what, end){ 281 | function proceed(type) { 282 | if (type == ",") cont(what, proceed); 283 | else if (type == end) cont(); 284 | else cont(expect(end)); 285 | }; 286 | return function commaSeparated(type) { 287 | if (type == end) cont(); 288 | else pass(what, proceed); 289 | }; 290 | } 291 | // Look for statements until a closing brace is found. 292 | function block(type){ 293 | if (type == "}") cont(); 294 | else pass(statement, block); 295 | } 296 | // Variable definitions are split into two actions -- 1 looks for 297 | // a name or the end of the definition, 2 looks for an '=' sign or 298 | // a comma. 299 | function vardef1(type, value){ 300 | if (type == "variable"){register(value); cont(vardef2);} 301 | else cont(); 302 | } 303 | function vardef2(type, value){ 304 | if (value == "=") cont(expression, vardef2); 305 | else if (type == ",") cont(vardef1); 306 | } 307 | // For loops. 308 | function forspec1(type){ 309 | if (type == "var") cont(vardef1, forspec2); 310 | else if (type == ";") pass(forspec2); 311 | else if (type == "variable") cont(formaybein); 312 | else pass(forspec2); 313 | } 314 | function formaybein(type, value){ 315 | if (value == "in") cont(expression); 316 | else cont(maybeoperator, forspec2); 317 | } 318 | function forspec2(type, value){ 319 | if (type == ";") cont(forspec3); 320 | else if (value == "in") cont(expression); 321 | else cont(expression, expect(";"), forspec3); 322 | } 323 | function forspec3(type) { 324 | if (type == ")") pass(); 325 | else cont(expression); 326 | } 327 | // A function definition creates a new context, and the variables 328 | // in its argument list have to be added to this context. 329 | function functiondef(type, value){ 330 | if (type == "variable"){register(value); cont(functiondef);} 331 | else if (type == "(") cont(pushcontext, commasep(funarg, ")"), statement, popcontext); 332 | } 333 | function funarg(type, value){ 334 | if (type == "variable"){register(value); cont();} 335 | } 336 | 337 | return parser; 338 | } 339 | 340 | return {make: parseJS, electricChars: "{}:"}; 341 | })(); 342 | -------------------------------------------------------------------------------- /site/static/js/parsesparql.js: -------------------------------------------------------------------------------- 1 | Editor.Parser = (function() { 2 | function wordRegexp(words) { 3 | return new RegExp("^(?:" + words.join("|") + ")$", "i"); 4 | } 5 | var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", 6 | "isblank", "isliteral", "union", "a"]); 7 | var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", 8 | "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", 9 | "graph", "by", "asc", "desc", ]); 10 | var operatorChars = /[*+\-<>=&|]/; 11 | 12 | var tokenizeSparql = (function() { 13 | function normal(source, setState) { 14 | var ch = source.next(); 15 | if (ch == "$" || ch == "?") { 16 | source.nextWhile(matcher(/[\w\d]/)); 17 | return "sp-var"; 18 | } 19 | else if (ch == "<" && !source.applies(matcher(/[\s\u00a0=]/))) { 20 | source.nextWhile(matcher(/[^\s\u00a0>]/)); 21 | if (source.equals(">")) source.next(); 22 | return "sp-uri"; 23 | } 24 | else if (ch == "\"" || ch == "'") { 25 | setState(inLiteral(ch)); 26 | return null; 27 | } 28 | else if (/[{}\(\),\.;\[\]]/.test(ch)) { 29 | return "sp-punc"; 30 | } 31 | else if (ch == "#") { 32 | while (!source.endOfLine()) source.next(); 33 | return "sp-comment"; 34 | } 35 | else if (operatorChars.test(ch)) { 36 | source.nextWhile(matcher(operatorChars)); 37 | return "sp-operator"; 38 | } 39 | else if (ch == ":") { 40 | source.nextWhile(matcher(/[\w\d\._\-]/)); 41 | return "sp-prefixed"; 42 | } 43 | else { 44 | source.nextWhile(matcher(/[_\w\d]/)); 45 | if (source.equals(":")) { 46 | source.next(); 47 | source.nextWhile(matcher(/[\w\d_\-]/)); 48 | return "sp-prefixed"; 49 | } 50 | var word = source.get(), type; 51 | if (ops.test(word)) 52 | type = "sp-operator"; 53 | else if (keywords.test(word)) 54 | type = "sp-keyword"; 55 | else 56 | type = "sp-word"; 57 | return {style: type, content: word}; 58 | } 59 | } 60 | 61 | function inLiteral(quote) { 62 | return function(source, setState) { 63 | var escaped = false; 64 | while (!source.endOfLine()) { 65 | var ch = source.next(); 66 | if (ch == quote && !escaped) { 67 | setState(normal); 68 | break; 69 | } 70 | escaped = !escaped && ch == "\\"; 71 | } 72 | return "sp-literal"; 73 | }; 74 | } 75 | 76 | return function(source, startState) { 77 | return tokenizer(source, startState || normal); 78 | }; 79 | })(); 80 | 81 | function indentSparql(context) { 82 | return function(nextChars) { 83 | var firstChar = nextChars && nextChars.charAt(0); 84 | if (/[\]\}]/.test(firstChar)) 85 | while (context && context.type == "pattern") context = context.prev; 86 | 87 | var closing = context && firstChar == matching[context.type]; 88 | if (!context) 89 | return 0; 90 | else if (context.type == "pattern") 91 | return context.col; 92 | else if (context.align) 93 | return context.col - (closing ? context.width : 0); 94 | else 95 | return context.indent + (closing ? 0 : indentUnit); 96 | } 97 | } 98 | 99 | function parseSparql(source) { 100 | var tokens = tokenizeSparql(source); 101 | var context = null, indent = 0, col = 0; 102 | function pushContext(type, width) { 103 | context = {prev: context, indent: indent, col: col, type: type, width: width}; 104 | } 105 | function popContext() { 106 | context = context.prev; 107 | } 108 | 109 | var iter = { 110 | next: function() { 111 | var token = tokens.next(), type = token.style, content = token.content, width = token.value.length; 112 | 113 | if (content == "\n") { 114 | token.indentation = indentSparql(context); 115 | indent = col = 0; 116 | if (context && context.align == null) context.align = false; 117 | } 118 | else if (type == "whitespace" && col == 0) { 119 | indent = width; 120 | } 121 | else if (type != "sp-comment" && context && context.align == null) { 122 | context.align = true; 123 | } 124 | 125 | if (content != "\n") col += width; 126 | 127 | if (/[\[\{\(]/.test(content)) { 128 | pushContext(content, width); 129 | } 130 | else if (/[\]\}\)]/.test(content)) { 131 | while (context && context.type == "pattern") 132 | popContext(); 133 | if (context && content == matching[context.type]) 134 | popContext(); 135 | } 136 | else if (content == "." && context && context.type == "pattern") { 137 | popContext(); 138 | } 139 | else if ((type == "sp-word" || type == "sp-prefixed" || type == "sp-uri" || type == "sp-var" || type == "sp-literal") && 140 | context && /[\{\[]/.test(context.type)) { 141 | pushContext("pattern", width); 142 | } 143 | 144 | return token; 145 | }, 146 | 147 | copy: function() { 148 | var _context = context, _indent = indent, _col = col, _tokenState = tokens.state; 149 | return function(source) { 150 | tokens = tokenizeSparql(source, _tokenState); 151 | context = _context; 152 | indent = _indent; 153 | col = _col; 154 | return iter; 155 | }; 156 | } 157 | }; 158 | return iter; 159 | } 160 | 161 | return {make: parseSparql, electricChars: "}]"}; 162 | })(); 163 | -------------------------------------------------------------------------------- /site/static/js/parsexml.js: -------------------------------------------------------------------------------- 1 | /* This file defines an XML parser, with a few kludges to make it 2 | * useable for HTML. autoSelfClosers defines a set of tag names that 3 | * are expected to not have a closing tag, and doNotIndent specifies 4 | * the tags inside of which no indentation should happen (see Config 5 | * object). These can be disabled by passing the editor an object like 6 | * {useHTMLKludges: false} as parserConfig option. 7 | */ 8 | 9 | var XMLParser = Editor.Parser = (function() { 10 | var Kludges = { 11 | autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true, 12 | "meta": true, "col": true, "frame": true, "base": true, "area": true}, 13 | doNotIndent: {"pre": true, "!cdata": true} 14 | }; 15 | var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}}; 16 | var UseKludges = Kludges; 17 | var alignCDATA = false; 18 | 19 | // Simple stateful tokenizer for XML documents. Returns a 20 | // MochiKit-style iterator, with a state property that contains a 21 | // function encapsulating the current state. See tokenize.js. 22 | var tokenizeXML = (function() { 23 | function inText(source, setState) { 24 | var ch = source.next(); 25 | if (ch == "<") { 26 | if (source.equals("!")) { 27 | source.next(); 28 | if (source.equals("[")) { 29 | if (source.lookAhead("[CDATA[", true)) { 30 | setState(inBlock("xml-cdata", "]]>")); 31 | return null; 32 | } 33 | else { 34 | return "xml-text"; 35 | } 36 | } 37 | else if (source.lookAhead("--", true)) { 38 | setState(inBlock("xml-comment", "-->")); 39 | return null; 40 | } 41 | else { 42 | return "xml-text"; 43 | } 44 | } 45 | else if (source.equals("?")) { 46 | source.next(); 47 | source.nextWhile(matcher(/[\w\._\-]/)); 48 | setState(inBlock("xml-processing", "?>")); 49 | return "xml-processing"; 50 | } 51 | else { 52 | if (source.equals("/")) source.next(); 53 | setState(inTag); 54 | return "xml-punctuation"; 55 | } 56 | } 57 | else if (ch == "&") { 58 | while (!source.endOfLine()) { 59 | if (source.next() == ";") 60 | break; 61 | } 62 | return "xml-entity"; 63 | } 64 | else { 65 | source.nextWhile(matcher(/[^&<\n]/)); 66 | return "xml-text"; 67 | } 68 | } 69 | 70 | function inTag(source, setState) { 71 | var ch = source.next(); 72 | if (ch == ">") { 73 | setState(inText); 74 | return "xml-punctuation"; 75 | } 76 | else if (/[?\/]/.test(ch) && source.equals(">")) { 77 | source.next(); 78 | setState(inText); 79 | return "xml-punctuation"; 80 | } 81 | else if (ch == "=") { 82 | return "xml-punctuation"; 83 | } 84 | else if (/[\'\"]/.test(ch)) { 85 | setState(inAttribute(ch)); 86 | return null; 87 | } 88 | else { 89 | source.nextWhile(matcher(/[^\s\u00a0=<>\"\'\/?]/)); 90 | return "xml-name"; 91 | } 92 | } 93 | 94 | function inAttribute(quote) { 95 | return function(source, setState) { 96 | while (!source.endOfLine()) { 97 | if (source.next() == quote) { 98 | setState(inTag); 99 | break; 100 | } 101 | } 102 | return "xml-attribute"; 103 | }; 104 | } 105 | 106 | function inBlock(style, terminator) { 107 | return function(source, setState) { 108 | while (!source.endOfLine()) { 109 | if (source.lookAhead(terminator, true)) { 110 | setState(inText); 111 | break; 112 | } 113 | source.next(); 114 | } 115 | return style; 116 | }; 117 | } 118 | 119 | return function(source, startState) { 120 | return tokenizer(source, startState || inText); 121 | }; 122 | })(); 123 | 124 | // The parser. The structure of this function largely follows that of 125 | // parseJavaScript in parsejavascript.js (there is actually a bit more 126 | // shared code than I'd like), but it is quite a bit simpler. 127 | function parseXML(source) { 128 | var tokens = tokenizeXML(source); 129 | var cc = [base]; 130 | var tokenNr = 0, indented = 0; 131 | var currentTag = null, context = null; 132 | var consume, marked; 133 | 134 | function push(fs) { 135 | for (var i = fs.length - 1; i >= 0; i--) 136 | cc.push(fs[i]); 137 | } 138 | function cont() { 139 | push(arguments); 140 | consume = true; 141 | } 142 | function pass() { 143 | push(arguments); 144 | consume = false; 145 | } 146 | 147 | function mark(style) { 148 | marked = style; 149 | } 150 | function expect(text) { 151 | return function(style, content) { 152 | if (content == text) cont(); 153 | else mark("xml-error") || cont(arguments.callee); 154 | }; 155 | } 156 | 157 | function pushContext(tagname, startOfLine) { 158 | var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent); 159 | context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent}; 160 | } 161 | function popContext() { 162 | context = context.prev; 163 | } 164 | function computeIndentation(baseContext) { 165 | return function(nextChars, current) { 166 | var context = baseContext; 167 | if (context && context.noIndent) 168 | return current; 169 | if (alignCDATA && /")); 189 | else if (style == "xml-cdata") { 190 | if (!context || context.name != "!cdata") pushContext("!cdata"); 191 | if (/\]\]>$/.test(content)) popContext(); 192 | cont(); 193 | } 194 | else if (harmlessTokens.hasOwnProperty(style)) cont(); 195 | else mark("xml-error") || cont(); 196 | } 197 | function tagname(style, content) { 198 | if (style == "xml-name") { 199 | currentTag = content.toLowerCase(); 200 | mark("xml-tagname"); 201 | cont(); 202 | } 203 | else { 204 | currentTag = null; 205 | pass(); 206 | } 207 | } 208 | function closetagname(style, content) { 209 | if (style == "xml-name" && context && content.toLowerCase() == context.name) { 210 | popContext(); 211 | mark("xml-tagname"); 212 | } 213 | else { 214 | mark("xml-error"); 215 | } 216 | cont(); 217 | } 218 | function endtag(startOfLine) { 219 | return function(style, content) { 220 | if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont(); 221 | else if (content == ">") pushContext(currentTag, startOfLine) || cont(); 222 | else mark("xml-error") || cont(arguments.callee); 223 | }; 224 | } 225 | function attributes(style) { 226 | if (style == "xml-name") mark("xml-attname") || cont(attribute, attributes); 227 | else pass(); 228 | } 229 | function attribute(style, content) { 230 | if (content == "=") cont(value); 231 | else if (content == ">" || content == "/>") pass(endtag); 232 | else pass(); 233 | } 234 | function value(style) { 235 | if (style == "xml-attribute") cont(value); 236 | else pass(); 237 | } 238 | 239 | return { 240 | indentation: function() {return indented;}, 241 | 242 | next: function(){ 243 | var token = tokens.next(); 244 | if (token.style == "whitespace" && tokenNr == 0) 245 | indented = token.value.length; 246 | else 247 | tokenNr++; 248 | if (token.content == "\n") { 249 | indented = tokenNr = 0; 250 | token.indentation = computeIndentation(context); 251 | } 252 | 253 | if (token.style == "whitespace" || token.type == "xml-comment") 254 | return token; 255 | 256 | while(true){ 257 | consume = marked = false; 258 | cc.pop()(token.style, token.content); 259 | if (consume){ 260 | if (marked) 261 | token.style = marked; 262 | return token; 263 | } 264 | } 265 | }, 266 | 267 | copy: function(){ 268 | var _cc = cc.concat([]), _tokenState = tokens.state, _context = context; 269 | var parser = this; 270 | 271 | return function(input){ 272 | cc = _cc.concat([]); 273 | tokenNr = indented = 0; 274 | context = _context; 275 | tokens = tokenizeXML(input, _tokenState); 276 | return parser; 277 | }; 278 | } 279 | }; 280 | } 281 | 282 | return { 283 | make: parseXML, 284 | electricChars: "/", 285 | configure: function(config) { 286 | if (config.useHTMLKludges != null) 287 | UseKludges = config.useHTMLKludges ? Kludges : NoKludges; 288 | if (config.alignCDATA) 289 | alignCDATA = config.alignCDATA; 290 | } 291 | }; 292 | })(); 293 | -------------------------------------------------------------------------------- /site/static/js/select.js: -------------------------------------------------------------------------------- 1 | /* Functionality for finding, storing, and restoring selections 2 | * 3 | * This does not provide a generic API, just the minimal functionality 4 | * required by the CodeMirror system. 5 | */ 6 | 7 | // Namespace object. 8 | var select = {}; 9 | 10 | (function() { 11 | select.ie_selection = document.selection && document.selection.createRangeCollection; 12 | 13 | // Find the 'top-level' (defined as 'a direct child of the node 14 | // passed as the top argument') node that the given node is 15 | // contained in. Return null if the given node is not inside the top 16 | // node. 17 | function topLevelNodeAt(node, top) { 18 | while (node && node.parentNode != top) 19 | node = node.parentNode; 20 | return node; 21 | } 22 | 23 | // Find the top-level node that contains the node before this one. 24 | function topLevelNodeBefore(node, top) { 25 | while (!node.previousSibling && node.parentNode != top) 26 | node = node.parentNode; 27 | return topLevelNodeAt(node.previousSibling, top); 28 | } 29 | 30 | // Used to prevent restoring a selection when we do not need to. 31 | var currentSelection = null; 32 | 33 | var fourSpaces = "\u00a0\u00a0\u00a0\u00a0"; 34 | 35 | select.snapshotChanged = function() { 36 | if (currentSelection) currentSelection.changed = true; 37 | }; 38 | 39 | // This is called by the code in editor.js whenever it is replacing 40 | // a text node. The function sees whether the given oldNode is part 41 | // of the current selection, and updates this selection if it is. 42 | // Because nodes are often only partially replaced, the length of 43 | // the part that gets replaced has to be taken into account -- the 44 | // selection might stay in the oldNode if the newNode is smaller 45 | // than the selection's offset. The offset argument is needed in 46 | // case the selection does move to the new object, and the given 47 | // length is not the whole length of the new node (part of it might 48 | // have been used to replace another node). 49 | select.snapshotReplaceNode = function(from, to, length, offset) { 50 | if (!currentSelection) return; 51 | currentSelection.changed = true; 52 | 53 | function replace(point) { 54 | if (from == point.node) { 55 | if (length && point.offset > length) { 56 | point.offset -= length; 57 | } 58 | else { 59 | point.node = to; 60 | point.offset += (offset || 0); 61 | } 62 | } 63 | } 64 | replace(currentSelection.start); 65 | replace(currentSelection.end); 66 | }; 67 | 68 | select.snapshotMove = function(from, to, distance, relative, ifAtStart) { 69 | if (!currentSelection) return; 70 | currentSelection.changed = true; 71 | 72 | function move(point) { 73 | if (from == point.node && (!ifAtStart || point.offset == 0)) { 74 | point.node = to; 75 | if (relative) point.offset = Math.max(0, point.offset + distance); 76 | else point.offset = distance; 77 | } 78 | } 79 | move(currentSelection.start); 80 | move(currentSelection.end); 81 | }; 82 | 83 | // Most functions are defined in two ways, one for the IE selection 84 | // model, one for the W3C one. 85 | if (select.ie_selection) { 86 | function selectionNode(win, start) { 87 | var range = win.document.selection.createRange(); 88 | range.collapse(start); 89 | 90 | function nodeAfter(node) { 91 | var found = null; 92 | while (!found && node) { 93 | found = node.nextSibling; 94 | node = node.parentNode; 95 | } 96 | return nodeAtStartOf(found); 97 | } 98 | 99 | function nodeAtStartOf(node) { 100 | while (node && node.firstChild) node = node.firstChild; 101 | return {node: node, offset: 0}; 102 | } 103 | 104 | var containing = range.parentElement(); 105 | if (!isAncestor(win.document.body, containing)) return null; 106 | if (!containing.firstChild) return nodeAtStartOf(containing); 107 | 108 | var working = range.duplicate(); 109 | working.moveToElementText(containing); 110 | working.collapse(true); 111 | for (var cur = containing.firstChild; cur; cur = cur.nextSibling) { 112 | if (cur.nodeType == 3) { 113 | var size = cur.nodeValue.length; 114 | working.move("character", size); 115 | } 116 | else { 117 | working.moveToElementText(cur); 118 | working.collapse(false); 119 | } 120 | 121 | var dir = range.compareEndPoints("StartToStart", working); 122 | if (dir == 0) return nodeAfter(cur); 123 | if (dir == 1) continue; 124 | if (cur.nodeType != 3) return nodeAtStartOf(cur); 125 | 126 | working.setEndPoint("StartToEnd", range); 127 | return {node: cur, offset: size - working.text.length}; 128 | } 129 | return nodeAfter(containing); 130 | } 131 | 132 | select.markSelection = function(win) { 133 | currentSelection = null; 134 | var sel = win.document.selection; 135 | if (!sel) return; 136 | var start = selectionNode(win, true), 137 | end = sel.createRange().text == "" ? start : selectionNode(win, false); 138 | if (!start || !end) return; 139 | currentSelection = {start: start, end: end, window: win, changed: false}; 140 | }; 141 | 142 | select.selectMarked = function() { 143 | if (!currentSelection || !currentSelection.changed) return; 144 | 145 | function makeRange(point) { 146 | var range = currentSelection.window.document.body.createTextRange(); 147 | var node = point.node; 148 | if (!node) { 149 | range.moveToElementText(currentSelection.window.document.body); 150 | range.collapse(false); 151 | } 152 | else if (node.nodeType == 3) { 153 | range.moveToElementText(node.parentNode); 154 | var offset = point.offset; 155 | while (node.previousSibling) { 156 | node = node.previousSibling; 157 | offset += (node.innerText || "").length; 158 | } 159 | range.move("character", offset); 160 | } 161 | else { 162 | range.moveToElementText(node); 163 | range.collapse(true); 164 | } 165 | return range; 166 | } 167 | 168 | var start = makeRange(currentSelection.start), end = makeRange(currentSelection.end); 169 | start.setEndPoint("StartToEnd", end); 170 | start.select(); 171 | }; 172 | 173 | // Get the top-level node that one end of the cursor is inside or 174 | // after. Note that this returns false for 'no cursor', and null 175 | // for 'start of document'. 176 | select.selectionTopNode = function(container, start) { 177 | var selection = container.ownerDocument.selection; 178 | if (!selection) return false; 179 | 180 | var range = selection.createRange(); 181 | range.collapse(start); 182 | var around = range.parentElement(); 183 | if (around && isAncestor(container, around)) { 184 | // Only use this node if the selection is not at its start. 185 | var range2 = range.duplicate(); 186 | range2.moveToElementText(around); 187 | if (range.compareEndPoints("StartToStart", range2) == -1) 188 | return topLevelNodeAt(around, container); 189 | } 190 | // Fall-back hack 191 | try {range.pasteHTML("");} 192 | catch (e) {return false;} 193 | 194 | var temp = container.ownerDocument.getElementById("xxx-temp-xxx"); 195 | if (temp) { 196 | var result = topLevelNodeBefore(temp, container); 197 | removeElement(temp); 198 | return result; 199 | } 200 | return false; 201 | }; 202 | 203 | // Place the cursor after this.start. This is only useful when 204 | // manually moving the cursor instead of restoring it to its old 205 | // position. 206 | select.focusAfterNode = function(node, container) { 207 | var range = container.ownerDocument.body.createTextRange(); 208 | range.moveToElementText(node || container); 209 | range.collapse(!node); 210 | range.select(); 211 | }; 212 | 213 | select.somethingSelected = function(win) { 214 | var sel = win.document.selection; 215 | return sel && (sel.createRange().text != ""); 216 | }; 217 | 218 | function insertAtCursor(window, html) { 219 | var selection = window.document.selection; 220 | if (selection) { 221 | var range = selection.createRange(); 222 | range.pasteHTML(html); 223 | range.collapse(false); 224 | range.select(); 225 | } 226 | } 227 | 228 | // Used to normalize the effect of the enter key, since browsers 229 | // do widely different things when pressing enter in designMode. 230 | select.insertNewlineAtCursor = function(window) { 231 | insertAtCursor(window, "
"); 232 | }; 233 | 234 | select.insertTabAtCursor = function(window) { 235 | insertAtCursor(window, fourSpaces); 236 | }; 237 | 238 | // Get the BR node at the start of the line on which the cursor 239 | // currently is, and the offset into the line. Returns null as 240 | // node if cursor is on first line. 241 | select.cursorPos = function(container, start) { 242 | var selection = container.ownerDocument.selection; 243 | if (!selection) return null; 244 | 245 | var topNode = select.selectionTopNode(container, start); 246 | while (topNode && topNode.nodeName != "BR") 247 | topNode = topNode.previousSibling; 248 | 249 | var range = selection.createRange(), range2 = range.duplicate(); 250 | range.collapse(start); 251 | if (topNode) { 252 | range2.moveToElementText(topNode); 253 | range2.collapse(false); 254 | } 255 | else { 256 | // When nothing is selected, we can get all kinds of funky errors here. 257 | try { range2.moveToElementText(container); } 258 | catch (e) { return null; } 259 | range2.collapse(true); 260 | } 261 | range.setEndPoint("StartToStart", range2); 262 | 263 | return {node: topNode, offset: range.text.length}; 264 | }; 265 | 266 | select.setCursorPos = function(container, from, to) { 267 | function rangeAt(pos) { 268 | var range = container.ownerDocument.body.createTextRange(); 269 | if (!pos.node) { 270 | range.moveToElementText(container); 271 | range.collapse(true); 272 | } 273 | else { 274 | range.moveToElementText(pos.node); 275 | range.collapse(false); 276 | } 277 | range.move("character", pos.offset); 278 | return range; 279 | } 280 | 281 | var range = rangeAt(from); 282 | if (to && to != from) 283 | range.setEndPoint("EndToEnd", rangeAt(to)); 284 | range.select(); 285 | } 286 | 287 | // Make sure the cursor is visible. 288 | select.scrollToCursor = function(container) { 289 | var selection = container.ownerDocument.selection; 290 | if (!selection) return null; 291 | selection.createRange().scrollIntoView(); 292 | }; 293 | 294 | select.scrollToNode = function(node) { 295 | if (!node) return; 296 | node.scrollIntoView(); 297 | }; 298 | 299 | // Some hacks for storing and re-storing the selection when the editor loses and regains focus. 300 | select.selectionCoords = function (win) { 301 | var selection = win.document.selection; 302 | if (!selection) return null; 303 | var start = selection.createRange(), end = start.duplicate(); 304 | start.collapse(true); 305 | end.collapse(false); 306 | 307 | var body = win.document.body; 308 | return {start: {x: start.boundingLeft + body.scrollLeft - 1, 309 | y: start.boundingTop + body.scrollTop}, 310 | end: {x: end.boundingLeft + body.scrollLeft - 1, 311 | y: end.boundingTop + body.scrollTop}}; 312 | }; 313 | 314 | // Restore a stored selection. 315 | select.selectCoords = function(win, coords) { 316 | if (!coords) return; 317 | 318 | var range1 = win.document.body.createTextRange(), range2 = range1.duplicate(); 319 | // This can fail for various hard-to-handle reasons. 320 | try { 321 | range1.moveToPoint(coords.start.x, coords.start.y); 322 | range2.moveToPoint(coords.end.x, coords.end.y); 323 | range1.setEndPoint("EndToStart", range2); 324 | range1.select(); 325 | } catch(e) {alert(e.message);} 326 | }; 327 | } 328 | // W3C model 329 | else { 330 | // Store start and end nodes, and offsets within these, and refer 331 | // back to the selection object from those nodes, so that this 332 | // object can be updated when the nodes are replaced before the 333 | // selection is restored. 334 | select.markSelection = function (win) { 335 | var selection = win.getSelection(); 336 | if (!selection || selection.rangeCount == 0) 337 | return (currentSelection = null); 338 | var range = selection.getRangeAt(0); 339 | 340 | currentSelection = { 341 | start: {node: range.startContainer, offset: range.startOffset}, 342 | end: {node: range.endContainer, offset: range.endOffset}, 343 | window: win, 344 | changed: false 345 | }; 346 | 347 | // We want the nodes right at the cursor, not one of their 348 | // ancestors with a suitable offset. This goes down the DOM tree 349 | // until a 'leaf' is reached (or is it *up* the DOM tree?). 350 | function normalize(point){ 351 | while (point.node.nodeType != 3 && point.node.nodeName != "BR") { 352 | var newNode = point.node.childNodes[point.offset] || point.node.nextSibling; 353 | point.offset = 0; 354 | while (!newNode && point.node.parentNode) { 355 | point.node = point.node.parentNode; 356 | newNode = point.node.nextSibling; 357 | } 358 | point.node = newNode; 359 | if (!newNode) 360 | break; 361 | } 362 | } 363 | 364 | normalize(currentSelection.start); 365 | normalize(currentSelection.end); 366 | }; 367 | 368 | select.selectMarked = function () { 369 | if (!currentSelection || !currentSelection.changed) return; 370 | var win = currentSelection.window, range = win.document.createRange(); 371 | 372 | function setPoint(point, which) { 373 | if (point.node) { 374 | // Some magic to generalize the setting of the start and end 375 | // of a range. 376 | if (point.offset == 0) 377 | range["set" + which + "Before"](point.node); 378 | else 379 | range["set" + which](point.node, point.offset); 380 | } 381 | else { 382 | range.setStartAfter(win.document.body.lastChild || win.document.body); 383 | } 384 | } 385 | 386 | setPoint(currentSelection.end, "End"); 387 | setPoint(currentSelection.start, "Start"); 388 | selectRange(range, win); 389 | }; 390 | 391 | // Helper for selecting a range object. 392 | function selectRange(range, window) { 393 | var selection = window.getSelection(); 394 | selection.removeAllRanges(); 395 | selection.addRange(range); 396 | }; 397 | function selectionRange(window) { 398 | var selection = window.getSelection(); 399 | if (!selection || selection.rangeCount == 0) 400 | return false; 401 | else 402 | return selection.getRangeAt(0); 403 | } 404 | 405 | // Finding the top-level node at the cursor in the W3C is, as you 406 | // can see, quite an involved process. 407 | select.selectionTopNode = function(container, start) { 408 | var range = selectionRange(container.ownerDocument.defaultView); 409 | if (!range) return false; 410 | 411 | var node = start ? range.startContainer : range.endContainer; 412 | var offset = start ? range.startOffset : range.endOffset; 413 | // Work around (yet another) bug in Opera's selection model. 414 | if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 && 415 | container.childNodes[range.startOffset] && container.childNodes[range.startOffset].nodeName == "BR") 416 | offset--; 417 | 418 | // For text nodes, we look at the node itself if the cursor is 419 | // inside, or at the node before it if the cursor is at the 420 | // start. 421 | if (node.nodeType == 3){ 422 | if (offset > 0) 423 | return topLevelNodeAt(node, container); 424 | else 425 | return topLevelNodeBefore(node, container); 426 | } 427 | // Occasionally, browsers will return the HTML node as 428 | // selection. If the offset is 0, we take the start of the frame 429 | // ('after null'), otherwise, we take the last node. 430 | else if (node.nodeName == "HTML") { 431 | return (offset == 1 ? null : container.lastChild); 432 | } 433 | // If the given node is our 'container', we just look up the 434 | // correct node by using the offset. 435 | else if (node == container) { 436 | return (offset == 0) ? null : node.childNodes[offset - 1]; 437 | } 438 | // In any other case, we have a regular node. If the cursor is 439 | // at the end of the node, we use the node itself, if it is at 440 | // the start, we use the node before it, and in any other 441 | // case, we look up the child before the cursor and use that. 442 | else { 443 | if (offset == node.childNodes.length) 444 | return topLevelNodeAt(node, container); 445 | else if (offset == 0) 446 | return topLevelNodeBefore(node, container); 447 | else 448 | return topLevelNodeAt(node.childNodes[offset - 1], container); 449 | } 450 | }; 451 | 452 | select.focusAfterNode = function(node, container) { 453 | var win = container.ownerDocument.defaultView, 454 | range = win.document.createRange(); 455 | range.setStartBefore(container.firstChild || container); 456 | // In Opera, setting the end of a range at the end of a line 457 | // (before a BR) will cause the cursor to appear on the next 458 | // line, so we set the end inside of the start node when 459 | // possible. 460 | if (node && !node.firstChild) 461 | range.setEndAfter(node); 462 | else if (node) 463 | range.setEnd(node, node.childNodes.length); 464 | else 465 | range.setEndBefore(container.firstChild || container); 466 | range.collapse(false); 467 | selectRange(range, win); 468 | }; 469 | 470 | select.somethingSelected = function(win) { 471 | var range = selectionRange(win); 472 | return range && !range.collapsed; 473 | }; 474 | 475 | function insertNodeAtCursor(window, node) { 476 | var range = selectionRange(window); 477 | if (!range) return; 478 | 479 | range.deleteContents(); 480 | range.insertNode(node); 481 | webkitLastLineHack(window.document.body); 482 | range = window.document.createRange(); 483 | range.selectNode(node); 484 | range.collapse(false); 485 | selectRange(range, window); 486 | } 487 | 488 | select.insertNewlineAtCursor = function(window) { 489 | insertNodeAtCursor(window, window.document.createElement("BR")); 490 | }; 491 | 492 | select.insertTabAtCursor = function(window) { 493 | insertNodeAtCursor(window, window.document.createTextNode(fourSpaces)); 494 | }; 495 | 496 | select.cursorPos = function(container, start) { 497 | var range = selectionRange(window); 498 | if (!range) return; 499 | 500 | var topNode = select.selectionTopNode(container, start); 501 | while (topNode && topNode.nodeName != "BR") 502 | topNode = topNode.previousSibling; 503 | 504 | range = range.cloneRange(); 505 | range.collapse(start); 506 | if (topNode) 507 | range.setStartAfter(topNode); 508 | else 509 | range.setStartBefore(container); 510 | return {node: topNode, offset: range.toString().length}; 511 | }; 512 | 513 | select.setCursorPos = function(container, from, to) { 514 | var win = container.ownerDocument.defaultView, 515 | range = win.document.createRange(); 516 | 517 | function setPoint(node, offset, side) { 518 | if (!node) 519 | node = container.firstChild; 520 | else 521 | node = node.nextSibling; 522 | 523 | if (!node) 524 | return; 525 | 526 | if (offset == 0) { 527 | range["set" + side + "Before"](node); 528 | return true; 529 | } 530 | 531 | var backlog = [] 532 | function decompose(node) { 533 | if (node.nodeType == 3) 534 | backlog.push(node); 535 | else 536 | forEach(node.childNodes, decompose); 537 | } 538 | while (true) { 539 | while (node && !backlog.length) { 540 | decompose(node); 541 | node = node.nextSibling; 542 | } 543 | var cur = backlog.shift(); 544 | if (!cur) return false; 545 | 546 | var length = cur.nodeValue.length; 547 | if (length >= offset) { 548 | range["set" + side](cur, offset); 549 | return true; 550 | } 551 | offset -= length; 552 | } 553 | } 554 | 555 | to = to || from; 556 | if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start")) 557 | selectRange(range, win); 558 | }; 559 | 560 | select.scrollToNode = function(element) { 561 | if (!element) return; 562 | var doc = element.ownerDocument, body = doc.body, win = doc.defaultView, html = doc.documentElement; 563 | 564 | // In Opera, BR elements *always* have a scrollTop property of zero. Go Opera. 565 | while (element && !element.offsetTop) 566 | element = element.previousSibling; 567 | 568 | var y = 0, pos = element; 569 | while (pos && pos.offsetParent) { 570 | y += pos.offsetTop; 571 | pos = pos.offsetParent; 572 | } 573 | 574 | var screen_y = y - (body.scrollTop || html.scrollTop || 0); 575 | if (screen_y < 0 || screen_y > win.innerHeight - 30) 576 | win.scrollTo(body.scrollLeft || html.scrollLeft || 0, y); 577 | }; 578 | 579 | select.scrollToCursor = function(container) { 580 | select.scrollToNode(select.selectionTopNode(container, true) || container.firstChild); 581 | }; 582 | } 583 | })(); 584 | -------------------------------------------------------------------------------- /site/static/js/stringstream.js: -------------------------------------------------------------------------------- 1 | /* String streams are the things fed to parsers (which can feed them 2 | * to a tokenizer if they want). They provide peek and next methods 3 | * for looking at the current character (next 'consumes' this 4 | * character, peek does not), and a get method for retrieving all the 5 | * text that was consumed since the last time get was called. 6 | * 7 | * An easy mistake to make is to let a StopIteration exception finish 8 | * the token stream while there are still characters pending in the 9 | * string stream (hitting the end of the buffer while parsing a 10 | * token). To make it easier to detect such errors, the strings throw 11 | * an exception when this happens. 12 | */ 13 | 14 | // Make a string stream out of an iterator that returns strings. This 15 | // is applied to the result of traverseDOM (see codemirror.js), and 16 | // the resulting stream is fed to the parser. 17 | window.stringStream = function(source){ 18 | function wrapString(string) { 19 | var pos = 0; 20 | return {next: function() { 21 | if (pos >= string.length) throw StopIteration; 22 | else return string.charAt(pos++); 23 | }}; 24 | } 25 | 26 | if (typeof source == "string") source = wrapString(source); 27 | // String that's currently being iterated over. 28 | var current = ""; 29 | // Position in that string. 30 | var pos = 0; 31 | // Accumulator for strings that have been iterated over but not 32 | // get()-ed yet. 33 | var accum = ""; 34 | // Make sure there are more characters ready, or throw 35 | // StopIteration. 36 | function ensureChars() { 37 | while (pos == current.length) { 38 | accum += current; 39 | current = ""; // In case source.next() throws 40 | pos = 0; 41 | try {current = source.next();} 42 | catch (e) { 43 | if (e != StopIteration) throw e; 44 | else return false; 45 | } 46 | } 47 | return true; 48 | } 49 | 50 | return { 51 | // Return the next character in the stream. 52 | peek: function() { 53 | if (!ensureChars()) return null; 54 | return current.charAt(pos); 55 | }, 56 | // Get the next character, throw StopIteration if at end, check 57 | // for unused content. 58 | next: function() { 59 | if (!ensureChars()) { 60 | if (accum.length > 0) 61 | throw "End of stringstream reached without emptying buffer ('" + accum + "')."; 62 | else 63 | throw StopIteration; 64 | } 65 | return current.charAt(pos++); 66 | }, 67 | // Return the characters iterated over since the last call to 68 | // .get(). 69 | get: function() { 70 | var temp = accum; 71 | accum = ""; 72 | if (pos > 0){ 73 | temp += current.slice(0, pos); 74 | current = current.slice(pos); 75 | pos = 0; 76 | } 77 | return temp; 78 | }, 79 | // Push a string back into the stream. 80 | push: function(str) { 81 | current = current.slice(0, pos) + str + current.slice(pos); 82 | }, 83 | lookAhead: function(str, consume, skipSpaces, caseInsensitive) { 84 | function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} 85 | str = cased(str); 86 | var found = false; 87 | 88 | var _accum = accum, _pos = pos; 89 | if (skipSpaces) this.nextWhile(matcher(/[\s\u00a0]/)); 90 | 91 | while (true) { 92 | var end = pos + str.length, left = current.length - pos; 93 | if (end <= current.length) { 94 | found = str == cased(current.slice(pos, end)); 95 | pos = end; 96 | break; 97 | } 98 | else if (str.slice(0, left) == cased(current.slice(pos))) { 99 | accum += current; current = ""; 100 | try {current = source.next();} 101 | catch (e) {break;} 102 | pos = 0; 103 | str = str.slice(left); 104 | } 105 | else { 106 | break; 107 | } 108 | } 109 | 110 | if (!(found && consume)) { 111 | current = accum.slice(_accum.length) + current; 112 | pos = _pos; 113 | accum = _accum; 114 | } 115 | 116 | return found; 117 | }, 118 | 119 | // Utils built on top of the above 120 | more: function() { 121 | return this.peek() !== null; 122 | }, 123 | applies: function(test) { 124 | var next = this.peek(); 125 | return (next !== null && test(next)); 126 | }, 127 | nextWhile: function(test) { 128 | while (this.applies(test)) 129 | this.next(); 130 | }, 131 | equals: function(ch) { 132 | return ch === this.peek(); 133 | }, 134 | endOfLine: function() { 135 | var next = this.peek(); 136 | return next == null || next == "\n"; 137 | } 138 | }; 139 | }; 140 | -------------------------------------------------------------------------------- /site/static/js/tokenize.js: -------------------------------------------------------------------------------- 1 | // A framework for simple tokenizers. Takes care of newlines and 2 | // white-space, and of getting the text from the source stream into 3 | // the token object. A state is a function of two arguments -- a 4 | // string stream and a setState function. The second can be used to 5 | // change the tokenizer's state, and can be ignored for stateless 6 | // tokenizers. This function should advance the stream over a token 7 | // and return a string or object containing information about the next 8 | // token, or null to pass and have the (new) state be called to finish 9 | // the token. When a string is given, it is wrapped in a {style, type} 10 | // object. In the resulting object, the characters consumed are stored 11 | // under the content property. Any whitespace following them is also 12 | // automatically consumed, and added to the value property. (Thus, 13 | // content is the actual meaningful part of the token, while value 14 | // contains all the text it spans.) 15 | 16 | function tokenizer(source, state) { 17 | // Newlines are always a separate token. 18 | function isWhiteSpace(ch) { 19 | // The messy regexp is because IE's regexp matcher is of the 20 | // opinion that non-breaking spaces are no whitespace. 21 | return ch != "\n" && /^[\s\u00a0]*$/.test(ch); 22 | } 23 | 24 | var tokenizer = { 25 | state: state, 26 | 27 | take: function(type) { 28 | if (typeof(type) == "string") 29 | type = {style: type, type: type}; 30 | 31 | type.content = (type.content || "") + source.get(); 32 | if (!/\n$/.test(type.content)) 33 | source.nextWhile(isWhiteSpace); 34 | type.value = type.content + source.get(); 35 | return type; 36 | }, 37 | 38 | next: function () { 39 | if (!source.more()) throw StopIteration; 40 | 41 | var type; 42 | if (source.equals("\n")) { 43 | source.next(); 44 | return this.take("whitespace"); 45 | } 46 | 47 | if (source.applies(isWhiteSpace)) 48 | type = "whitespace"; 49 | else 50 | while (!type) 51 | type = this.state(source, function(s) {tokenizer.state = s;}); 52 | 53 | return this.take(type); 54 | } 55 | }; 56 | return tokenizer; 57 | } 58 | -------------------------------------------------------------------------------- /site/static/js/tokenizejavascript.js: -------------------------------------------------------------------------------- 1 | /* Tokenizer for JavaScript code */ 2 | 3 | var tokenizeJavaScript = (function() { 4 | // Advance the stream until the given character (not preceded by a 5 | // backslash) is encountered, or the end of the line is reached. 6 | function nextUntilUnescaped(source, end) { 7 | var escaped = false; 8 | var next; 9 | while (!source.endOfLine()) { 10 | var next = source.next(); 11 | if (next == end && !escaped) 12 | return false; 13 | escaped = !escaped && next == "\\"; 14 | } 15 | return escaped; 16 | } 17 | 18 | // A map of JavaScript's keywords. The a/b/c keyword distinction is 19 | // very rough, but it gives the parser enough information to parse 20 | // correct code correctly (we don't care that much how we parse 21 | // incorrect code). The style information included in these objects 22 | // is used by the highlighter to pick the correct CSS style for a 23 | // token. 24 | var keywords = function(){ 25 | function result(type, style){ 26 | return {type: type, style: "js-" + style}; 27 | } 28 | // keywords that take a parenthised expression, and then a 29 | // statement (if) 30 | var keywordA = result("keyword a", "keyword"); 31 | // keywords that take just a statement (else) 32 | var keywordB = result("keyword b", "keyword"); 33 | // keywords that optionally take an expression, and form a 34 | // statement (return) 35 | var keywordC = result("keyword c", "keyword"); 36 | var operator = result("operator", "keyword"); 37 | var atom = result("atom", "atom"); 38 | return { 39 | "if": keywordA, "while": keywordA, "with": keywordA, 40 | "else": keywordB, "do": keywordB, "try": keywordB, "finally": keywordB, 41 | "return": keywordC, "break": keywordC, "continue": keywordC, "new": keywordC, "delete": keywordC, "throw": keywordC, 42 | "in": operator, "typeof": operator, "instanceof": operator, 43 | "var": result("var", "keyword"), "function": result("function", "keyword"), "catch": result("catch", "keyword"), 44 | "for": result("for", "keyword"), "switch": result("switch", "keyword"), 45 | "case": result("case", "keyword"), "default": result("default", "keyword"), 46 | "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom 47 | }; 48 | }(); 49 | 50 | // Some helper regexp matchers. 51 | var isOperatorChar = matcher(/[+\-*&%\/=<>!?|]/); 52 | var isDigit = matcher(/[0-9]/); 53 | var isHexDigit = matcher(/[0-9A-Fa-f]/); 54 | var isWordChar = matcher(/[\w\$_]/); 55 | 56 | // Wrapper around jsToken that helps maintain parser state (whether 57 | // we are inside of a multi-line comment and whether the next token 58 | // could be a regular expression). 59 | function jsTokenState(inside, regexp) { 60 | return function(source, setState) { 61 | var newInside = inside; 62 | var type = jsToken(inside, regexp, source, function(c) {newInside = c;}); 63 | var newRegexp = type.type == "operator" || type.type == "keyword c" || type.type.match(/^[\[{}\(,;:]$/); 64 | if (newRegexp != regexp || newInside != inside) 65 | setState(jsTokenState(newInside, newRegexp)); 66 | return type; 67 | }; 68 | } 69 | 70 | // The token reader, inteded to be used by the tokenizer from 71 | // tokenize.js (through jsTokenState). Advances the source stream 72 | // over a token, and returns an object containing the type and style 73 | // of that token. 74 | function jsToken(inside, regexp, source, setInside) { 75 | function readHexNumber(){ 76 | source.next(); // skip the 'x' 77 | source.nextWhile(isHexDigit); 78 | return {type: "number", style: "js-atom"}; 79 | } 80 | 81 | function readNumber() { 82 | source.nextWhile(isDigit); 83 | if (source.equals(".")){ 84 | source.next(); 85 | source.nextWhile(isDigit); 86 | } 87 | if (source.equals("e") || source.equals("E")){ 88 | source.next(); 89 | if (source.equals("-")) 90 | source.next(); 91 | source.nextWhile(isDigit); 92 | } 93 | return {type: "number", style: "js-atom"}; 94 | } 95 | // Read a word, look it up in keywords. If not found, it is a 96 | // variable, otherwise it is a keyword of the type found. 97 | function readWord() { 98 | source.nextWhile(isWordChar); 99 | var word = source.get(); 100 | var known = keywords.hasOwnProperty(word) && keywords.propertyIsEnumerable(word) && keywords[word]; 101 | return known ? {type: known.type, style: known.style, content: word} : 102 | {type: "variable", style: "js-variable", content: word}; 103 | } 104 | function readRegexp() { 105 | nextUntilUnescaped(source, "/"); 106 | source.nextWhile(matcher(/[gi]/)); 107 | return {type: "regexp", style: "js-string"}; 108 | } 109 | // Mutli-line comments are tricky. We want to return the newlines 110 | // embedded in them as regular newline tokens, and then continue 111 | // returning a comment token for every line of the comment. So 112 | // some state has to be saved (inside) to indicate whether we are 113 | // inside a /* */ sequence. 114 | function readMultilineComment(start){ 115 | var newInside = "/*"; 116 | var maybeEnd = (start == "*"); 117 | while (true) { 118 | if (source.endOfLine()) 119 | break; 120 | var next = source.next(); 121 | if (next == "/" && maybeEnd){ 122 | newInside = null; 123 | break; 124 | } 125 | maybeEnd = (next == "*"); 126 | } 127 | setInside(newInside); 128 | return {type: "comment", style: "js-comment"}; 129 | } 130 | function readOperator() { 131 | source.nextWhile(isOperatorChar); 132 | return {type: "operator", style: "js-operator"}; 133 | } 134 | function readString(quote) { 135 | var endBackSlash = nextUntilUnescaped(source, quote); 136 | setInside(endBackSlash ? quote : null); 137 | return {type: "string", style: "js-string"}; 138 | } 139 | 140 | // Fetch the next token. Dispatches on first character in the 141 | // stream, or first two characters when the first is a slash. 142 | if (inside == "\"" || inside == "'") 143 | return readString(inside); 144 | var ch = source.next(); 145 | if (inside == "/*") 146 | return readMultilineComment(ch); 147 | else if (ch == "\"" || ch == "'") 148 | return readString(ch); 149 | // with punctuation, the type of the token is the symbol itself 150 | else if (/[\[\]{}\(\),;\:\.]/.test(ch)) 151 | return {type: ch, style: "js-punctuation"}; 152 | else if (ch == "0" && (source.equals("x") || source.equals("X"))) 153 | return readHexNumber(); 154 | else if (isDigit(ch)) 155 | return readNumber(); 156 | else if (ch == "/"){ 157 | if (source.equals("*")) 158 | { source.next(); return readMultilineComment(ch); } 159 | else if (source.equals("/")) 160 | { nextUntilUnescaped(source, null); return {type: "comment", style: "js-comment"};} 161 | else if (regexp) 162 | return readRegexp(); 163 | else 164 | return readOperator(); 165 | } 166 | else if (isOperatorChar(ch)) 167 | return readOperator(); 168 | else 169 | return readWord(); 170 | } 171 | 172 | // The external interface to the tokenizer. 173 | return function(source, startState) { 174 | return tokenizer(source, startState || jsTokenState(false, true)); 175 | }; 176 | })(); 177 | -------------------------------------------------------------------------------- /site/static/js/undo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Storage and control for undo information within a CodeMirror 3 | * editor. 'Why on earth is such a complicated mess required for 4 | * that?', I hear you ask. The goal, in implementing this, was to make 5 | * the complexity of storing and reverting undo information depend 6 | * only on the size of the edited or restored content, not on the size 7 | * of the whole document. This makes it necessary to use a kind of 8 | * 'diff' system, which, when applied to a DOM tree, causes some 9 | * complexity and hackery. 10 | * 11 | * In short, the editor 'touches' BR elements as it parses them, and 12 | * the History stores these. When nothing is touched in commitDelay 13 | * milliseconds, the changes are committed: It goes over all touched 14 | * nodes, throws out the ones that did not change since last commit or 15 | * are no longer in the document, and assembles the rest into zero or 16 | * more 'chains' -- arrays of adjacent lines. Links back to these 17 | * chains are added to the BR nodes, while the chain that previously 18 | * spanned these nodes is added to the undo history. Undoing a change 19 | * means taking such a chain off the undo history, restoring its 20 | * content (text is saved per line) and linking it back into the 21 | * document. 22 | */ 23 | 24 | // A history object needs to know about the DOM container holding the 25 | // document, the maximum amount of undo levels it should store, the 26 | // delay (of no input) after which it commits a set of changes, and, 27 | // unfortunately, the 'parent' window -- a window that is not in 28 | // designMode, and on which setTimeout works in every browser. 29 | function History(container, maxDepth, commitDelay, editor, onChange) { 30 | this.container = container; 31 | this.maxDepth = maxDepth; this.commitDelay = commitDelay; 32 | this.editor = editor; this.parent = editor.parent; 33 | this.onChange = onChange; 34 | // This line object represents the initial, empty editor. 35 | var initial = {text: "", from: null, to: null}; 36 | // As the borders between lines are represented by BR elements, the 37 | // start of the first line and the end of the last one are 38 | // represented by null. Since you can not store any properties 39 | // (links to line objects) in null, these properties are used in 40 | // those cases. 41 | this.first = initial; this.last = initial; 42 | // Similarly, a 'historyTouched' property is added to the BR in 43 | // front of lines that have already been touched, and 'firstTouched' 44 | // is used for the first line. 45 | this.firstTouched = false; 46 | // History is the set of committed changes, touched is the set of 47 | // nodes touched since the last commit. 48 | this.history = []; this.redoHistory = []; this.touched = []; 49 | } 50 | 51 | History.prototype = { 52 | // Schedule a commit (if no other touches come in for commitDelay 53 | // milliseconds). 54 | scheduleCommit: function() { 55 | this.parent.clearTimeout(this.commitTimeout); 56 | this.commitTimeout = this.parent.setTimeout(method(this, "tryCommit"), this.commitDelay); 57 | }, 58 | 59 | // Mark a node as touched. Null is a valid argument. 60 | touch: function(node) { 61 | this.setTouched(node); 62 | this.scheduleCommit(); 63 | }, 64 | 65 | // Undo the last change. 66 | undo: function() { 67 | // Make sure pending changes have been committed. 68 | this.commit(); 69 | 70 | if (this.history.length) { 71 | // Take the top diff from the history, apply it, and store its 72 | // shadow in the redo history. 73 | var item = this.history.pop(); 74 | this.redoHistory.push(this.updateTo(item, "applyChain")); 75 | if (this.onChange) this.onChange(); 76 | return this.chainNode(item); 77 | } 78 | }, 79 | 80 | // Redo the last undone change. 81 | redo: function() { 82 | this.commit(); 83 | if (this.redoHistory.length) { 84 | // The inverse of undo, basically. 85 | var item = this.redoHistory.pop(); 86 | this.addUndoLevel(this.updateTo(item, "applyChain")); 87 | if (this.onChange) this.onChange(); 88 | return this.chainNode(item); 89 | } 90 | }, 91 | 92 | clear: function() { 93 | this.history = []; 94 | this.redoHistory = []; 95 | }, 96 | 97 | // Ask for the size of the un/redo histories. 98 | historySize: function() { 99 | return {undo: this.history.length, redo: this.redoHistory.length}; 100 | }, 101 | 102 | // Push a changeset into the document. 103 | push: function(from, to, lines) { 104 | var chain = []; 105 | for (var i = 0; i < lines.length; i++) { 106 | var end = (i == lines.length - 1) ? to : this.container.ownerDocument.createElement("BR"); 107 | chain.push({from: from, to: end, text: cleanText(lines[i])}); 108 | from = end; 109 | } 110 | this.pushChains([chain], from == null && to == null); 111 | }, 112 | 113 | pushChains: function(chains, doNotHighlight) { 114 | this.commit(doNotHighlight); 115 | this.addUndoLevel(this.updateTo(chains, "applyChain")); 116 | this.redoHistory = []; 117 | }, 118 | 119 | // Retrieve a DOM node from a chain (for scrolling to it after undo/redo). 120 | chainNode: function(chains) { 121 | for (var i = 0; i < chains.length; i++) { 122 | var start = chains[i][0], node = start && (start.from || start.to); 123 | if (node) return node; 124 | } 125 | }, 126 | 127 | // Clear the undo history, make the current document the start 128 | // position. 129 | reset: function() { 130 | this.history = []; this.redoHistory = []; 131 | }, 132 | 133 | textAfter: function(br) { 134 | return this.after(br).text; 135 | }, 136 | 137 | nodeAfter: function(br) { 138 | return this.after(br).to; 139 | }, 140 | 141 | nodeBefore: function(br) { 142 | return this.before(br).from; 143 | }, 144 | 145 | // Commit unless there are pending dirty nodes. 146 | tryCommit: function() { 147 | if (this.editor.highlightDirty()) this.commit(); 148 | else this.scheduleCommit(); 149 | }, 150 | 151 | // Check whether the touched nodes hold any changes, if so, commit 152 | // them. 153 | commit: function(doNotHighlight) { 154 | this.parent.clearTimeout(this.commitTimeout); 155 | // Make sure there are no pending dirty nodes. 156 | if (!doNotHighlight) this.editor.highlightDirty(true); 157 | // Build set of chains. 158 | var chains = this.touchedChains(), self = this; 159 | 160 | if (chains.length) { 161 | this.addUndoLevel(this.updateTo(chains, "linkChain")); 162 | this.redoHistory = []; 163 | if (this.onChange) this.onChange(); 164 | } 165 | }, 166 | 167 | // [ end of public interface ] 168 | 169 | // Update the document with a given set of chains, return its 170 | // shadow. updateFunc should be "applyChain" or "linkChain". In the 171 | // second case, the chains are taken to correspond the the current 172 | // document, and only the state of the line data is updated. In the 173 | // first case, the content of the chains is also pushed iinto the 174 | // document. 175 | updateTo: function(chains, updateFunc) { 176 | var shadows = [], dirty = []; 177 | for (var i = 0; i < chains.length; i++) { 178 | shadows.push(this.shadowChain(chains[i])); 179 | dirty.push(this[updateFunc](chains[i])); 180 | } 181 | if (updateFunc == "applyChain") 182 | this.notifyDirty(dirty); 183 | return shadows; 184 | }, 185 | 186 | // Notify the editor that some nodes have changed. 187 | notifyDirty: function(nodes) { 188 | forEach(nodes, method(this.editor, "addDirtyNode")) 189 | this.editor.scheduleHighlight(); 190 | }, 191 | 192 | // Link a chain into the DOM nodes (or the first/last links for null 193 | // nodes). 194 | linkChain: function(chain) { 195 | for (var i = 0; i < chain.length; i++) { 196 | var line = chain[i]; 197 | if (line.from) line.from.historyAfter = line; 198 | else this.first = line; 199 | if (line.to) line.to.historyBefore = line; 200 | else this.last = line; 201 | } 202 | }, 203 | 204 | // Get the line object after/before a given node. 205 | after: function(node) { 206 | return node ? node.historyAfter : this.first; 207 | }, 208 | before: function(node) { 209 | return node ? node.historyBefore : this.last; 210 | }, 211 | 212 | // Mark a node as touched if it has not already been marked. 213 | setTouched: function(node) { 214 | if (node) { 215 | if (!node.historyTouched) { 216 | this.touched.push(node); 217 | node.historyTouched = true; 218 | } 219 | } 220 | else { 221 | this.firstTouched = true; 222 | } 223 | }, 224 | 225 | // Store a new set of undo info, throw away info if there is more of 226 | // it than allowed. 227 | addUndoLevel: function(diffs) { 228 | this.history.push(diffs); 229 | if (this.history.length > this.maxDepth) 230 | this.history.shift(); 231 | }, 232 | 233 | // Build chains from a set of touched nodes. 234 | touchedChains: function() { 235 | var self = this; 236 | 237 | // The temp system is a crummy hack to speed up determining 238 | // whether a (currently touched) node has a line object associated 239 | // with it. nullTemp is used to store the object for the first 240 | // line, other nodes get it stored in their historyTemp property. 241 | var nullTemp = null; 242 | function temp(node) {return node ? node.historyTemp : nullTemp;} 243 | function setTemp(node, line) { 244 | if (node) node.historyTemp = line; 245 | else nullTemp = line; 246 | } 247 | 248 | function buildLine(node) { 249 | var text = []; 250 | for (var cur = node ? node.nextSibling : self.container.firstChild; 251 | cur && cur.nodeName != "BR"; cur = cur.nextSibling) 252 | if (cur.currentText) text.push(cur.currentText); 253 | return {from: node, to: cur, text: cleanText(text.join(""))}; 254 | } 255 | 256 | // Filter out unchanged lines and nodes that are no longer in the 257 | // document. Build up line objects for remaining nodes. 258 | var lines = []; 259 | if (self.firstTouched) self.touched.push(null); 260 | forEach(self.touched, function(node) { 261 | if (node && node.parentNode != self.container) return; 262 | 263 | if (node) node.historyTouched = false; 264 | else self.firstTouched = false; 265 | 266 | var line = buildLine(node), shadow = self.after(node); 267 | if (!shadow || shadow.text != line.text || shadow.to != line.to) { 268 | lines.push(line); 269 | setTemp(node, line); 270 | } 271 | }); 272 | 273 | // Get the BR element after/before the given node. 274 | function nextBR(node, dir) { 275 | var link = dir + "Sibling", search = node[link]; 276 | while (search && search.nodeName != "BR") 277 | search = search[link]; 278 | return search; 279 | } 280 | 281 | // Assemble line objects into chains by scanning the DOM tree 282 | // around them. 283 | var chains = []; self.touched = []; 284 | forEach(lines, function(line) { 285 | // Note that this makes the loop skip line objects that have 286 | // been pulled into chains by lines before them. 287 | if (!temp(line.from)) return; 288 | 289 | var chain = [], curNode = line.from, safe = true; 290 | // Put any line objects (referred to by temp info) before this 291 | // one on the front of the array. 292 | while (true) { 293 | var curLine = temp(curNode); 294 | if (!curLine) { 295 | if (safe) break; 296 | else curLine = buildLine(curNode); 297 | } 298 | chain.unshift(curLine); 299 | setTemp(curNode, null); 300 | if (!curNode) break; 301 | safe = self.after(curNode); 302 | curNode = nextBR(curNode, "previous"); 303 | } 304 | curNode = line.to; safe = self.before(line.from); 305 | // Add lines after this one at end of array. 306 | while (true) { 307 | if (!curNode) break; 308 | var curLine = temp(curNode); 309 | if (!curLine) { 310 | if (safe) break; 311 | else curLine = buildLine(curNode); 312 | } 313 | chain.push(curLine); 314 | setTemp(curNode, null); 315 | safe = self.before(curNode); 316 | curNode = nextBR(curNode, "next"); 317 | } 318 | chains.push(chain); 319 | }); 320 | 321 | return chains; 322 | }, 323 | 324 | // Find the 'shadow' of a given chain by following the links in the 325 | // DOM nodes at its start and end. 326 | shadowChain: function(chain) { 327 | var shadows = [], next = this.after(chain[0].from), end = chain[chain.length - 1].to; 328 | while (true) { 329 | shadows.push(next); 330 | var nextNode = next.to; 331 | if (!nextNode || nextNode == end) 332 | break; 333 | else 334 | next = nextNode.historyAfter || this.before(end); 335 | // (The this.before(end) is a hack -- FF sometimes removes 336 | // properties from BR nodes, in which case the best we can hope 337 | // for is to not break.) 338 | } 339 | return shadows; 340 | }, 341 | 342 | // Update the DOM tree to contain the lines specified in a given 343 | // chain, link this chain into the DOM nodes. 344 | applyChain: function(chain) { 345 | // Some attempt is made to prevent the cursor from jumping 346 | // randomly when an undo or redo happens. It still behaves a bit 347 | // strange sometimes. 348 | var cursor = select.cursorPos(this.container, false), self = this; 349 | 350 | // Remove all nodes in the DOM tree between from and to (null for 351 | // start/end of container). 352 | function removeRange(from, to) { 353 | var pos = from ? from.nextSibling : self.container.firstChild; 354 | while (pos != to) { 355 | var temp = pos.nextSibling; 356 | removeElement(pos); 357 | pos = temp; 358 | } 359 | } 360 | 361 | var start = chain[0].from, end = chain[chain.length - 1].to; 362 | // Clear the space where this change has to be made. 363 | removeRange(start, end); 364 | 365 | // Insert the content specified by the chain into the DOM tree. 366 | for (var i = 0; i < chain.length; i++) { 367 | var line = chain[i]; 368 | // The start and end of the space are already correct, but BR 369 | // tags inside it have to be put back. 370 | if (i > 0) 371 | self.container.insertBefore(line.from, end); 372 | 373 | // Add the text. 374 | var node = makePartSpan(fixSpaces(line.text), this.container.ownerDocument); 375 | self.container.insertBefore(node, end); 376 | // See if the cursor was on this line. Put it back, adjusting 377 | // for changed line length, if it was. 378 | if (cursor && cursor.node == line.from) { 379 | var cursordiff = 0; 380 | var prev = this.after(line.from); 381 | if (prev && i == chain.length - 1) { 382 | // Only adjust if the cursor is after the unchanged part of 383 | // the line. 384 | for (var match = 0; match < cursor.offset && 385 | line.text.charAt(match) == prev.text.charAt(match); match++); 386 | if (cursor.offset > match) 387 | cursordiff = line.text.length - prev.text.length; 388 | } 389 | select.setCursorPos(this.container, {node: line.from, offset: Math.max(0, cursor.offset + cursordiff)}); 390 | } 391 | // Cursor was in removed line, this is last new line. 392 | else if (cursor && (i == chain.length - 1) && cursor.node && cursor.node.parentNode != this.container) { 393 | select.setCursorPos(this.container, {node: line.from, offset: line.text.length}); 394 | } 395 | } 396 | 397 | // Anchor the chain in the DOM tree. 398 | this.linkChain(chain); 399 | return start; 400 | } 401 | }; 402 | -------------------------------------------------------------------------------- /site/static/js/util.js: -------------------------------------------------------------------------------- 1 | /* A few useful utility functions. */ 2 | 3 | var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent); 4 | var webkit = /AppleWebKit/.test(navigator.userAgent); 5 | 6 | // Capture a method on an object. 7 | function method(obj, name) { 8 | return function() {obj[name].apply(obj, arguments);}; 9 | } 10 | 11 | // The value used to signal the end of a sequence in iterators. 12 | var StopIteration = {toString: function() {return "StopIteration"}}; 13 | 14 | // Apply a function to each element in a sequence. 15 | function forEach(iter, f) { 16 | if (iter.next) { 17 | try {while (true) f(iter.next());} 18 | catch (e) {if (e != StopIteration) throw e;} 19 | } 20 | else { 21 | for (var i = 0; i < iter.length; i++) 22 | f(iter[i]); 23 | } 24 | } 25 | 26 | // Map a function over a sequence, producing an array of results. 27 | function map(iter, f) { 28 | var accum = []; 29 | forEach(iter, function(val) {accum.push(f(val));}); 30 | return accum; 31 | } 32 | 33 | // Create a predicate function that tests a string againsts a given 34 | // regular expression. 35 | function matcher(regexp){ 36 | return function(value){return regexp.test(value);}; 37 | } 38 | 39 | // Test whether a DOM node has a certain CSS class. Much faster than 40 | // the MochiKit equivalent, for some reason. 41 | function hasClass(element, className){ 42 | var classes = element.className; 43 | return classes && new RegExp("(^| )" + className + "($| )").test(classes); 44 | } 45 | 46 | // Insert a DOM node after another node. 47 | function insertAfter(newNode, oldNode) { 48 | var parent = oldNode.parentNode; 49 | parent.insertBefore(newNode, oldNode.nextSibling); 50 | return newNode; 51 | } 52 | 53 | function removeElement(node) { 54 | if (node.parentNode) 55 | node.parentNode.removeChild(node); 56 | } 57 | 58 | function clearElement(node) { 59 | while (node.firstChild) 60 | node.removeChild(node.firstChild); 61 | } 62 | 63 | // Check whether a node is contained in another one. 64 | function isAncestor(node, child) { 65 | while (child = child.parentNode) { 66 | if (node == child) 67 | return true; 68 | } 69 | return false; 70 | } 71 | 72 | // The non-breaking space character. 73 | var nbsp = "\u00a0"; 74 | var matching = {"{": "}", "[": "]", "(": ")", 75 | "}": "{", "]": "[", ")": "("}; 76 | 77 | // Standardize a few unportable event properties. 78 | function normalizeEvent(event) { 79 | if (!event.stopPropagation) { 80 | event.stopPropagation = function() {this.cancelBubble = true;}; 81 | event.preventDefault = function() {this.returnValue = false;}; 82 | } 83 | if (!event.stop) { 84 | event.stop = function() { 85 | this.stopPropagation(); 86 | this.preventDefault(); 87 | }; 88 | } 89 | 90 | if (event.type == "keypress") { 91 | event.code = (event.charCode == null) ? event.keyCode : event.charCode; 92 | event.character = String.fromCharCode(event.code); 93 | } 94 | return event; 95 | } 96 | 97 | // Portably register event handlers. 98 | function addEventHandler(node, type, handler, removeFunc) { 99 | function wrapHandler(event) { 100 | handler(normalizeEvent(event || window.event)); 101 | } 102 | if (typeof node.addEventListener == "function") { 103 | node.addEventListener(type, wrapHandler, false); 104 | if (removeFunc) return function() {node.removeEventListener(type, wrapHandler, false);}; 105 | } 106 | else { 107 | node.attachEvent("on" + type, wrapHandler); 108 | if (removeFunc) return function() {node.detachEvent("on" + type, wrapHandler);}; 109 | } 110 | } 111 | 112 | function nodeText(node) { 113 | return node.innerText || node.textContent || node.nodeValue || ""; 114 | } 115 | -------------------------------------------------------------------------------- /site/static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /site/static/style.css: -------------------------------------------------------------------------------- 1 | body { font-family: Helvetica,Arial,sans-serif; font-size: smaller;} 2 | #header h1 { display: block; margin: 0;} 3 | #header { border-bottom: 2px solid darkblue; padding: 5px; padding-bottom: 1px; background-color: lightblue;} 4 | #wrapper { margin-left: auto; margin-right: auto; width: 800px;} 5 | #content { margin: 10px; } 6 | pre { font-size: larger;} 7 | ul { padding-left: 20px;} 8 | #make-form { margin: 20px; text-align: center; margin-right: 80px;} 9 | #make-form input { font-size: larger;} 10 | #footer { 11 | margin-top: 20px; 12 | border-top: 1px solid darkblue; 13 | background-color: lightblue; 14 | padding: 5px; 15 | padding-left: 10px; 16 | } 17 | #footer a:visited { color: blue;} -------------------------------------------------------------------------------- /site/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% block title %}Scriptlets - Quick web scripts{% endblock %} 4 | 5 | 6 | 7 | 10 | {% block head %}{% endblock %} 11 | 12 | 13 | 23 |
24 | {% block content %}{% endblock %} 25 |
26 | 31 | 38 | 42 | 54 | 55 | -------------------------------------------------------------------------------- /site/templates/new.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 | 7 |
8 | 11 | to get a URL that will run this code. 12 |
13 | {% endblock %} -------------------------------------------------------------------------------- /site/templates/not_authorized.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |

Not authorized

4 | {% endblock %} -------------------------------------------------------------------------------- /site/templates/not_found.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |

Not found

4 | {% endblock %} -------------------------------------------------------------------------------- /site/templates/script.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 | 7 |
8 | 10 | to get a URL that will run this code. 11 |
12 | {% endblock %} --------------------------------------------------------------------------------