diff --git a/.gitignore b/.gitignore index 4aa1d556..16597177 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ HOWTO-web2py-devel *.sublime-project *.sublime-workspace .idea/* +site-packages/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..71525aff --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "gluon/packages/dal"] + path = gluon/packages/dal + url = https://github.com/web2py/pydal.git diff --git a/.travis.yml b/.travis.yml index ac5914e4..99bed1e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,62 +1,30 @@ language: python +sudo: false + +cache: + directories: + - $HOME/.pip-cache/ + python: - '2.6' - '2.7' - 'pypy' + install: - pip install -e . -env: - - DB=sqlite:memory - - DB=mysql://root:@localhost/test_w2p - - DB=postgres://postgres:@localhost/test_w2p - - DB=google:datastore -# - DB=google:datastore+ndb - - DB=mongodb://mongodb:mongodb@localhost/test_w2p - - DB=imap://imap:imap@localhost:993 + before_script: - - if [[ $TRAVIS_PYTHON_VERSION != '2.7' ]]; then pip install unittest2; fi - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install coverage; fi; - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install python-coveralls; fi - - if [[ $DB == postgres* ]]; then pip install psycopg2; fi; - - if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install pysqlite; fi - - if [[ $DB == mysql* ]]; then mysql -e 'create database test_w2p;'; fi - - if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; fi - - if [[ $DB == postgres* ]]; then psql -c 'create extension postgis;' -U postgres -d test_w2p; fi - - - # Install last sdk for app engine (update only whenever a new release is available) - - if [[ $DB == google* ]]; then wget http://googleappengine.googlecode.com/files/google_appengine_1.8.9.zip -nv; fi - - if [[ $DB == google* ]]; then unzip -q google_appengine_1.8.9.zip; fi - - if [[ $DB == google* ]]; then mv -f ./google_appengine/google ./google; fi - - - if [[ $DB == mongodb* ]]; then pip install pymongo; fi - - if [[ $DB == mongodb* ]]; then mongo test_w2p --eval 'db.addUser("mongodb", "mongodb");'; fi - - #Temporal solution to travis issue #155 - - sudo chmod 777 /dev/shm - - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm -matrix: - exclude: - - python: 'pypy' - env: DB=postgres://postgres:@localhost/test_w2p - - python: 'pypy' - env: DB=mysql://root:@localhost/test_w2p - - python: 'pypy' - env: DB=google:datastore - - python: '2.6' - env: DB=google:datastore -# - python: '2.6' -# env: DB=google:datastore+ndb + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --download-cache $HOME/.pip-cache unittest2; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache coverage; fi; + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache codecov; fi script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage + after_success: - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coverage combine; fi - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --config_file=gluon/tests/coverage.ini; fi - + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then codecov; fi + notifications: email: true - -services: mongodb - diff --git a/CHANGELOG b/CHANGELOG index f6329e6b..a1efbb0a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,24 @@ +## 2.11.1 + +- Many small but significative improvements and bug fixes + +## 2.10.1-2.10.2 + +- welcome app defaults to Bootstrap 3 +- DAL -> pyDAL (thanks Giovanni, Niphlod, Paolo) + - new modular dal + - fixed problems with GAE support + - moved to full NDB support + - improved connection pooling logic +- optional cache.ram.max_ram_utilization = 90 (experimental) +- improved cache.disk logic (thanks Niphlod and Leonel) +- lots of pep8 improvements, thanks Richard +- added support for email attchments when auth.settings.server='gae' +- fixed app.yaml.example for GAE +- fixed many small issues +- many many more tests (thanks Giovanni, Niphlod, Paolo) +- upgrading static libraries (bootstrap, codemirror, jquery, etc) + ## 2.9.12 - Tornado HTTPS support, thanks Diego diff --git a/Makefile b/Makefile index 42f7375b..e16e0967 100644 --- a/Makefile +++ b/Makefile @@ -17,11 +17,6 @@ clean: find ./applications/examples/ -name '.*' -exec rm -f {} \; find ./applications/welcome/ -name '.*' -exec rm -f {} \; find ./ -name '*.pyc' -exec rm -f {} \; -epydoc: - ### build epydoc - rm -f -r applications/examples/static/epydoc/ - epydoc --config extras/epydoc/epydoc.conf - cp applications/examples/static/title.png applications/examples/static/epydoc tests: python web2py.py --run_system_tests coverage: @@ -37,7 +32,7 @@ update: echo "remember that pymysql was tweaked" src: ### Use semantic versioning - echo 'Version 2.9.12-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION + echo 'Version 2.11.2-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION ### rm -f all junk files make clean ### clean up baisc apps @@ -51,8 +46,6 @@ src: rm -rf applications/admin/uploads/* rm -rf applications/welcome/uploads/* rm -rf applications/examples/uploads/* - ### NO MORE make epydoc - # make epydoc ### make welcome layout and appadmin the default cp applications/welcome/views/appadmin.html applications/admin/views cp applications/welcome/views/appadmin.html applications/examples/views @@ -61,7 +54,7 @@ src: ### build web2py_src.zip echo '' > NEWINSTALL mv web2py_src.zip web2py_src_old.zip | echo 'no old' - cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/*.py web2py/gluon/dal/* web2py/gluon/contrib/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py + cd ..; zip -r web2py/web2py_src.zip web2py/web2py.py web2py/anyserver.py web2py/gluon/* web2py/extras/* web2py/handlers/* web2py/examples/* web2py/README.markdown web2py/LICENSE web2py/CHANGELOG web2py/NEWINSTALL web2py/VERSION web2py/MANIFEST.in web2py/scripts/*.sh web2py/scripts/*.py web2py/applications/admin web2py/applications/examples/ web2py/applications/welcome web2py/applications/__init__.py web2py/site-packages/__init__.py web2py/gluon/tests/*.sh web2py/gluon/tests/*.py mdp: make src @@ -73,8 +66,8 @@ app: #cd ../web2py_osx/site-packages/; unzip ../site-packages.zip #find gluon -path '*.pyc' -exec cp {} ../web2py_osx/site-packages/{} \; #cd ../web2py_osx/site-packages/; zip -r ../site-packages.zip * - #mv ../web2py_osx/site-packages.zip ../web2py_osx/web2py/web2py.app/Contents/Resources/lib/python2.7 - find gluon -path '*.py' -exec cp -R {} ../web2py_osx/web2py/web2py.app/Contents/Resources/{} \; + cp ../web2py_osx/site-packages.zip ../web2py_osx/web2py/web2py.app/Contents/Resources/lib/python2.7 + find gluon -path '*.py' -exec cp -r --parents {} ../web2py_osx/web2py/web2py.app/Contents/Resources/ \; cp README.markdown ../web2py_osx/web2py/web2py.app/Contents/Resources cp NEWINSTALL ../web2py_osx/web2py/web2py.app/Contents/Resources cp LICENSE ../web2py_osx/web2py/web2py.app/Contents/Resources @@ -82,7 +75,6 @@ app: cp CHANGELOG ../web2py_osx/web2py/web2py.app/Contents/Resources cp -r extras ../web2py_osx/web2py/web2py.app/Contents/Resources cp -r examples ../web2py_osx/web2py/web2py.app/Contents/Resources - cp -r handlers ../web2py_osx/web2py/web2py.app/Contents/Resources cp -r applications/admin ../web2py_osx/web2py/web2py.app/Contents/Resources/applications cp -r applications/welcome ../web2py_osx/web2py/web2py.app/Contents/Resources/applications cp -r applications/examples ../web2py_osx/web2py/web2py.app/Contents/Resources/applications @@ -91,22 +83,19 @@ app: mv ../web2py_osx/web2py_osx.zip . win: - python2.7 -c 'import compileall; compileall.compile_dir("gluon/")' - #cd ../web2py_win/library/; unzip ../library.zip - find gluon -path '*.pyc' -exec cp -R {} ../web2py_win/library/{} \; - cd ../web2py_win/library/; zip -r ../library.zip * - mv ../web2py_win/library.zip ../web2py_win/web2py + #cd ../web2py_win/library/; zip -r ../library.zip * + cp ../web2py_win/library.zip ../web2py_win/web2py + find gluon -path '*.py' -exec cp -r --parents {} ../web2py_win/web2py/ \; cp README.markdown ../web2py_win/web2py/ cp NEWINSTALL ../web2py_win/web2py/ cp LICENSE ../web2py_win/web2py/ cp VERSION ../web2py_win/web2py/ cp CHANGELOG ../web2py_win/web2py/ - cp -R extras ../web2py_win/web2py/ - cp -R examples ../web2py_win/web2py/ - cp -R handlers ../web2py_win/web2py/ - cp -R applications/admin ../web2py_win/web2py/applications - cp -R applications/welcome ../web2py_win/web2py/applications - cp -R applications/examples ../web2py_win/web2py/applications + cp -r extras ../web2py_win/web2py/ + cp -r examples ../web2py_win/web2py/ + cp -r applications/admin ../web2py_win/web2py/applications + cp -r applications/welcome ../web2py_win/web2py/applications + cp -r applications/examples ../web2py_win/web2py/applications cp applications/__init__.py ../web2py_win/web2py/applications cd ../web2py_win; zip -r web2py_win.zip web2py mv ../web2py_win/web2py_win.zip . diff --git a/README.markdown b/README.markdown index 306d5601..1c906755 100644 --- a/README.markdown +++ b/README.markdown @@ -13,15 +13,35 @@ Learn more at http://web2py.com Then edit ./app.yaml and replace "yourappname" with yourappname. +## Important reminder about this GIT repo + +An important part of web2py is the Database Abstraction Layer (DAL). In early 2015 this was decoupled into a separate code-base (PyDAL). In terms of git, it is a sub-module of the main repository. + +The use of a sub-module requires a one-time use of the --recursive flag for git clone if you are cloning web2py from scratch. + + git clone --recursive https://github.com/web2py/web2py.git + +If you have an existing repository, the commands below need to be executed at least once: + + git submodule update --init --recursive + +If you have a folder gluon/dal you must remove it: + + rm -r gluon/dal + +PyDAL uses a separate stable release cycle to the rest of web2py. PyDAL releases will use a date-naming scheme similar to Ubuntu. Issues related to PyDAL should be reported to its separate repository. + + ## Documentation (readthedocs.org) [![Docs Status](https://readthedocs.org/projects/web2py/badge/?version=latest&style=flat-square)](http://web2py.rtfd.org/) ## Tests -[![Build Status](https://img.shields.io/travis/web2py/web2py.svg?style=flat-square)](https://travis-ci.org/web2py/web2py) +[![Build Status](https://img.shields.io/travis/web2py/web2py/master.svg?style=flat-square&label=Travis-CI)](https://travis-ci.org/web2py/web2py) +[![MS Build Status](https://img.shields.io/appveyor/ci/web2py/web2py/master.svg?style=flat-square&label=Appveyor-CI)](https://ci.appveyor.com/project/web2py/web2py) +[![Coverage Status](https://img.shields.io/codecov/c/github/web2py/web2py.svg?style=flat-square)](https://codecov.io/github/web2py/web2py) -[![Coverage Status](https://img.shields.io/coveralls/web2py/web2py.svg?style=flat-square)](https://coveralls.io/r/web2py/web2py) ## Installation Instructions @@ -41,8 +61,10 @@ That's it!!! anyserver.py > to run with third party servers ... > other handlers and example files gluon/ > the core libraries + packages/ > web2py submodules + dal/ contrib/ > third party libraries - tests/ > unittests + tests/ > unittests applications/ > are the apps admin/ > web based IDE ... @@ -75,4 +97,4 @@ That's it!!! ## Issues? -Report issues at http://code.google.com/p/web2py/issues/ +Report issues at https://github.com/web2py/web2py/issues diff --git a/VERSION b/VERSION index 3af04ccf..4b49a8a7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -Version 2.9.12-stable+timestamp.2015.01.17.00.07.04 +Version 2.11.2-stable+timestamp.2015.05.30.11.29.46 diff --git a/anyserver.py b/anyserver.py index a7d7eec8..a500922a 100644 --- a/anyserver.py +++ b/anyserver.py @@ -180,6 +180,11 @@ class Servers: s = wsgi.WSGIServer(callable=app, bind="%s:%d" % address) s.start() + @staticmethod + def waitress(app, address, **options): + from waitress import serve + serve(app, host=address[0], port=address[1], _quiet=True) + def mongrel2_handler(application, conn, debug=False): """ diff --git a/applications/admin/controllers/appadmin.py b/applications/admin/controllers/appadmin.py index c2d5836c..4d208944 100644 --- a/applications/admin/controllers/appadmin.py +++ b/applications/admin/controllers/appadmin.py @@ -49,7 +49,8 @@ if request.function == 'manage': auth.table_group(), auth.table_permission()]) manager_role = manager_action.get('role', None) if manager_action else None - auth.requires_membership(manager_role)(lambda: None)() + if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)): + raise HTTP(403, "Not authorized") menu = False elif (request.application == 'admin' and not session.authorized) or \ (request.application != 'admin' and not gluon.fileutils.check_credentials(request)): @@ -80,7 +81,6 @@ if False and request.tickets_db: def get_databases(request): dbs = {} for (key, value) in global_env.items(): - cond = False try: cond = isinstance(value, GQLDB) except: @@ -420,7 +420,7 @@ def ccache(): 'oldest': time.time(), 'keys': [] } - + disk = copy.copy(ram) total = copy.copy(ram) disk['keys'] = [] @@ -480,12 +480,12 @@ def ccache(): disk['oldest'] = value[0] disk['keys'].append((key, GetInHMS(time.time() - value[0]))) - total['entries'] = ram['entries'] + disk['entries'] - total['bytes'] = ram['bytes'] + disk['bytes'] - total['objects'] = ram['objects'] + disk['objects'] - total['hits'] = ram['hits'] + disk['hits'] - total['misses'] = ram['misses'] + disk['misses'] - total['keys'] = ram['keys'] + disk['keys'] + ram_keys = ram.keys() # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses'] + ram_keys.remove('ratio') + ram_keys.remove('oldest') + for key in ram_keys: + total[key] = ram[key] + disk[key] + try: total['ratio'] = total['hits'] * 100 / (total['hits'] + total['misses']) @@ -577,9 +577,7 @@ def bg_graph_model(): group = meta_graphmodel['group'].replace(' ', '') if not subgraphs.has_key(group): subgraphs[group] = dict(meta=meta_graphmodel, tables=[]) - subgraphs[group]['tables'].append(tablename) - else: - subgraphs[group]['tables'].append(tablename) + subgraphs[group]['tables'].append(tablename) graph.add_node(tablename, name=tablename, shape='plaintext', label=table_template(tablename)) @@ -672,16 +670,20 @@ def hooks(): functions = [] for f in getattr(db[t], op): if hasattr(f, '__call__'): - if isinstance(f, (functools.partial)): - f = f.func - filename = inspect.getsourcefile(f) - details = {'funcname':f.__name__, - 'filename':filename[len(request.folder):] if request.folder in filename else None, - 'lineno': inspect.getsourcelines(f)[1]} - if details['filename']: # Built in functions as delete_uploaded_files are not editable - details['url'] = URL(a='admin',c='default',f='edit', args=[request['application'], details['filename']],vars={'lineno':details['lineno']}) - if details['filename'] or with_build_it: - functions.append(details) + try: + if isinstance(f, (functools.partial)): + f = f.func + filename = inspect.getsourcefile(f) + details = {'funcname':f.__name__, + 'filename':filename[len(request.folder):] if request.folder in filename else None, + 'lineno': inspect.getsourcelines(f)[1]} + if details['filename']: # Built in functions as delete_uploaded_files are not editable + details['url'] = URL(a='admin',c='default',f='edit', args=[request['application'], details['filename']],vars={'lineno':details['lineno']}) + if details['filename'] or with_build_it: + functions.append(details) + # compiled app and windows build don't support code inspection + except: + pass if len(functions): method_hooks.append({'name':op, 'functions':functions}) if len(method_hooks): diff --git a/applications/admin/controllers/debug.py b/applications/admin/controllers/debug.py index 8c8522ac..7176e09c 100644 --- a/applications/admin/controllers/debug.py +++ b/applications/admin/controllers/debug.py @@ -220,7 +220,7 @@ def list_breakpoints(): "Return a list of linenumbers for current breakpoints" breakpoints = [] - ok = None + ok = False try: filename = os.path.join(request.env['applications_parent'], 'applications', request.vars.filename) @@ -235,5 +235,4 @@ def list_breakpoints(): ok = True except Exception, e: session.flash = str(e) - ok = False return response.json({'ok': ok, 'breakpoints': breakpoints}) diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py index 4623c517..3e615c1e 100644 --- a/applications/admin/controllers/default.py +++ b/applications/admin/controllers/default.py @@ -292,9 +292,6 @@ def site(): log_progress(appname) session.flash = T(msg, dict(appname=appname, digest=md5_hash(installed))) - elif f and form_update.vars.overwrite: - msg = 'unable to install application "%(appname)s"' - session.flash = T(msg, dict(appname=form_update.vars.name)) else: msg = 'unable to install application "%(appname)s"' session.flash = T(msg, dict(appname=form_update.vars.name)) @@ -370,25 +367,56 @@ def pack_plugin(): session.flash = T('internal error') redirect(URL('plugin', args=request.args)) + + +def pack_exe(app, base, filenames=None): + import urllib + import zipfile + from cStringIO import StringIO + # Download latest web2py_win and open it with zipfile + download_url = 'http://www.web2py.com/examples/static/web2py_win.zip' + out = StringIO() + out.write(urllib.urlopen(download_url).read()) + web2py_win = zipfile.ZipFile(out, mode='a') + # Write routes.py with the application as default + routes = u'# -*- coding: utf-8 -*-\nrouters = dict(BASE=dict(default_application="%s"))' % app + web2py_win.writestr('web2py/routes.py', routes.encode('utf-8')) + # Copy the application into the zipfile + common_root = os.path.dirname(base) + for filename in filenames: + fname = os.path.join(base, filename) + arcname = os.path.join('web2py/applications', app, filename) + web2py_win.write(fname, arcname) + web2py_win.close() + response.headers['Content-Type'] = 'application/zip' + response.headers['Content-Disposition'] = 'attachment; filename=web2py.app.%s.zip' % app + out.seek(0) + return response.stream(out) + + def pack_custom(): app = get_app() base = apath(app, r=request) if request.post_vars.file: + files = request.post_vars.file files = [files] if not isinstance(files,list) else files - fname = 'web2py.app.%s.w2p' % app - try: - filename = app_pack(app, request, raise_ex=True, filenames=files) - except Exception, e: - filename = None - if filename: - response.headers['Content-Type'] = 'application/w2p' - disposition = 'attachment; filename=%s' % fname - response.headers['Content-Disposition'] = disposition - return safe_read(filename, 'rb') + if request.post_vars.doexe is None: + fname = 'web2py.app.%s.w2p' % app + try: + filename = app_pack(app, request, raise_ex=True, filenames=files) + except Exception, e: + filename = None + if filename: + response.headers['Content-Type'] = 'application/w2p' + disposition = 'attachment; filename=%s' % fname + response.headers['Content-Disposition'] = disposition + return safe_read(filename, 'rb') + else: + session.flash = T('internal error: %s', e) + redirect(URL(args=request.args)) else: - session.flash = T('internal error: %s', e) - redirect(URL(args=request.args)) + return pack_exe(app, base, files) def ignore(fs): return [f for f in fs if not ( f[:1] in '#' or f.endswith('~') or f.endswith('.bak'))] @@ -744,7 +772,7 @@ def edit(): viewlist.append(aviewpath + '.html') if len(viewlist): editviewlinks = [] - for v in viewlist: + for v in sorted(viewlist): vf = os.path.split(v)[-1] vargs = "/".join([viewpath.replace(os.sep, "/"), vf]) editviewlinks.append(A(vf.split(".")[0], @@ -754,6 +782,7 @@ def edit(): if len(request.args) > 2 and request.args[1] == 'controllers': controller = (request.args[2])[:-3] functions = find_exposed_functions(data) + functions = functions and sorted(functions) or [] else: (controller, functions) = (None, None) @@ -866,13 +895,9 @@ def resolve(): def getclass(item): """ Determine item class """ - - if item[0] == ' ': - return 'normal' - if item[0] == '+': - return 'plus' - if item[0] == '-': - return 'minus' + operators = {' ':'normal', '+':'plus', '-':'minus'} + + return operators[item[0]] if request.vars: c = '\n'.join([item[2:].rstrip() for (i, item) in enumerate(d) if item[0] @@ -1067,7 +1092,7 @@ def design(): for c in controllers: data = safe_read(apath('%s/controllers/%s' % (app, c), r=request)) items = find_exposed_functions(data) - functions[c] = items + functions[c] = items and sorted(items) or [] # Get all views views = sorted( @@ -1205,7 +1230,7 @@ def plugin(): for c in controllers: data = safe_read(apath('%s/controllers/%s' % (app, c), r=request)) items = find_exposed_functions(data) - functions[c] = items + functions[c] = items and sorted(items) or [] # Get all views views = sorted( @@ -1427,14 +1452,13 @@ def create_file(): if request.vars.dir: response.flash = result response.headers['web2py-component-content'] = 'append' - response.headers['web2py-component-command'] = """ - $.web2py.invalidate('#files_menu'); - load_file('%s'); - $.web2py.enableElement($('#form form').find($.web2py.formInputClickSelector)); - """ % URL('edit', args=[app,request.vars.dir,filename]) + response.headers['web2py-component-command'] = "%s %s %s" % ( + "$.web2py.invalidate('#files_menu');", + "load_file('%s');" % URL('edit', args=[app,request.vars.dir,filename]), + "$.web2py.enableElement($('#form form').find($.web2py.formInputClickSelector));") return '' else: - redirect(request.vars.sender + anchor) + redirect(request.vars.sender + anchor) def listfiles(app, dir, regexp='.*\.py$'): @@ -1510,7 +1534,7 @@ def upload_file(): if filename: d = dict(filename=filename[len(path):]) else: - d = dict(filename='unkown') + d = dict(filename='unknown') session.flash = T('cannot upload file "%(filename)s"', d) redirect(request.vars.sender) diff --git a/applications/admin/models/0.py b/applications/admin/models/0.py index adf95f7f..d9f8d273 100644 --- a/applications/admin/models/0.py +++ b/applications/admin/models/0.py @@ -47,3 +47,4 @@ if 'adminLanguage' in request.cookies and not (request.cookies['adminLanguage'] #set static_version from gluon.settings import global_settings response.static_version = global_settings.web2py_version.split('-')[0] +response.static_version_urls = True diff --git a/applications/admin/static/codemirror/CONTRIBUTING.md b/applications/admin/static/codemirror/CONTRIBUTING.md deleted file mode 100644 index 8938f620..00000000 --- a/applications/admin/static/codemirror/CONTRIBUTING.md +++ /dev/null @@ -1,72 +0,0 @@ -# How to contribute - -- [Getting help](#getting-help-) -- [Submitting bug reports](#submitting-bug-reports-) -- [Contributing code](#contributing-code-) - -## Getting help - -Community discussion, questions, and informal bug reporting is done on the -[CodeMirror Google group](http://groups.google.com/group/codemirror). - -## Submitting bug reports - -The preferred way to report bugs is to use the -[GitHub issue tracker](http://github.com/marijnh/CodeMirror/issues). Before -reporting a bug, read these pointers. - -**Note:** The issue tracker is for *bugs*, not requests for help. Questions -should be asked on the -[CodeMirror Google group](http://groups.google.com/group/codemirror) instead. - -### Reporting bugs effectively - -- CodeMirror is maintained by volunteers. They don't owe you anything, so be - polite. Reports with an indignant or belligerent tone tend to be moved to the - bottom of the pile. - -- Include information about **the browser in which the problem occurred**. Even - if you tested several browsers, and the problem occurred in all of them, - mention this fact in the bug report. Also include browser version numbers and - the operating system that you're on. - -- Mention which release of CodeMirror you're using. Preferably, try also with - the current development snapshot, to ensure the problem has not already been - fixed. - -- Mention very precisely what went wrong. "X is broken" is not a good bug - report. What did you expect to happen? What happened instead? Describe the - exact steps a maintainer has to take to make the problem occur. We can not - fix something that we can not observe. - -- If the problem can not be reproduced in any of the demos included in the - CodeMirror distribution, please provide an HTML document that demonstrates - the problem. The best way to do this is to go to - [jsbin.com](http://jsbin.com/ihunin/edit), enter it there, press save, and - include the resulting link in your bug report. - -## Contributing code - -- Make sure you have a [GitHub Account](https://github.com/signup/free) -- Fork [CodeMirror](https://github.com/marijnh/CodeMirror/) - ([how to fork a repo](https://help.github.com/articles/fork-a-repo)) -- Make your changes -- If your changes are easy to test or likely to regress, add tests. - Tests for the core go into `test/test.js`, some modes have their own - test suite under `mode/XXX/test.js`. Feel free to add new test - suites to modes that don't have one yet (be sure to link the new - tests into `test/index.html`). -- Follow the general code style of the rest of the project (see - below). Run `bin/lint` to verify that the linter is happy. -- Make sure all tests pass. Visit `test/index.html` in your browser to - run them. -- Submit a pull request -([how to create a pull request](https://help.github.com/articles/fork-a-repo)) - -### Coding standards - -- 2 spaces per indentation level, no tabs. -- Include semicolons after statements. -- Note that the linter (`bin/lint`) which is run after each commit - complains about unused variables and functions. Prefix their names - with an underscore to muffle it. diff --git a/applications/admin/static/codemirror/LICENSE b/applications/admin/static/codemirror/LICENSE deleted file mode 100644 index 442d11cd..00000000 --- a/applications/admin/static/codemirror/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (C) 2013 by Marijn Haverbeke and others - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/applications/admin/static/codemirror/README.md b/applications/admin/static/codemirror/README.md deleted file mode 100644 index 61f6b645..00000000 --- a/applications/admin/static/codemirror/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# CodeMirror -[![Build Status](https://secure.travis-ci.org/marijnh/CodeMirror.png?branch=master)](http://travis-ci.org/marijnh/CodeMirror) -[![NPM version](https://badge.fury.io/js/codemirror.png)](http://badge.fury.io/js/codemirror) - -CodeMirror is a JavaScript component that provides a code editor in -the browser. When a mode is available for the language you are coding -in, it will color your code, and optionally help with indentation. - -The project page is http://codemirror.net -The manual is at http://codemirror.net/doc/manual.html -The contributing guidelines are in [CONTRIBUTING.md](https://github.com/marijnh/CodeMirror/blob/master/CONTRIBUTING.md) diff --git a/applications/admin/static/codemirror/addon/comment/comment.js b/applications/admin/static/codemirror/addon/comment/comment.js index 1eb9a05c..2dd114d3 100644 --- a/applications/admin/static/codemirror/addon/comment/comment.js +++ b/applications/admin/static/codemirror/addon/comment/comment.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -106,7 +109,7 @@ CodeMirror.defineExtension("uncomment", function(from, to, options) { if (!options) options = noOptions; var self = this, mode = self.getModeAt(from); - var end = Math.min(to.line, self.lastLine()), start = Math.min(from.line, end); + var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end); // Try finding line comments var lineString = options.lineComment || mode.lineComment, lines = []; @@ -150,6 +153,17 @@ !/comment/.test(self.getTokenTypeAt(Pos(end, close + 1)))) return false; + // Avoid killing block comments completely outside the selection. + // Positions of the last startString before the start of the selection, and the first endString after it. + var lastStart = startLine.lastIndexOf(startString, from.ch); + var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length); + if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false; + // Positions of the first endString after the end of the selection, and the last startString before it. + firstEnd = endLine.indexOf(endString, to.ch); + var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch); + lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart; + if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false; + self.operation(function() { self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)), Pos(end, close + endString.length)); diff --git a/applications/admin/static/codemirror/addon/comment/continuecomment.js b/applications/admin/static/codemirror/addon/comment/continuecomment.js new file mode 100644 index 00000000..b11d51e6 --- /dev/null +++ b/applications/admin/static/codemirror/addon/comment/continuecomment.js @@ -0,0 +1,85 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + var modes = ["clike", "css", "javascript"]; + + for (var i = 0; i < modes.length; ++i) + CodeMirror.extendMode(modes[i], {blockCommentContinue: " * "}); + + function continueComment(cm) { + if (cm.getOption("disableInput")) return CodeMirror.Pass; + var ranges = cm.listSelections(), mode, inserts = []; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].head, token = cm.getTokenAt(pos); + if (token.type != "comment") return CodeMirror.Pass; + var modeHere = CodeMirror.innerMode(cm.getMode(), token.state).mode; + if (!mode) mode = modeHere; + else if (mode != modeHere) return CodeMirror.Pass; + + var insert = null; + if (mode.blockCommentStart && mode.blockCommentContinue) { + var end = token.string.indexOf(mode.blockCommentEnd); + var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found; + if (end != -1 && end == token.string.length - mode.blockCommentEnd.length && pos.ch >= end) { + // Comment ended, don't continue it + } else if (token.string.indexOf(mode.blockCommentStart) == 0) { + insert = full.slice(0, token.start); + if (!/^\s*$/.test(insert)) { + insert = ""; + for (var j = 0; j < token.start; ++j) insert += " "; + } + } else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 && + found + mode.blockCommentContinue.length > token.start && + /^\s*$/.test(full.slice(0, found))) { + insert = full.slice(0, found); + } + if (insert != null) insert += mode.blockCommentContinue; + } + if (insert == null && mode.lineComment && continueLineCommentEnabled(cm)) { + var line = cm.getLine(pos.line), found = line.indexOf(mode.lineComment); + if (found > -1) { + insert = line.slice(0, found); + if (/\S/.test(insert)) insert = null; + else insert += mode.lineComment + line.slice(found + mode.lineComment.length).match(/^\s*/)[0]; + } + } + if (insert == null) return CodeMirror.Pass; + inserts[i] = "\n" + insert; + } + + cm.operation(function() { + for (var i = ranges.length - 1; i >= 0; i--) + cm.replaceRange(inserts[i], ranges[i].from(), ranges[i].to(), "+insert"); + }); + } + + function continueLineCommentEnabled(cm) { + var opt = cm.getOption("continueComments"); + if (opt && typeof opt == "object") + return opt.continueLineComment !== false; + return true; + } + + CodeMirror.defineOption("continueComments", null, function(cm, val, prev) { + if (prev && prev != CodeMirror.Init) + cm.removeKeyMap("continueComment"); + if (val) { + var key = "Enter"; + if (typeof val == "string") + key = val; + else if (typeof val == "object" && val.key) + key = val.key; + var map = {name: "continueComment"}; + map[key] = continueComment; + cm.addKeyMap(map); + } + }); +}); diff --git a/applications/admin/static/codemirror/addon/dialog/dialog.js b/applications/admin/static/codemirror/addon/dialog/dialog.js index 586b7370..e0e8ad4e 100644 --- a/applications/admin/static/codemirror/addon/dialog/dialog.js +++ b/applications/admin/static/codemirror/addon/dialog/dialog.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + // Open simple dialogs on top of an editor. Relies on dialog.css. (function(mod) { @@ -12,11 +15,11 @@ var wrap = cm.getWrapperElement(); var dialog; dialog = wrap.appendChild(document.createElement("div")); - if (bottom) { + if (bottom) dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom"; - } else { + else dialog.className = "CodeMirror-dialog CodeMirror-dialog-top"; - } + if (typeof template == "string") { dialog.innerHTML = template; } else { // Assuming it's a detached DOM element. @@ -32,40 +35,59 @@ } CodeMirror.defineExtension("openDialog", function(template, callback, options) { + if (!options) options = {}; + closeNotification(this, null); - var dialog = dialogDiv(this, template, options && options.bottom); + + var dialog = dialogDiv(this, template, options.bottom); var closed = false, me = this; - function close() { - if (closed) return; - closed = true; - dialog.parentNode.removeChild(dialog); + function close(newVal) { + if (typeof newVal == 'string') { + inp.value = newVal; + } else { + if (closed) return; + closed = true; + dialog.parentNode.removeChild(dialog); + me.focus(); + + if (options.onClose) options.onClose(dialog); + } } + var inp = dialog.getElementsByTagName("input")[0], button; if (inp) { - if (options && options.value) inp.value = options.value; + if (options.value) { + inp.value = options.value; + inp.select(); + } + + if (options.onInput) + CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);}); + if (options.onKeyUp) + CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);}); + CodeMirror.on(inp, "keydown", function(e) { if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; } - if (e.keyCode == 13 || e.keyCode == 27) { + if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) { inp.blur(); CodeMirror.e_stop(e); close(); - me.focus(); - if (e.keyCode == 13) callback(inp.value); } + if (e.keyCode == 13) callback(inp.value, e); }); - if (options && options.onKeyUp) { - CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);}); - } - if (options && options.value) inp.value = options.value; + + if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close); + inp.focus(); - CodeMirror.on(inp, "blur", close); } else if (button = dialog.getElementsByTagName("button")[0]) { CodeMirror.on(button, "click", function() { close(); me.focus(); }); + + if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close); + button.focus(); - CodeMirror.on(button, "blur", close); } return close; }); @@ -110,8 +132,8 @@ CodeMirror.defineExtension("openNotification", function(template, options) { closeNotification(this, close); var dialog = dialogDiv(this, template, options && options.bottom); - var duration = options && (options.duration === undefined ? 5000 : options.duration); var closed = false, doneTimer; + var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000; function close() { if (closed) return; @@ -124,7 +146,10 @@ CodeMirror.e_preventDefault(e); close(); }); + if (duration) - doneTimer = setTimeout(close, options.duration); + doneTimer = setTimeout(close, duration); + + return close; }); }); diff --git a/applications/admin/static/codemirror/addon/display/fullscreen.js b/applications/admin/static/codemirror/addon/display/fullscreen.js index e39c6e16..cd3673b9 100644 --- a/applications/admin/static/codemirror/addon/display/fullscreen.js +++ b/applications/admin/static/codemirror/addon/display/fullscreen.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); diff --git a/applications/admin/static/codemirror/addon/display/panel.js b/applications/admin/static/codemirror/addon/display/panel.js new file mode 100644 index 00000000..22c0453e --- /dev/null +++ b/applications/admin/static/codemirror/addon/display/panel.js @@ -0,0 +1,94 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + CodeMirror.defineExtension("addPanel", function(node, options) { + if (!this.state.panels) initPanels(this); + + var info = this.state.panels; + if (options && options.position == "bottom") + info.wrapper.appendChild(node); + else + info.wrapper.insertBefore(node, info.wrapper.firstChild); + var height = (options && options.height) || node.offsetHeight; + this._setSize(null, info.heightLeft -= height); + info.panels++; + return new Panel(this, node, options, height); + }); + + function Panel(cm, node, options, height) { + this.cm = cm; + this.node = node; + this.options = options; + this.height = height; + this.cleared = false; + } + + Panel.prototype.clear = function() { + if (this.cleared) return; + this.cleared = true; + var info = this.cm.state.panels; + this.cm._setSize(null, info.heightLeft += this.height); + info.wrapper.removeChild(this.node); + if (--info.panels == 0) removePanels(this.cm); + }; + + Panel.prototype.changed = function(height) { + var newHeight = height == null ? this.node.offsetHeight : height; + var info = this.cm.state.panels; + this.cm._setSize(null, info.height += (newHeight - this.height)); + this.height = newHeight; + }; + + function initPanels(cm) { + var wrap = cm.getWrapperElement(); + var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle; + var height = parseInt(style.height); + var info = cm.state.panels = { + setHeight: wrap.style.height, + heightLeft: height, + panels: 0, + wrapper: document.createElement("div") + }; + wrap.parentNode.insertBefore(info.wrapper, wrap); + var hasFocus = cm.hasFocus(); + info.wrapper.appendChild(wrap); + if (hasFocus) cm.focus(); + + cm._setSize = cm.setSize; + if (height != null) cm.setSize = function(width, newHeight) { + if (newHeight == null) return this._setSize(width, newHeight); + info.setHeight = newHeight; + if (typeof newHeight != "number") { + var px = /^(\d+\.?\d*)px$/.exec(newHeight); + if (px) { + newHeight = Number(px[1]); + } else { + info.wrapper.style.height = newHeight; + newHeight = info.wrapper.offsetHeight; + info.wrapper.style.height = ""; + } + } + cm._setSize(width, info.heightLeft += (newHeight - height)); + height = newHeight; + }; + } + + function removePanels(cm) { + var info = cm.state.panels; + cm.state.panels = null; + + var wrap = cm.getWrapperElement(); + info.wrapper.parentNode.replaceChild(wrap, info.wrapper); + wrap.style.height = info.setHeight; + cm.setSize = cm._setSize; + cm.setSize(); + } +}); diff --git a/applications/admin/static/codemirror/addon/display/placeholder.js b/applications/admin/static/codemirror/addon/display/placeholder.js index 0fdc9b0d..bb0c3931 100644 --- a/applications/admin/static/codemirror/addon/display/placeholder.js +++ b/applications/admin/static/codemirror/addon/display/placeholder.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); diff --git a/applications/admin/static/codemirror/addon/display/rulers.js b/applications/admin/static/codemirror/addon/display/rulers.js new file mode 100644 index 00000000..13185d30 --- /dev/null +++ b/applications/admin/static/codemirror/addon/display/rulers.js @@ -0,0 +1,64 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineOption("rulers", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + clearRulers(cm); + cm.off("refresh", refreshRulers); + } + if (val && val.length) { + setRulers(cm); + cm.on("refresh", refreshRulers); + } + }); + + function clearRulers(cm) { + for (var i = cm.display.lineSpace.childNodes.length - 1; i >= 0; i--) { + var node = cm.display.lineSpace.childNodes[i]; + if (/(^|\s)CodeMirror-ruler($|\s)/.test(node.className)) + node.parentNode.removeChild(node); + } + } + + function setRulers(cm) { + var val = cm.getOption("rulers"); + var cw = cm.defaultCharWidth(); + var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left; + var minH = cm.display.scroller.offsetHeight + 30; + for (var i = 0; i < val.length; i++) { + var elt = document.createElement("div"); + elt.className = "CodeMirror-ruler"; + var col, cls = null, conf = val[i]; + if (typeof conf == "number") { + col = conf; + } else { + col = conf.column; + if (conf.className) elt.className += " " + conf.className; + if (conf.color) elt.style.borderColor = conf.color; + if (conf.lineStyle) elt.style.borderLeftStyle = conf.lineStyle; + if (conf.width) elt.style.borderLeftWidth = conf.width; + cls = val[i].className; + } + elt.style.left = (left + col * cw) + "px"; + elt.style.top = "-50px"; + elt.style.bottom = "-20px"; + elt.style.minHeight = minH + "px"; + cm.display.lineSpace.insertBefore(elt, cm.display.cursorDiv); + } + } + + function refreshRulers(cm) { + clearRulers(cm); + setRulers(cm); + } +}); diff --git a/applications/admin/static/codemirror/addon/edit/closebrackets.js b/applications/admin/static/codemirror/addon/edit/closebrackets.js index f48ad881..ff4bb3f7 100644 --- a/applications/admin/static/codemirror/addon/edit/closebrackets.js +++ b/applications/admin/static/codemirror/addon/edit/closebrackets.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -7,31 +10,51 @@ mod(CodeMirror); })(function(CodeMirror) { var DEFAULT_BRACKETS = "()[]{}''\"\""; + var DEFAULT_TRIPLES = "'\""; var DEFAULT_EXPLODE_ON_ENTER = "[]{}"; var SPACE_CHAR_REGEX = /\s/; + var Pos = CodeMirror.Pos; + CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) { if (old != CodeMirror.Init && old) cm.removeKeyMap("autoCloseBrackets"); if (!val) return; - var pairs = DEFAULT_BRACKETS, explode = DEFAULT_EXPLODE_ON_ENTER; + var pairs = DEFAULT_BRACKETS, triples = DEFAULT_TRIPLES, explode = DEFAULT_EXPLODE_ON_ENTER; if (typeof val == "string") pairs = val; else if (typeof val == "object") { if (val.pairs != null) pairs = val.pairs; + if (val.triples != null) triples = val.triples; if (val.explode != null) explode = val.explode; } - var map = buildKeymap(pairs); + var map = buildKeymap(pairs, triples); if (explode) map.Enter = buildExplodeHandler(explode); cm.addKeyMap(map); }); function charsAround(cm, pos) { - var str = cm.getRange(CodeMirror.Pos(pos.line, pos.ch - 1), - CodeMirror.Pos(pos.line, pos.ch + 1)); + var str = cm.getRange(Pos(pos.line, pos.ch - 1), + Pos(pos.line, pos.ch + 1)); return str.length == 2 ? str : null; } - function buildKeymap(pairs) { + // Project the token type that will exists after the given char is + // typed, and use it to determine whether it would cause the start + // of a string token. + function enteringString(cm, pos, ch) { + var line = cm.getLine(pos.line); + var token = cm.getTokenAt(pos); + if (/\bstring2?\b/.test(token.type)) return false; + var stream = new CodeMirror.StringStream(line.slice(0, pos.ch) + ch + line.slice(pos.ch), 4); + stream.pos = stream.start = token.start; + for (;;) { + var type1 = cm.getMode().token(stream, token.state); + if (stream.pos >= pos.ch + 1) return /\bstring2?\b/.test(type1); + stream.start = stream.pos; + } + } + + function buildKeymap(pairs, triples) { var map = { name : "autoCloseBrackets", Backspace: function(cm) { @@ -44,53 +67,68 @@ } for (var i = ranges.length - 1; i >= 0; i--) { var cur = ranges[i].head; - cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1)); + cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1)); } } }; var closingBrackets = ""; for (var i = 0; i < pairs.length; i += 2) (function(left, right) { - if (left != right) closingBrackets += right; + closingBrackets += right; map["'" + left + "'"] = function(cm) { if (cm.getOption("disableInput")) return CodeMirror.Pass; var ranges = cm.listSelections(), type, next; for (var i = 0; i < ranges.length; i++) { var range = ranges[i], cur = range.head, curType; - if (left == "'" && cm.getTokenTypeAt(cur) == "comment") - return CodeMirror.Pass; - var next = cm.getRange(cur, CodeMirror.Pos(cur.line, cur.ch + 1)); - if (!range.empty()) + var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1)); + if (!range.empty()) { curType = "surround"; - else if (left == right && next == right) - curType = "skip"; - else if (left == right && CodeMirror.isWordChar(next)) - return CodeMirror.Pass; - else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next)) + } else if (left == right && next == right) { + if (cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == left + left + left) + curType = "skipThree"; + else + curType = "skip"; + } else if (left == right && cur.ch > 1 && triples.indexOf(left) >= 0 && + cm.getRange(Pos(cur.line, cur.ch - 2), cur) == left + left && + (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != left)) { + curType = "addFour"; + } else if (left == '"' || left == "'") { + if (!CodeMirror.isWordChar(next) && enteringString(cm, cur, left)) curType = "both"; + else return CodeMirror.Pass; + } else if (cm.getLine(cur.line).length == cur.ch || closingBrackets.indexOf(next) >= 0 || SPACE_CHAR_REGEX.test(next)) { curType = "both"; - else + } else { return CodeMirror.Pass; + } if (!type) type = curType; else if (type != curType) return CodeMirror.Pass; } - if (type == "skip") { - cm.execCommand("goCharRight"); - } else if (type == "surround") { - var sels = cm.getSelections(); - for (var i = 0; i < sels.length; i++) - sels[i] = left + sels[i] + right; - cm.replaceSelections(sels, "around"); - } else if (type == "both") { - cm.replaceSelection(left + right, null); - cm.execCommand("goCharLeft"); - } + cm.operation(function() { + if (type == "skip") { + cm.execCommand("goCharRight"); + } else if (type == "skipThree") { + for (var i = 0; i < 3; i++) + cm.execCommand("goCharRight"); + } else if (type == "surround") { + var sels = cm.getSelections(); + for (var i = 0; i < sels.length; i++) + sels[i] = left + sels[i] + right; + cm.replaceSelections(sels, "around"); + } else if (type == "both") { + cm.replaceSelection(left + right, null); + cm.execCommand("goCharLeft"); + } else if (type == "addFour") { + cm.replaceSelection(left + left + left + left, "before"); + cm.execCommand("goCharRight"); + } + }); }; if (left != right) map["'" + right + "'"] = function(cm) { var ranges = cm.listSelections(); for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; if (!range.empty() || - cm.getRange(range.head, CodeMirror.Pos(range.head.line, range.head.ch + 1)) != right) + cm.getRange(range.head, Pos(range.head.line, range.head.ch + 1)) != right) return CodeMirror.Pass; } cm.execCommand("goCharRight"); diff --git a/applications/admin/static/codemirror/addon/edit/closetag.js b/applications/admin/static/codemirror/addon/edit/closetag.js index c7c0701b..e68d52d9 100644 --- a/applications/admin/static/codemirror/addon/edit/closetag.js +++ b/applications/admin/static/codemirror/addon/edit/closetag.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + /** * Tag-closer extension for CodeMirror. * @@ -55,6 +58,7 @@ var pos = ranges[i].head, tok = cm.getTokenAt(pos); var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state; if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass; + var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html"; var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose); var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent); @@ -68,8 +72,7 @@ tok.type == "tag" && state.type == "closeTag" || tok.string.indexOf("/") == (tok.string.length - 1) || // match something like dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 || - CodeMirror.scanForClosingTag && CodeMirror.scanForClosingTag(cm, pos, tagName, - Math.min(cm.lastLine() + 1, pos.line + 50))) + closingTagExists(cm, tagName, pos, state, true)) return CodeMirror.Pass; var indent = indentTags && indexOf(indentTags, lowerTagName) > -1; @@ -91,26 +94,73 @@ } } - function autoCloseSlash(cm) { - if (cm.getOption("disableInput")) return CodeMirror.Pass; + function autoCloseCurrent(cm, typingSlash) { var ranges = cm.listSelections(), replacements = []; + var head = typingSlash ? "/" : ""; + // Kludge to get around the fact that we are not in XML mode + // when completing in JS/CSS snippet in htmlmixed mode. Does not + // work for other XML embedded languages (there is no general + // way to go from a mixed mode to its current XML state). + if (inner.mode.name != "xml") { + if (cm.getMode().name == "htmlmixed" && inner.mode.name == "javascript") + replacements[i] = head + "script>"; + else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css") + replacements[i] = head + "style>"; + else + return CodeMirror.Pass; + } else { + if (!state.context || !state.context.tagName || + closingTagExists(cm, state.context.tagName, pos, state)) + return CodeMirror.Pass; + replacements[i] = head + state.context.tagName + ">"; + } } cm.replaceSelections(replacements); + ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) + if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line) + cm.indentLine(ranges[i].head.line); } + function autoCloseSlash(cm) { + if (cm.getOption("disableInput")) return CodeMirror.Pass; + return autoCloseCurrent(cm, true); + } + + CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); }; + function indexOf(collection, elt) { if (collection.indexOf) return collection.indexOf(elt); for (var i = 0, e = collection.length; i < e; ++i) if (collection[i] == elt) return i; return -1; } + + // If xml-fold is loaded, we use its functionality to try and verify + // whether a given tag is actually unclosed. + function closingTagExists(cm, tagName, pos, state, newTag) { + if (!CodeMirror.scanForClosingTag) return false; + var end = Math.min(cm.lastLine() + 1, pos.line + 500); + var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end); + if (!nextClose || nextClose.tag != tagName) return false; + var cx = state.context; + // If the immediate wrapping context contains onCx instances of + // the same tag, a closing tag only exists if there are at least + // that many closing tags of that type following. + for (var onCx = newTag ? 1 : 0; cx && cx.tagName == tagName; cx = cx.prev) ++onCx; + pos = nextClose.to; + for (var i = 1; i < onCx; i++) { + var next = CodeMirror.scanForClosingTag(cm, pos, null, end); + if (!next || next.tag != tagName) return false; + pos = next.to; + } + return true; + } }); diff --git a/applications/admin/static/codemirror/addon/edit/continuecomment.js b/applications/admin/static/codemirror/addon/edit/continuecomment.js deleted file mode 100644 index 30802622..00000000 --- a/applications/admin/static/codemirror/addon/edit/continuecomment.js +++ /dev/null @@ -1,44 +0,0 @@ -(function() { - var modes = ["clike", "css", "javascript"]; - for (var i = 0; i < modes.length; ++i) - CodeMirror.extendMode(modes[i], {blockCommentStart: "/*", - blockCommentEnd: "*/", - blockCommentContinue: " * "}); - - function continueComment(cm) { - var pos = cm.getCursor(), token = cm.getTokenAt(pos); - var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode; - var space; - - if (token.type == "comment" && mode.blockCommentStart) { - var end = token.string.indexOf(mode.blockCommentEnd); - var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found; - if (end != -1 && end == token.string.length - mode.blockCommentEnd.length) { - // Comment ended, don't continue it - } else if (token.string.indexOf(mode.blockCommentStart) == 0) { - space = full.slice(0, token.start); - if (!/^\s*$/.test(space)) { - space = ""; - for (var i = 0; i < token.start; ++i) space += " "; - } - } else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 && - found + mode.blockCommentContinue.length > token.start && - /^\s*$/.test(full.slice(0, found))) { - space = full.slice(0, found); - } - } - - if (space != null) - cm.replaceSelection("\n" + space + mode.blockCommentContinue, "end"); - else - return CodeMirror.Pass; - } - - CodeMirror.defineOption("continueComments", null, function(cm, val, prev) { - if (prev && prev != CodeMirror.Init) - cm.removeKeyMap("continueComment"); - var map = {name: "continueComment"}; - map[typeof val == "string" ? val : "Enter"] = continueComment; - cm.addKeyMap(map); - }); -})(); diff --git a/applications/admin/static/codemirror/addon/edit/continuelist.js b/applications/admin/static/codemirror/addon/edit/continuelist.js index 2946aa6a..ca8d2675 100644 --- a/applications/admin/static/codemirror/addon/edit/continuelist.js +++ b/applications/admin/static/codemirror/addon/edit/continuelist.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -8,26 +11,39 @@ })(function(CodeMirror) { "use strict"; - var listRE = /^(\s*)([*+-]|(\d+)\.)(\s*)/, - unorderedBullets = "*+-"; + var listRE = /^(\s*)(>[> ]*|[*+-]\s|(\d+)\.)(\s*)/, + emptyListRE = /^(\s*)(>[> ]*|[*+-]|(\d+)\.)(\s*)$/, + unorderedListRE = /[*+-]\s/; CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) { if (cm.getOption("disableInput")) return CodeMirror.Pass; var ranges = cm.listSelections(), replacements = []; for (var i = 0; i < ranges.length; i++) { var pos = ranges[i].head, match; - var inList = cm.getStateAfter(pos.line).list !== false; + var eolState = cm.getStateAfter(pos.line); + var inList = eolState.list !== false; + var inQuote = eolState.quote !== false; - if (!ranges[i].empty() || !inList || !(match = cm.getLine(pos.line).match(listRE))) { + if (!ranges[i].empty() || (!inList && !inQuote) || !(match = cm.getLine(pos.line).match(listRE))) { cm.execCommand("newlineAndIndent"); return; } - var indent = match[1], after = match[4]; - var bullet = unorderedBullets.indexOf(match[2]) >= 0 - ? match[2] - : (parseInt(match[3], 10) + 1) + "."; + if (cm.getLine(pos.line).match(emptyListRE)) { + cm.replaceRange("", { + line: pos.line, ch: 0 + }, { + line: pos.line, ch: pos.ch + 1 + }); + replacements[i] = "\n"; - replacements[i] = "\n" + indent + bullet + after; + } else { + var indent = match[1], after = match[4]; + var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0 + ? match[2] + : (parseInt(match[3], 10) + 1) + "."; + + replacements[i] = "\n" + indent + bullet + after; + } } cm.replaceSelections(replacements); diff --git a/applications/admin/static/codemirror/addon/edit/matchbrackets.js b/applications/admin/static/codemirror/addon/edit/matchbrackets.js index 576ec143..fa1ae030 100644 --- a/applications/admin/static/codemirror/addon/edit/matchbrackets.js +++ b/applications/admin/static/codemirror/addon/edit/matchbrackets.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -22,15 +25,24 @@ var style = cm.getTokenTypeAt(Pos(where.line, pos + 1)); var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config); + if (found == null) return null; return {from: Pos(where.line, pos), to: found && found.pos, match: found && found.ch == match.charAt(0), forward: dir > 0}; } + // bracketRegex is used to specify which type of bracket to scan + // should be a regexp, e.g. /[[\]]/ + // + // Note: If "where" is on an open bracket, then this bracket is ignored. + // + // Returns false when no bracket was found, null when it reached + // maxScanLines and gave up function scanForBracket(cm, where, dir, style, config) { var maxScanLen = (config && config.maxScanLineLength) || 10000; - var maxScanLines = (config && config.maxScanLines) || 500; + var maxScanLines = (config && config.maxScanLines) || 1000; - var stack = [], re = /[(){}[\]]/; + var stack = []; + var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/; var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) : Math.max(cm.firstLine() - 1, where.line - maxScanLines); for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { @@ -49,6 +61,7 @@ } } } + return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; } function matchBrackets(cm, autoclear, config) { @@ -57,11 +70,10 @@ var marks = [], ranges = cm.listSelections(); for (var i = 0; i < ranges.length; i++) { var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config); - if (match && cm.getLine(match.from.line).length <= maxHighlightLen && - match.to && cm.getLine(match.to.line).length <= maxHighlightLen) { + if (match && cm.getLine(match.from.line).length <= maxHighlightLen) { var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style})); - if (match.to) + if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen) marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style})); } } @@ -99,10 +111,10 @@ }); CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); - CodeMirror.defineExtension("findMatchingBracket", function(pos, strict){ - return findMatchingBracket(this, pos, strict); + CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){ + return findMatchingBracket(this, pos, strict, config); }); - CodeMirror.defineExtension("scanForBracket", function(pos, dir, style){ - return scanForBracket(this, pos, dir, style); + CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){ + return scanForBracket(this, pos, dir, style, config); }); }); diff --git a/applications/admin/static/codemirror/addon/edit/matchtags.js b/applications/admin/static/codemirror/addon/edit/matchtags.js new file mode 100644 index 00000000..fb1911a8 --- /dev/null +++ b/applications/admin/static/codemirror/addon/edit/matchtags.js @@ -0,0 +1,66 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../fold/xml-fold")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../fold/xml-fold"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineOption("matchTags", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.off("cursorActivity", doMatchTags); + cm.off("viewportChange", maybeUpdateMatch); + clear(cm); + } + if (val) { + cm.state.matchBothTags = typeof val == "object" && val.bothTags; + cm.on("cursorActivity", doMatchTags); + cm.on("viewportChange", maybeUpdateMatch); + doMatchTags(cm); + } + }); + + function clear(cm) { + if (cm.state.tagHit) cm.state.tagHit.clear(); + if (cm.state.tagOther) cm.state.tagOther.clear(); + cm.state.tagHit = cm.state.tagOther = null; + } + + function doMatchTags(cm) { + cm.state.failedTagMatch = false; + cm.operation(function() { + clear(cm); + if (cm.somethingSelected()) return; + var cur = cm.getCursor(), range = cm.getViewport(); + range.from = Math.min(range.from, cur.line); range.to = Math.max(cur.line + 1, range.to); + var match = CodeMirror.findMatchingTag(cm, cur, range); + if (!match) return; + if (cm.state.matchBothTags) { + var hit = match.at == "open" ? match.open : match.close; + if (hit) cm.state.tagHit = cm.markText(hit.from, hit.to, {className: "CodeMirror-matchingtag"}); + } + var other = match.at == "close" ? match.open : match.close; + if (other) + cm.state.tagOther = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"}); + else + cm.state.failedTagMatch = true; + }); + } + + function maybeUpdateMatch(cm) { + if (cm.state.failedTagMatch) doMatchTags(cm); + } + + CodeMirror.commands.toMatchingTag = function(cm) { + var found = CodeMirror.findMatchingTag(cm, cm.getCursor()); + if (found) { + var other = found.at == "close" ? found.open : found.close; + if (other) cm.extendSelection(other.to, other.from); + } + }; +}); diff --git a/applications/admin/static/codemirror/addon/edit/trailingspace.js b/applications/admin/static/codemirror/addon/edit/trailingspace.js index ec07221e..fa7b56be 100644 --- a/applications/admin/static/codemirror/addon/edit/trailingspace.js +++ b/applications/admin/static/codemirror/addon/edit/trailingspace.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); diff --git a/applications/admin/static/codemirror/addon/fold/brace-fold.js b/applications/admin/static/codemirror/addon/fold/brace-fold.js index f0ee6202..1605f6c2 100644 --- a/applications/admin/static/codemirror/addon/fold/brace-fold.js +++ b/applications/admin/static/codemirror/addon/fold/brace-fold.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); diff --git a/applications/admin/static/codemirror/addon/fold/comment-fold.js b/applications/admin/static/codemirror/addon/fold/comment-fold.js index d72c5479..b75db7ea 100644 --- a/applications/admin/static/codemirror/addon/fold/comment-fold.js +++ b/applications/admin/static/codemirror/addon/fold/comment-fold.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); diff --git a/applications/admin/static/codemirror/addon/fold/foldcode.js b/applications/admin/static/codemirror/addon/fold/foldcode.js index d7a0bb5e..62911f93 100644 --- a/applications/admin/static/codemirror/addon/fold/foldcode.js +++ b/applications/admin/static/codemirror/addon/fold/foldcode.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -9,10 +12,14 @@ "use strict"; function doFold(cm, pos, options, force) { - var finder = options && (options.call ? options : options.rangeFinder); - if (!finder) finder = CodeMirror.fold.auto; + if (options && options.call) { + var finder = options; + options = null; + } else { + var finder = getOption(cm, options, "rangeFinder"); + } if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0); - var minSize = options && options.minFoldSize || 0; + var minSize = getOption(cm, options, "minFoldSize"); function getRange(allowFolded) { var range = finder(cm, pos); @@ -29,14 +36,17 @@ } var range = getRange(true); - if (options && options.scanUp) while (!range && pos.line > cm.firstLine()) { + if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) { pos = CodeMirror.Pos(pos.line - 1, 0); range = getRange(false); } if (!range || range.cleared || force === "unfold") return; - var myWidget = makeWidget(options); - CodeMirror.on(myWidget, "mousedown", function() { myRange.clear(); }); + var myWidget = makeWidget(cm, options); + CodeMirror.on(myWidget, "mousedown", function(e) { + myRange.clear(); + CodeMirror.e_preventDefault(e); + }); var myRange = cm.markText(range.from, range.to, { replacedWith: myWidget, clearOnEnter: true, @@ -48,8 +58,8 @@ CodeMirror.signal(cm, "fold", cm, range.from, range.to); } - function makeWidget(options) { - var widget = (options && options.widget) || "\u2194"; + function makeWidget(cm, options) { + var widget = getOption(cm, options, "widget"); if (typeof widget == "string") { var text = document.createTextNode(widget); widget = document.createElement("span"); @@ -114,4 +124,26 @@ if (cur) return cur; } }); + + var defaultOptions = { + rangeFinder: CodeMirror.fold.auto, + widget: "\u2194", + minFoldSize: 0, + scanUp: false + }; + + CodeMirror.defineOption("foldOptions", null); + + function getOption(cm, options, name) { + if (options && options[name] !== undefined) + return options[name]; + var editorOptions = cm.options.foldOptions; + if (editorOptions && editorOptions[name] !== undefined) + return editorOptions[name]; + return defaultOptions[name]; + } + + CodeMirror.defineExtension("foldOption", function(options, name) { + return getOption(this, options, name); + }); }); diff --git a/applications/admin/static/codemirror/addon/fold/foldgutter.css b/applications/admin/static/codemirror/addon/fold/foldgutter.css index 49805393..ad19ae2d 100644 --- a/applications/admin/static/codemirror/addon/fold/foldgutter.css +++ b/applications/admin/static/codemirror/addon/fold/foldgutter.css @@ -10,7 +10,6 @@ } .CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded { - color: #555; cursor: pointer; } .CodeMirror-foldgutter-open:after { diff --git a/applications/admin/static/codemirror/addon/fold/foldgutter.js b/applications/admin/static/codemirror/addon/fold/foldgutter.js index 9caba59a..199120c7 100644 --- a/applications/admin/static/codemirror/addon/fold/foldgutter.js +++ b/applications/admin/static/codemirror/addon/fold/foldgutter.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("./foldcode")); @@ -55,7 +58,7 @@ function marker(spec) { if (typeof spec == "string") { var elt = document.createElement("div"); - elt.className = spec; + elt.className = spec + " CodeMirror-guttermarker-subtle"; return elt; } else { return spec.cloneNode(true); @@ -64,14 +67,16 @@ function updateFoldInfo(cm, from, to) { var opts = cm.state.foldGutter.options, cur = from; + var minSize = cm.foldOption(opts, "minFoldSize"); + var func = cm.foldOption(opts, "rangeFinder"); cm.eachLine(from, to, function(line) { var mark = null; if (isFolded(cm, cur)) { mark = marker(opts.indicatorFolded); } else { - var pos = Pos(cur, 0), func = opts.rangeFinder || CodeMirror.fold.auto; + var pos = Pos(cur, 0); var range = func && func(cm, pos); - if (range && range.from.line + 1 < range.to.line) + if (range && range.to.line - range.from.line >= minSize) mark = marker(opts.indicatorOpen); } cm.setGutterMarker(line, opts.gutter, mark); @@ -89,20 +94,26 @@ } function onGutterClick(cm, line, gutter) { - var opts = cm.state.foldGutter.options; + var state = cm.state.foldGutter; + if (!state) return; + var opts = state.options; if (gutter != opts.gutter) return; cm.foldCode(Pos(line, 0), opts.rangeFinder); } function onChange(cm) { - var state = cm.state.foldGutter, opts = cm.state.foldGutter.options; + var state = cm.state.foldGutter; + if (!state) return; + var opts = state.options; state.from = state.to = 0; clearTimeout(state.changeUpdate); state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600); } function onViewportChange(cm) { - var state = cm.state.foldGutter, opts = cm.state.foldGutter.options; + var state = cm.state.foldGutter; + if (!state) return; + var opts = state.options; clearTimeout(state.changeUpdate); state.changeUpdate = setTimeout(function() { var vp = cm.getViewport(); @@ -124,7 +135,9 @@ } function onFold(cm, from) { - var state = cm.state.foldGutter, line = from.line; + var state = cm.state.foldGutter; + if (!state) return; + var line = from.line; if (line >= state.from && line < state.to) updateFoldInfo(cm, line, line + 1); } diff --git a/applications/admin/static/codemirror/addon/fold/indent-fold.js b/applications/admin/static/codemirror/addon/fold/indent-fold.js index d0130836..e29f15e9 100644 --- a/applications/admin/static/codemirror/addon/fold/indent-fold.js +++ b/applications/admin/static/codemirror/addon/fold/indent-fold.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); diff --git a/applications/admin/static/codemirror/addon/fold/markdown-fold.js b/applications/admin/static/codemirror/addon/fold/markdown-fold.js new file mode 100644 index 00000000..ce84c946 --- /dev/null +++ b/applications/admin/static/codemirror/addon/fold/markdown-fold.js @@ -0,0 +1,49 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.registerHelper("fold", "markdown", function(cm, start) { + var maxDepth = 100; + + function isHeader(lineNo) { + var tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0)); + return tokentype && /\bheader\b/.test(tokentype); + } + + function headerLevel(lineNo, line, nextLine) { + var match = line && line.match(/^#+/); + if (match && isHeader(lineNo)) return match[0].length; + match = nextLine && nextLine.match(/^[=\-]+\s*$/); + if (match && isHeader(lineNo + 1)) return nextLine[0] == "=" ? 1 : 2; + return maxDepth; + } + + var firstLine = cm.getLine(start.line), nextLine = cm.getLine(start.line + 1); + var level = headerLevel(start.line, firstLine, nextLine); + if (level === maxDepth) return undefined; + + var lastLineNo = cm.lastLine(); + var end = start.line, nextNextLine = cm.getLine(end + 2); + while (end < lastLineNo) { + if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break; + ++end; + nextLine = nextNextLine; + nextNextLine = cm.getLine(end + 2); + } + + return { + from: CodeMirror.Pos(start.line, firstLine.length), + to: CodeMirror.Pos(end, cm.getLine(end).length) + }; +}); + +}); diff --git a/applications/admin/static/codemirror/addon/fold/xml-fold.js b/applications/admin/static/codemirror/addon/fold/xml-fold.js index d554e2fc..504727f3 100644 --- a/applications/admin/static/codemirror/addon/fold/xml-fold.js +++ b/applications/admin/static/codemirror/addon/fold/xml-fold.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -148,8 +151,9 @@ if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return; var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch); var start = end && toTagStart(iter); - if (!end || end == "selfClose" || !start || cmp(iter, pos) > 0) return; + if (!end || !start || cmp(iter, pos) > 0) return; var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]}; + if (end == "selfClose") return {open: here, close: null, at: "open"}; if (start[1]) { // closing tag return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"}; @@ -173,6 +177,6 @@ // Used by addon/edit/closetag.js CodeMirror.scanForClosingTag = function(cm, pos, name, end) { var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null); - return !!findMatchingClose(iter, name); + return findMatchingClose(iter, name); }; }); diff --git a/applications/admin/static/codemirror/addon/hint/anyword-hint.js b/applications/admin/static/codemirror/addon/hint/anyword-hint.js new file mode 100644 index 00000000..8e74a920 --- /dev/null +++ b/applications/admin/static/codemirror/addon/hint/anyword-hint.js @@ -0,0 +1,41 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var WORD = /[\w$]+/, RANGE = 500; + + CodeMirror.registerHelper("hint", "anyword", function(editor, options) { + var word = options && options.word || WORD; + var range = options && options.range || RANGE; + var cur = editor.getCursor(), curLine = editor.getLine(cur.line); + var end = cur.ch, start = end; + while (start && word.test(curLine.charAt(start - 1))) --start; + var curWord = start != end && curLine.slice(start, end); + + var list = [], seen = {}; + var re = new RegExp(word.source, "g"); + for (var dir = -1; dir <= 1; dir += 2) { + var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir; + for (; line != endLine; line += dir) { + var text = editor.getLine(line), m; + while (m = re.exec(text)) { + if (line == cur.line && m[0] === curWord) continue; + if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) { + seen[m[0]] = true; + list.push(m[0]); + } + } + } + } + return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)}; + }); +}); diff --git a/applications/admin/static/codemirror/addon/hint/css-hint.js b/applications/admin/static/codemirror/addon/hint/css-hint.js new file mode 100644 index 00000000..488da344 --- /dev/null +++ b/applications/admin/static/codemirror/addon/hint/css-hint.js @@ -0,0 +1,56 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../../mode/css/css")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../../mode/css/css"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var pseudoClasses = {link: 1, visited: 1, active: 1, hover: 1, focus: 1, + "first-letter": 1, "first-line": 1, "first-child": 1, + before: 1, after: 1, lang: 1}; + + CodeMirror.registerHelper("hint", "css", function(cm) { + var cur = cm.getCursor(), token = cm.getTokenAt(cur); + var inner = CodeMirror.innerMode(cm.getMode(), token.state); + if (inner.mode.name != "css") return; + + var start = token.start, end = cur.ch, word = token.string.slice(0, end - start); + if (/[^\w$_-]/.test(word)) { + word = ""; start = end = cur.ch; + } + + var spec = CodeMirror.resolveMode("text/css"); + + var result = []; + function add(keywords) { + for (var name in keywords) + if (!word || name.lastIndexOf(word, 0) == 0) + result.push(name); + } + + var st = inner.state.state; + if (st == "pseudo" || token.type == "variable-3") { + add(pseudoClasses); + } else if (st == "block" || st == "maybeprop") { + add(spec.propertyKeywords); + } else if (st == "prop" || st == "parens" || st == "at" || st == "params") { + add(spec.valueKeywords); + add(spec.colorKeywords); + } else if (st == "media" || st == "media_parens") { + add(spec.mediaTypes); + add(spec.mediaFeatures); + } + + if (result.length) return { + list: result, + from: CodeMirror.Pos(cur.line, start), + to: CodeMirror.Pos(cur.line, end) + }; + }); +}); diff --git a/applications/admin/static/codemirror/addon/hint/html-hint.js b/applications/admin/static/codemirror/addon/hint/html-hint.js old mode 100755 new mode 100644 index cbe7c61a..c6769bca --- a/applications/admin/static/codemirror/addon/hint/html-hint.js +++ b/applications/admin/static/codemirror/addon/hint/html-hint.js @@ -1,8 +1,11 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); + mod(require("../../lib/codemirror"), require("./xml-hint")); else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); + define(["../../lib/codemirror", "./xml-hint"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { diff --git a/applications/admin/static/codemirror/addon/hint/javascript-hint.js b/applications/admin/static/codemirror/addon/hint/javascript-hint.js index 305bb85a..7bcbf4a0 100644 --- a/applications/admin/static/codemirror/addon/hint/javascript-hint.js +++ b/applications/admin/static/codemirror/addon/hint/javascript-hint.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -27,15 +30,20 @@ function scriptHint(editor, keywords, getToken, options) { // Find the token at the cursor - var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token; + var cur = editor.getCursor(), token = getToken(editor, cur); if (/\b(?:string|comment)\b/.test(token.type)) return; token.state = CodeMirror.innerMode(editor.getMode(), token.state).state; // If it's not a 'word-style' token, ignore the token. if (!/^[\w$_]*$/.test(token.string)) { - token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state, - type: token.string == "." ? "property" : null}; + token = {start: cur.ch, end: cur.ch, string: "", state: token.state, + type: token.string == "." ? "property" : null}; + } else if (token.end > cur.ch) { + token.end = cur.ch; + token.string = token.string.slice(0, cur.ch - token.start); } + + var tprop = token; // If it is a property, find out what it is a property of. while (tprop.type == "property") { tprop = getToken(editor, Pos(cur.line, tprop.start)); @@ -90,7 +98,7 @@ "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" "); function getCompletions(token, context, keywords, options) { - var found = [], start = token.string; + var found = [], start = token.string, global = options && options.globalScope || window; function maybeAdd(str) { if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str); } @@ -108,27 +116,29 @@ if (obj.type && obj.type.indexOf("variable") === 0) { if (options && options.additionalContext) base = options.additionalContext[obj.string]; - base = base || window[obj.string]; + if (!options || options.useGlobalScope !== false) + base = base || global[obj.string]; } else if (obj.type == "string") { base = ""; } else if (obj.type == "atom") { base = 1; } else if (obj.type == "function") { - if (window.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') && - (typeof window.jQuery == 'function')) - base = window.jQuery(); - else if (window._ != null && (obj.string == '_') && (typeof window._ == 'function')) - base = window._(); + if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') && + (typeof global.jQuery == 'function')) + base = global.jQuery(); + else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function')) + base = global._(); } while (base != null && context.length) base = base[context.pop().string]; if (base != null) gatherCompletions(base); } else { - // If not, just look in the window object and any local scope + // If not, just look in the global object and any local scope // (reading into JS mode internals to get at the local and global variables) for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name); for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name); - gatherCompletions(window); + if (!options || options.useGlobalScope !== false) + gatherCompletions(global); forEach(keywords, maybeAdd); } return found; diff --git a/applications/admin/static/codemirror/addon/hint/show-hint.css b/applications/admin/static/codemirror/addon/hint/show-hint.css index 8a4ff052..924e638f 100644 --- a/applications/admin/static/codemirror/addon/hint/show-hint.css +++ b/applications/admin/static/codemirror/addon/hint/show-hint.css @@ -32,7 +32,7 @@ cursor: pointer; } -.CodeMirror-hint-active { +li.CodeMirror-hint-active { background: #08f; color: white; } diff --git a/applications/admin/static/codemirror/addon/hint/show-hint.js b/applications/admin/static/codemirror/addon/hint/show-hint.js index 6f04c565..f5446194 100644 --- a/applications/admin/static/codemirror/addon/hint/show-hint.js +++ b/applications/admin/static/codemirror/addon/hint/show-hint.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -11,28 +14,44 @@ var HINT_ELEMENT_CLASS = "CodeMirror-hint"; var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active"; + // This is the old interface, kept around for now to stay + // backwards-compatible. CodeMirror.showHint = function(cm, getHints, options) { - // We want a single cursor position. - if (cm.listSelections().length > 1 || cm.somethingSelected()) return; - if (getHints == null) { - if (options && options.async) return; - else getHints = CodeMirror.hint.auto; - } - - if (cm.state.completionActive) cm.state.completionActive.close(); - - var completion = cm.state.completionActive = new Completion(cm, getHints, options || {}); - CodeMirror.signal(cm, "startCompletion", cm); - if (completion.options.async) - getHints(cm, function(hints) { completion.showHints(hints); }, completion.options); - else - return completion.showHints(getHints(cm, completion.options)); + if (!getHints) return cm.showHint(options); + if (options && options.async) getHints.async = true; + var newOpts = {hint: getHints}; + if (options) for (var prop in options) newOpts[prop] = options[prop]; + return cm.showHint(newOpts); }; - function Completion(cm, getHints, options) { + var asyncRunID = 0; + function retrieveHints(getter, cm, options, then) { + if (getter.async) { + var id = ++asyncRunID; + getter(cm, function(hints) { + if (asyncRunID == id) then(hints); + }, options); + } else { + then(getter(cm, options)); + } + } + + CodeMirror.defineExtension("showHint", function(options) { + // We want a single cursor position. + if (this.listSelections().length > 1 || this.somethingSelected()) return; + + if (this.state.completionActive) this.state.completionActive.close(); + var completion = this.state.completionActive = new Completion(this, options); + var getHints = completion.options.hint; + if (!getHints) return; + + CodeMirror.signal(this, "startCompletion", this); + return retrieveHints(getHints, this, completion.options, function(hints) { completion.showHints(hints); }); + }); + + function Completion(cm, options) { this.cm = cm; - this.getHints = getHints; - this.options = options; + this.options = this.buildOptions(options); this.widget = this.onClose = null; } @@ -53,7 +72,8 @@ pick: function(data, i) { var completion = data.list[i]; if (completion.hint) completion.hint(this.cm, data, completion); - else this.cm.replaceRange(getText(completion), completion.from||data.from, completion.to||data.to); + else this.cm.replaceRange(getText(completion), completion.from || data.from, + completion.to || data.to, "complete"); CodeMirror.signal(data, "pick", completion); this.close(); }, @@ -61,7 +81,7 @@ showHints: function(data) { if (!data || !data.list.length || !this.active()) return this.close(); - if (this.options.completeSingle != false && data.list.length == 1) + if (this.options.completeSingle && data.list.length == 1) this.pick(data, 0); else this.showWidget(data); @@ -72,7 +92,7 @@ CodeMirror.signal(data, "shown"); var debounce = 0, completion = this, finished; - var closeOn = this.options.closeCharacters || /[\s()\[\]{};:>,]/; + var closeOn = this.options.closeCharacters; var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length; var requestAnimationFrame = window.requestAnimationFrame || function(fn) { @@ -91,15 +111,13 @@ function update() { if (finished) return; CodeMirror.signal(data, "update"); - if (completion.options.async) - completion.getHints(completion.cm, finishUpdate, completion.options); - else - finishUpdate(completion.getHints(completion.cm, completion.options)); + retrieveHints(completion.options.hint, completion.cm, completion.options, finishUpdate); } function finishUpdate(data_) { data = data_; if (finished) return; if (!data || !data.list.length) return done(); + if (completion.widget) completion.widget.close(); completion.widget = new Widget(completion, data); } @@ -124,6 +142,17 @@ } this.cm.on("cursorActivity", activity); this.onClose = done; + }, + + buildOptions: function(options) { + var editor = this.cm.options.hintOptions; + var out = {}; + for (var prop in defaultOptions) out[prop] = defaultOptions[prop]; + if (editor) for (var prop in editor) + if (editor[prop] !== undefined) out[prop] = editor[prop]; + if (options) for (var prop in options) + if (options[prop] !== undefined) out[prop] = options[prop]; + return out; } }; @@ -132,7 +161,7 @@ else return completion.text; } - function buildKeyMap(options, handle) { + function buildKeyMap(completion, handle) { var baseMap = { Up: function() {handle.moveFocus(-1);}, Down: function() {handle.moveFocus(1);}, @@ -144,7 +173,8 @@ Tab: handle.pick, Esc: handle.close }; - var ourMap = options.customKeys ? {} : baseMap; + var custom = completion.options.customKeys; + var ourMap = custom ? {} : baseMap; function addBinding(key, val) { var bound; if (typeof val != "string") @@ -156,12 +186,13 @@ bound = val; ourMap[key] = bound; } - if (options.customKeys) - for (var key in options.customKeys) if (options.customKeys.hasOwnProperty(key)) - addBinding(key, options.customKeys[key]); - if (options.extraKeys) - for (var key in options.extraKeys) if (options.extraKeys.hasOwnProperty(key)) - addBinding(key, options.extraKeys[key]); + if (custom) + for (var key in custom) if (custom.hasOwnProperty(key)) + addBinding(key, custom[key]); + var extra = completion.options.extraKeys; + if (extra) + for (var key in extra) if (extra.hasOwnProperty(key)) + addBinding(key, extra[key]); return ourMap; } @@ -175,11 +206,11 @@ function Widget(completion, data) { this.completion = completion; this.data = data; - var widget = this, cm = completion.cm, options = completion.options; + var widget = this, cm = completion.cm; var hints = this.hints = document.createElement("ul"); hints.className = "CodeMirror-hints"; - this.selectedHint = options.getDefaultSelection ? options.getDefaultSelection(cm,options,data) : 0; + this.selectedHint = data.selectedHint || 0; var completions = data.list; for (var i = 0; i < completions.length; ++i) { @@ -192,19 +223,19 @@ elt.hintId = i; } - var pos = cm.cursorCoords(options.alignWithWord !== false ? data.from : null); + var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null); var left = pos.left, top = pos.bottom, below = true; hints.style.left = left + "px"; hints.style.top = top + "px"; // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth); var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); - (options.container || document.body).appendChild(hints); + (completion.options.container || document.body).appendChild(hints); var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH; if (overlapY > 0) { - var height = box.bottom - box.top, curTop = box.top - (pos.bottom - pos.top); + var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top); if (curTop - height > 0) { // Fits above cursor - hints.style.top = (top = curTop - height) + "px"; + hints.style.top = (top = pos.top - height) + "px"; below = false; } else if (height > winH) { hints.style.height = (winH - 5) + "px"; @@ -217,7 +248,7 @@ } } } - var overlapX = box.left - winW; + var overlapX = box.right - winW; if (overlapX > 0) { if (box.right - box.left > winW) { hints.style.width = (winW - 5) + "px"; @@ -226,7 +257,7 @@ hints.style.left = (left = pos.left - overlapX) + "px"; } - cm.addKeyMap(this.keyMap = buildKeyMap(options, { + cm.addKeyMap(this.keyMap = buildKeyMap(completion, { moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); }, setFocus: function(n) { widget.changeActive(n); }, menuSize: function() { return widget.screenAmount(); }, @@ -236,7 +267,7 @@ data: data })); - if (options.closeOnUnfocus !== false) { + if (completion.options.closeOnUnfocus) { var closingOnBlur; cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); }); cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); }); @@ -262,7 +293,7 @@ var t = getHintElement(hints, e.target || e.srcElement); if (t && t.hintId != null) { widget.changeActive(t.hintId); - if (options.completeOnSingleClick) widget.pick(); + if (completion.options.completeOnSingleClick) widget.pick(); } }); @@ -282,7 +313,7 @@ this.completion.cm.removeKeyMap(this.keyMap); var cm = this.completion.cm; - if (this.completion.options.closeOnUnfocus !== false) { + if (this.completion.options.closeOnUnfocus) { cm.off("blur", this.onBlur); cm.off("focus", this.onFocus); } @@ -346,4 +377,18 @@ }); CodeMirror.commands.autocomplete = CodeMirror.showHint; + + var defaultOptions = { + hint: CodeMirror.hint.auto, + completeSingle: true, + alignWithWord: true, + closeCharacters: /[\s()\[\]{};:>,]/, + closeOnUnfocus: true, + completeOnSingleClick: false, + container: null, + customKeys: null, + extraKeys: null + }; + + CodeMirror.defineOption("hintOptions", null); }); diff --git a/applications/admin/static/codemirror/addon/hint/sql-hint.js b/applications/admin/static/codemirror/addon/hint/sql-hint.js new file mode 100644 index 00000000..5b0cc769 --- /dev/null +++ b/applications/admin/static/codemirror/addon/hint/sql-hint.js @@ -0,0 +1,240 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../../mode/sql/sql")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../../mode/sql/sql"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var tables; + var defaultTable; + var keywords; + var CONS = { + QUERY_DIV: ";", + ALIAS_KEYWORD: "AS" + }; + var Pos = CodeMirror.Pos; + + function getKeywords(editor) { + var mode = editor.doc.modeOption; + if (mode === "sql") mode = "text/x-sql"; + return CodeMirror.resolveMode(mode).keywords; + } + + function getText(item) { + return typeof item == "string" ? item : item.text; + } + + function getItem(list, item) { + if (!list.slice) return list[item]; + for (var i = list.length - 1; i >= 0; i--) if (getText(list[i]) == item) + return list[i]; + } + + function shallowClone(object) { + var result = {}; + for (var key in object) if (object.hasOwnProperty(key)) + result[key] = object[key]; + return result; + } + + function match(string, word) { + var len = string.length; + var sub = getText(word).substr(0, len); + return string.toUpperCase() === sub.toUpperCase(); + } + + function addMatches(result, search, wordlist, formatter) { + for (var word in wordlist) { + if (!wordlist.hasOwnProperty(word)) continue; + if (Array.isArray(wordlist)) { + word = wordlist[word]; + } + if (match(search, word)) { + result.push(formatter(word)); + } + } + } + + function cleanName(name) { + // Get rid name from backticks(`) and preceding dot(.) + if (name.charAt(0) == ".") { + name = name.substr(1); + } + return name.replace(/`/g, ""); + } + + function insertBackticks(name) { + var nameParts = getText(name).split("."); + for (var i = 0; i < nameParts.length; i++) + nameParts[i] = "`" + nameParts[i] + "`"; + var escaped = nameParts.join("."); + if (typeof name == "string") return escaped; + name = shallowClone(name); + name.text = escaped; + return name; + } + + function nameCompletion(cur, token, result, editor) { + // Try to complete table, colunm names and return start position of completion + var useBacktick = false; + var nameParts = []; + var start = token.start; + var cont = true; + while (cont) { + cont = (token.string.charAt(0) == "."); + useBacktick = useBacktick || (token.string.charAt(0) == "`"); + + start = token.start; + nameParts.unshift(cleanName(token.string)); + + token = editor.getTokenAt(Pos(cur.line, token.start)); + if (token.string == ".") { + cont = true; + token = editor.getTokenAt(Pos(cur.line, token.start)); + } + } + + // Try to complete table names + var string = nameParts.join("."); + addMatches(result, string, tables, function(w) { + return useBacktick ? insertBackticks(w) : w; + }); + + // Try to complete columns from defaultTable + addMatches(result, string, defaultTable, function(w) { + return useBacktick ? insertBackticks(w) : w; + }); + + // Try to complete columns + string = nameParts.pop(); + var table = nameParts.join("."); + + // Check if table is available. If not, find table by Alias + if (!getItem(tables, table)) + table = findTableByAlias(table, editor); + + var columns = getItem(tables, table); + if (columns && Array.isArray(tables) && columns.columns) + columns = columns.columns; + + if (columns) { + addMatches(result, string, columns, function(w) { + if (typeof w == "string") { + w = table + "." + w; + } else { + w = shallowClone(w); + w.text = table + "." + w.text; + } + return useBacktick ? insertBackticks(w) : w; + }); + } + + return start; + } + + function eachWord(lineText, f) { + if (!lineText) return; + var excepted = /[,;]/g; + var words = lineText.split(" "); + for (var i = 0; i < words.length; i++) { + f(words[i]?words[i].replace(excepted, '') : ''); + } + } + + function convertCurToNumber(cur) { + // max characters of a line is 999,999. + return cur.line + cur.ch / Math.pow(10, 6); + } + + function convertNumberToCur(num) { + return Pos(Math.floor(num), +num.toString().split('.').pop()); + } + + function findTableByAlias(alias, editor) { + var doc = editor.doc; + var fullQuery = doc.getValue(); + var aliasUpperCase = alias.toUpperCase(); + var previousWord = ""; + var table = ""; + var separator = []; + var validRange = { + start: Pos(0, 0), + end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length) + }; + + //add separator + var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV); + while(indexOfSeparator != -1) { + separator.push(doc.posFromIndex(indexOfSeparator)); + indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1); + } + separator.unshift(Pos(0, 0)); + separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length)); + + //find valid range + var prevItem = 0; + var current = convertCurToNumber(editor.getCursor()); + for (var i=0; i< separator.length; i++) { + var _v = convertCurToNumber(separator[i]); + if (current > prevItem && current <= _v) { + validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) }; + break; + } + prevItem = _v; + } + + var query = doc.getRange(validRange.start, validRange.end, false); + + for (var i = 0; i < query.length; i++) { + var lineText = query[i]; + eachWord(lineText, function(word) { + var wordUpperCase = word.toUpperCase(); + if (wordUpperCase === aliasUpperCase && getItem(tables, previousWord)) + table = previousWord; + if (wordUpperCase !== CONS.ALIAS_KEYWORD) + previousWord = word; + }); + if (table) break; + } + return table; + } + + CodeMirror.registerHelper("hint", "sql", function(editor, options) { + tables = (options && options.tables) || {}; + var defaultTableName = options && options.defaultTable; + defaultTable = (defaultTableName && getItem(tables, defaultTableName)) || []; + keywords = keywords || getKeywords(editor); + + var cur = editor.getCursor(); + var result = []; + var token = editor.getTokenAt(cur), start, end, search; + if (token.end > cur.ch) { + token.end = cur.ch; + token.string = token.string.slice(0, cur.ch - token.start); + } + + if (token.string.match(/^[.`\w@]\w*$/)) { + search = token.string; + start = token.start; + end = token.end; + } else { + start = end = cur.ch; + search = ""; + } + if (search.charAt(0) == "." || search.charAt(0) == "`") { + start = nameCompletion(cur, token, result, editor); + } else { + addMatches(result, search, tables, function(w) {return w;}); + addMatches(result, search, defaultTable, function(w) {return w;}); + addMatches(result, search, keywords, function(w) {return w.toUpperCase();}); + } + + return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)}; + }); +}); diff --git a/applications/admin/static/codemirror/addon/hint/xml-hint.js b/applications/admin/static/codemirror/addon/hint/xml-hint.js index 85756490..9b9baa0c 100644 --- a/applications/admin/static/codemirror/addon/hint/xml-hint.js +++ b/applications/admin/static/codemirror/addon/hint/xml-hint.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -15,30 +18,55 @@ var quote = (options && options.quoteChar) || '"'; if (!tags) return; var cur = cm.getCursor(), token = cm.getTokenAt(cur); + if (token.end > cur.ch) { + token.end = cur.ch; + token.string = token.string.slice(0, cur.ch - token.start); + } var inner = CodeMirror.innerMode(cm.getMode(), token.state); if (inner.mode.name != "xml") return; var result = [], replaceToken = false, prefix; - var isTag = token.string.charAt(0) == "<"; - if (!inner.state.tagName || isTag) { // Tag completion - if (isTag) { - prefix = token.string.slice(1); - replaceToken = true; - } + var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string); + var tagName = tag && /^\w/.test(token.string), tagStart; + + if (tagName) { + var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start); + var tagType = /<\/$/.test(before) ? "close" : /<$/.test(before) ? "open" : null; + if (tagType) tagStart = token.start - (tagType == "close" ? 2 : 1); + } else if (tag && token.string == "<") { + tagType = "open"; + } else if (tag && token.string == ""); } else { // Attribute completion var curTag = tags[inner.state.tagName], attrs = curTag && curTag.attrs; - if (!attrs) return; + var globalAttrs = tags["!attrs"]; + if (!attrs && !globalAttrs) return; + if (!attrs) { + attrs = globalAttrs; + } else if (globalAttrs) { // Combine tag-local and global attributes + var set = {}; + for (var nm in globalAttrs) if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm]; + for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm]; + attrs = set; + } if (token.type == "string" || token.string == "=") { // A value var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)), Pos(cur.line, token.type == "string" ? token.start : token.end)); @@ -47,9 +75,16 @@ if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget if (token.type == "string") { prefix = token.string; + var n = 0; if (/['"]/.test(token.string.charAt(0))) { quote = token.string.charAt(0); prefix = token.string.slice(1); + n++; + } + var len = token.string.length; + if (/['"]/.test(token.string.charAt(len - 1))) { + quote = token.string.charAt(len - 1); + prefix = token.string.substr(n, len - 2); } replaceToken = true; } @@ -66,7 +101,7 @@ } return { list: result, - from: replaceToken ? Pos(cur.line, token.start) : cur, + from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur, to: replaceToken ? Pos(cur.line, token.end) : cur }; } diff --git a/applications/admin/static/codemirror/addon/lint/coffeescript-lint.js b/applications/admin/static/codemirror/addon/lint/coffeescript-lint.js deleted file mode 100644 index 6df17f8f..00000000 --- a/applications/admin/static/codemirror/addon/lint/coffeescript-lint.js +++ /dev/null @@ -1,38 +0,0 @@ -// Depends on coffeelint.js from http://www.coffeelint.org/js/coffeelint.js - -// declare global: coffeelint - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { -"use strict"; - -CodeMirror.registerHelper("lint", "coffeescript", function(text) { - var found = []; - var parseError = function(err) { - var loc = err.lineNumber; - found.push({from: CodeMirror.Pos(loc-1, 0), - to: CodeMirror.Pos(loc, 0), - severity: err.level, - message: err.message}); - }; - try { - var res = coffeelint.lint(text); - for(var i = 0; i < res.length; i++) { - parseError(res[i]); - } - } catch(e) { - found.push({from: CodeMirror.Pos(e.location.first_line, 0), - to: CodeMirror.Pos(e.location.last_line, e.location.last_column), - severity: 'error', - message: e.message}); - } - return found; -}); - -}); diff --git a/applications/admin/static/codemirror/addon/lint/javascript-lint.js b/applications/admin/static/codemirror/addon/lint/javascript-lint.js deleted file mode 100644 index bbb51083..00000000 --- a/applications/admin/static/codemirror/addon/lint/javascript-lint.js +++ /dev/null @@ -1,132 +0,0 @@ -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - // declare global: JSHINT - - var bogus = [ "Dangerous comment" ]; - - var warnings = [ [ "Expected '{'", - "Statement body should be inside '{ }' braces." ] ]; - - var errors = [ "Missing semicolon", "Extra comma", "Missing property name", - "Unmatched ", " and instead saw", " is not defined", - "Unclosed string", "Stopping, unable to continue" ]; - - function validator(text, options) { - JSHINT(text, options); - var errors = JSHINT.data().errors, result = []; - if (errors) parseErrors(errors, result); - return result; - } - - CodeMirror.registerHelper("lint", "javascript", validator); - - function cleanup(error) { - // All problems are warnings by default - fixWith(error, warnings, "warning", true); - fixWith(error, errors, "error"); - - return isBogus(error) ? null : error; - } - - function fixWith(error, fixes, severity, force) { - var description, fix, find, replace, found; - - description = error.description; - - for ( var i = 0; i < fixes.length; i++) { - fix = fixes[i]; - find = (typeof fix === "string" ? fix : fix[0]); - replace = (typeof fix === "string" ? null : fix[1]); - found = description.indexOf(find) !== -1; - - if (force || found) { - error.severity = severity; - } - if (found && replace) { - error.description = replace; - } - } - } - - function isBogus(error) { - var description = error.description; - for ( var i = 0; i < bogus.length; i++) { - if (description.indexOf(bogus[i]) !== -1) { - return true; - } - } - return false; - } - - function parseErrors(errors, output) { - for ( var i = 0; i < errors.length; i++) { - var error = errors[i]; - if (error) { - var linetabpositions, index; - - linetabpositions = []; - - // This next block is to fix a problem in jshint. Jshint - // replaces - // all tabs with spaces then performs some checks. The error - // positions (character/space) are then reported incorrectly, - // not taking the replacement step into account. Here we look - // at the evidence line and try to adjust the character position - // to the correct value. - if (error.evidence) { - // Tab positions are computed once per line and cached - var tabpositions = linetabpositions[error.line]; - if (!tabpositions) { - var evidence = error.evidence; - tabpositions = []; - // ugggh phantomjs does not like this - // forEachChar(evidence, function(item, index) { - Array.prototype.forEach.call(evidence, function(item, - index) { - if (item === '\t') { - // First col is 1 (not 0) to match error - // positions - tabpositions.push(index + 1); - } - }); - linetabpositions[error.line] = tabpositions; - } - if (tabpositions.length > 0) { - var pos = error.character; - tabpositions.forEach(function(tabposition) { - if (pos > tabposition) pos -= 1; - }); - error.character = pos; - } - } - - var start = error.character - 1, end = start + 1; - if (error.evidence) { - index = error.evidence.substring(start).search(/.\b/); - if (index > -1) { - end += index; - } - } - - // Convert to format expected by validation service - error.description = error.reason;// + "(jshint)"; - error.start = error.character; - error.end = end; - error = cleanup(error); - - if (error) - output.push({message: error.description, - severity: error.severity, - from: CodeMirror.Pos(error.line - 1, start), - to: CodeMirror.Pos(error.line - 1, end)}); - } - } - } -}); diff --git a/applications/admin/static/codemirror/addon/lint/json-lint.js b/applications/admin/static/codemirror/addon/lint/json-lint.js deleted file mode 100644 index 1f5f82d0..00000000 --- a/applications/admin/static/codemirror/addon/lint/json-lint.js +++ /dev/null @@ -1,28 +0,0 @@ -// Depends on jsonlint.js from https://github.com/zaach/jsonlint - -// declare global: jsonlint - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { -"use strict"; - -CodeMirror.registerHelper("lint", "json", function(text) { - var found = []; - jsonlint.parseError = function(str, hash) { - var loc = hash.loc; - found.push({from: CodeMirror.Pos(loc.first_line - 1, loc.first_column), - to: CodeMirror.Pos(loc.last_line - 1, loc.last_column), - message: str}); - }; - try { jsonlint.parse(text); } - catch(e) {} - return found; -}); - -}); diff --git a/applications/admin/static/codemirror/addon/lint/lint.css b/applications/admin/static/codemirror/addon/lint/lint.css deleted file mode 100644 index 414a9a0e..00000000 --- a/applications/admin/static/codemirror/addon/lint/lint.css +++ /dev/null @@ -1,73 +0,0 @@ -/* The lint marker gutter */ -.CodeMirror-lint-markers { - width: 16px; -} - -.CodeMirror-lint-tooltip { - background-color: infobackground; - border: 1px solid black; - border-radius: 4px 4px 4px 4px; - color: infotext; - font-family: monospace; - font-size: 10pt; - overflow: hidden; - padding: 2px 5px; - position: fixed; - white-space: pre; - white-space: pre-wrap; - z-index: 100; - max-width: 600px; - opacity: 0; - transition: opacity .4s; - -moz-transition: opacity .4s; - -webkit-transition: opacity .4s; - -o-transition: opacity .4s; - -ms-transition: opacity .4s; -} - -.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { - background-position: left bottom; - background-repeat: repeat-x; -} - -.CodeMirror-lint-mark-error { - background-image: - url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==") - ; -} - -.CodeMirror-lint-mark-warning { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); -} - -.CodeMirror-lint-marker-error, .CodeMirror-lint-marker-warning { - background-position: center center; - background-repeat: no-repeat; - cursor: pointer; - display: inline-block; - height: 16px; - width: 16px; - vertical-align: middle; - position: relative; -} - -.CodeMirror-lint-message-error, .CodeMirror-lint-message-warning { - padding-left: 18px; - background-position: top left; - background-repeat: no-repeat; -} - -.CodeMirror-lint-marker-error, .CodeMirror-lint-message-error { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII="); -} - -.CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); -} - -.CodeMirror-lint-marker-multiple { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"); - background-repeat: no-repeat; - background-position: right bottom; - width: 100%; height: 100%; -} diff --git a/applications/admin/static/codemirror/addon/lint/lint.js b/applications/admin/static/codemirror/addon/lint/lint.js deleted file mode 100644 index 393a6890..00000000 --- a/applications/admin/static/codemirror/addon/lint/lint.js +++ /dev/null @@ -1,207 +0,0 @@ -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - var GUTTER_ID = "CodeMirror-lint-markers"; - var SEVERITIES = /^(?:error|warning)$/; - - function showTooltip(e, content) { - var tt = document.createElement("div"); - tt.className = "CodeMirror-lint-tooltip"; - tt.appendChild(content.cloneNode(true)); - document.body.appendChild(tt); - - function position(e) { - if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position); - tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px"; - tt.style.left = (e.clientX + 5) + "px"; - } - CodeMirror.on(document, "mousemove", position); - position(e); - if (tt.style.opacity != null) tt.style.opacity = 1; - return tt; - } - function rm(elt) { - if (elt.parentNode) elt.parentNode.removeChild(elt); - } - function hideTooltip(tt) { - if (!tt.parentNode) return; - if (tt.style.opacity == null) rm(tt); - tt.style.opacity = 0; - setTimeout(function() { rm(tt); }, 600); - } - - function showTooltipFor(e, content, node) { - var tooltip = showTooltip(e, content); - function hide() { - CodeMirror.off(node, "mouseout", hide); - if (tooltip) { hideTooltip(tooltip); tooltip = null; } - } - var poll = setInterval(function() { - if (tooltip) for (var n = node;; n = n.parentNode) { - if (n == document.body) return; - if (!n) { hide(); break; } - } - if (!tooltip) return clearInterval(poll); - }, 400); - CodeMirror.on(node, "mouseout", hide); - } - - function LintState(cm, options, hasGutter) { - this.marked = []; - this.options = options; - this.timeout = null; - this.hasGutter = hasGutter; - this.onMouseOver = function(e) { onMouseOver(cm, e); }; - } - - function parseOptions(cm, options) { - if (options instanceof Function) return {getAnnotations: options}; - if (!options || options === true) options = {}; - if (!options.getAnnotations) options.getAnnotations = cm.getHelper(CodeMirror.Pos(0, 0), "lint"); - if (!options.getAnnotations) throw new Error("Required option 'getAnnotations' missing (lint addon)"); - return options; - } - - function clearMarks(cm) { - var state = cm.state.lint; - if (state.hasGutter) cm.clearGutter(GUTTER_ID); - for (var i = 0; i < state.marked.length; ++i) - state.marked[i].clear(); - state.marked.length = 0; - } - - function makeMarker(labels, severity, multiple, tooltips) { - var marker = document.createElement("div"), inner = marker; - marker.className = "CodeMirror-lint-marker-" + severity; - if (multiple) { - inner = marker.appendChild(document.createElement("div")); - inner.className = "CodeMirror-lint-marker-multiple"; - } - - if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) { - showTooltipFor(e, labels, inner); - }); - - return marker; - } - - function getMaxSeverity(a, b) { - if (a == "error") return a; - else return b; - } - - function groupByLine(annotations) { - var lines = []; - for (var i = 0; i < annotations.length; ++i) { - var ann = annotations[i], line = ann.from.line; - (lines[line] || (lines[line] = [])).push(ann); - } - return lines; - } - - function annotationTooltip(ann) { - var severity = ann.severity; - if (!SEVERITIES.test(severity)) severity = "error"; - var tip = document.createElement("div"); - tip.className = "CodeMirror-lint-message-" + severity; - tip.appendChild(document.createTextNode(ann.message)); - return tip; - } - - function startLinting(cm) { - var state = cm.state.lint, options = state.options; - if (options.async) - options.getAnnotations(cm, updateLinting, options); - else - updateLinting(cm, options.getAnnotations(cm.getValue(), options.options)); - } - - function updateLinting(cm, annotationsNotSorted) { - clearMarks(cm); - var state = cm.state.lint, options = state.options; - - var annotations = groupByLine(annotationsNotSorted); - - for (var line = 0; line < annotations.length; ++line) { - var anns = annotations[line]; - if (!anns) continue; - - var maxSeverity = null; - var tipLabel = state.hasGutter && document.createDocumentFragment(); - - for (var i = 0; i < anns.length; ++i) { - var ann = anns[i]; - var severity = ann.severity; - if (!SEVERITIES.test(severity)) severity = "error"; - maxSeverity = getMaxSeverity(maxSeverity, severity); - - if (options.formatAnnotation) ann = options.formatAnnotation(ann); - if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann)); - - if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, { - className: "CodeMirror-lint-mark-" + severity, - __annotation: ann - })); - } - - if (state.hasGutter) - cm.setGutterMarker(line, GUTTER_ID, makeMarker(tipLabel, maxSeverity, anns.length > 1, - state.options.tooltips)); - } - if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm); - } - - function onChange(cm) { - var state = cm.state.lint; - clearTimeout(state.timeout); - state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500); - } - - function popupSpanTooltip(ann, e) { - var target = e.target || e.srcElement; - showTooltipFor(e, annotationTooltip(ann), target); - } - - // When the mouseover fires, the cursor might not actually be over - // the character itself yet. These pairs of x,y offsets are used to - // probe a few nearby points when no suitable marked range is found. - var nearby = [0, 0, 0, 5, 0, -5, 5, 0, -5, 0]; - - function onMouseOver(cm, e) { - if (!/\bCodeMirror-lint-mark-/.test((e.target || e.srcElement).className)) return; - for (var i = 0; i < nearby.length; i += 2) { - var spans = cm.findMarksAt(cm.coordsChar({left: e.clientX + nearby[i], - top: e.clientY + nearby[i + 1]}, "client")); - for (var j = 0; j < spans.length; ++j) { - var span = spans[j], ann = span.__annotation; - if (ann) return popupSpanTooltip(ann, e); - } - } - } - - CodeMirror.defineOption("lint", false, function(cm, val, old) { - if (old && old != CodeMirror.Init) { - clearMarks(cm); - cm.off("change", onChange); - CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver); - delete cm.state.lint; - } - - if (val) { - var gutters = cm.getOption("gutters"), hasLintGutter = false; - for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true; - var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter); - cm.on("change", onChange); - if (state.options.tooltips != false) - CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver); - - startLinting(cm); - } - }); -}); diff --git a/applications/admin/static/codemirror/addon/merge/dep/diff_match_patch.js b/applications/admin/static/codemirror/addon/merge/dep/diff_match_patch.js deleted file mode 100644 index 9d615dc9..00000000 --- a/applications/admin/static/codemirror/addon/merge/dep/diff_match_patch.js +++ /dev/null @@ -1,50 +0,0 @@ -// From https://code.google.com/p/google-diff-match-patch/ , licensed under the Apache License 2.0 -(function(){function diff_match_patch(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=0.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=0.5;this.Patch_Margin=4;this.Match_MaxBits=32} -diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[[0,a]]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);var f=this.diff_commonSuffix(a,b),g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,b.length-f);a=this.diff_compute_(a, -b,e,d);c&&a.unshift([0,c]);g&&a.push([0,g]);this.diff_cleanupMerge(a);return a}; -diff_match_patch.prototype.diff_compute_=function(a,b,c,d){if(!a)return[[1,b]];if(!b)return[[-1,a]];var e=a.length>b.length?a:b,f=a.length>b.length?b:a,g=e.indexOf(f);return-1!=g?(c=[[1,e.substring(0,g)],[0,f],[1,e.substring(g+f.length)]],a.length>b.length&&(c[0][0]=c[2][0]=-1),c):1==f.length?[[-1,a],[1,b]]:(e=this.diff_halfMatch_(a,b))?(f=e[0],a=e[1],g=e[2],b=e[3],e=e[4],f=this.diff_main(f,g,c,d),c=this.diff_main(a,b,c,d),f.concat([[0,e]],c)):c&&100c);v++){for(var n=-v+r;n<=v-t;n+=2){var l=g+n,m;m=n==-v||n!=v&&j[l-1]d)t+=2;else if(s>e)r+=2;else if(q&&(l=g+k-n,0<=l&&l= -u)return this.diff_bisectSplit_(a,b,m,s,c)}}for(n=-v+p;n<=v-w;n+=2){l=g+n;u=n==-v||n!=v&&i[l-1]d)w+=2;else if(m>e)p+=2;else if(!q&&(l=g+k-n,0<=l&&(l=u)))return this.diff_bisectSplit_(a,b,m,s,c)}}return[[-1,a],[1,b]]}; -diff_match_patch.prototype.diff_bisectSplit_=function(a,b,c,d,e){var f=a.substring(0,c),g=b.substring(0,d);a=a.substring(c);b=b.substring(d);f=this.diff_main(f,g,!1,e);e=this.diff_main(a,b,!1,e);return f.concat(e)}; -diff_match_patch.prototype.diff_linesToChars_=function(a,b){function c(a){for(var b="",c=0,f=-1,g=d.length;fd?a=a.substring(c-d):c=a.length?[h,j,n,l,g]:null}if(0>=this.Diff_Timeout)return null; -var d=a.length>b.length?a:b,e=a.length>b.length?b:a;if(4>d.length||2*e.lengthd[4].length?g:d:d:g;var j;a.length>b.length?(g=h[0],d=h[1],e=h[2],j=h[3]):(e=h[0],j=h[1],g=h[2],d=h[3]);h=h[4];return[g,d,e,j,h]}; -diff_match_patch.prototype.diff_cleanupSemantic=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=0,h=0,j=0,i=0;f=e){if(d>=b.length/2||d>=c.length/2)a.splice(f,0,[0,c.substring(0,d)]),a[f-1][1]=b.substring(0,b.length-d),a[f+1][1]=c.substring(d),f++}else if(e>=b.length/2||e>=c.length/2)a.splice(f,0,[0,b.substring(0,e)]),a[f-1][0]=1,a[f-1][1]=c.substring(0,c.length-e),a[f+1][0]=-1,a[f+1][1]=b.substring(e),f++;f++}f++}}; -diff_match_patch.prototype.diff_cleanupSemanticLossless=function(a){function b(a,b){if(!a||!b)return 6;var c=a.charAt(a.length-1),d=b.charAt(0),e=c.match(diff_match_patch.nonAlphaNumericRegex_),f=d.match(diff_match_patch.nonAlphaNumericRegex_),g=e&&c.match(diff_match_patch.whitespaceRegex_),h=f&&d.match(diff_match_patch.whitespaceRegex_),c=g&&c.match(diff_match_patch.linebreakRegex_),d=h&&d.match(diff_match_patch.linebreakRegex_),i=c&&a.match(diff_match_patch.blanklineEndRegex_),j=d&&b.match(diff_match_patch.blanklineStartRegex_); -return i||j?5:c||d?4:e&&!g&&h?3:g||h?2:e||f?1:0}for(var c=1;c=i&&(i=k,g=d,h=e,j=f)}a[c-1][1]!=g&&(g?a[c-1][1]=g:(a.splice(c-1,1),c--),a[c][1]= -h,j?a[c+1][1]=j:(a.splice(c+1,1),c--))}c++}};diff_match_patch.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/;diff_match_patch.whitespaceRegex_=/\s/;diff_match_patch.linebreakRegex_=/[\r\n]/;diff_match_patch.blanklineEndRegex_=/\n\r?\n$/;diff_match_patch.blanklineStartRegex_=/^\r?\n\r?\n/; -diff_match_patch.prototype.diff_cleanupEfficiency=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=!1,h=!1,j=!1,i=!1;fb)break;e=c;f=d}return a.length!=g&&-1===a[g][0]?f:f+(b-e)}; -diff_match_patch.prototype.diff_prettyHtml=function(a){for(var b=[],c=/&/g,d=//g,f=/\n/g,g=0;g");switch(h){case 1:b[g]=''+j+"";break;case -1:b[g]=''+j+"";break;case 0:b[g]=""+j+""}}return b.join("")}; -diff_match_patch.prototype.diff_text1=function(a){for(var b=[],c=0;cthis.Match_MaxBits)throw Error("Pattern too long for this browser.");var e=this.match_alphabet_(b),f=this,g=this.Match_Threshold,h=a.indexOf(b,c);-1!=h&&(g=Math.min(d(0,h),g),h=a.lastIndexOf(b,c+b.length),-1!=h&&(g=Math.min(d(0,h),g)));for(var j=1<=i;p--){var w=e[a.charAt(p-1)];k[p]=0===t?(k[p+1]<<1|1)&w:(k[p+1]<<1|1)&w|((r[p+1]|r[p])<<1|1)|r[p+1];if(k[p]&j&&(w=d(t,p-1),w<=g))if(g=w,h=p-1,h>c)i=Math.max(1,2*c-h);else break}if(d(t+1,c)>g)break;r=k}return h}; -diff_match_patch.prototype.match_alphabet_=function(a){for(var b={},c=0;c=2*this.Patch_Margin&& -e&&(this.patch_addContext_(a,h),c.push(a),a=new diff_match_patch.patch_obj,e=0,h=d,f=g)}1!==i&&(f+=k.length);-1!==i&&(g+=k.length)}e&&(this.patch_addContext_(a,h),c.push(a));return c};diff_match_patch.prototype.patch_deepCopy=function(a){for(var b=[],c=0;cthis.Match_MaxBits){if(j=this.match_main(b,h.substring(0,this.Match_MaxBits),g),-1!=j&&(i=this.match_main(b,h.substring(h.length-this.Match_MaxBits),g+h.length-this.Match_MaxBits),-1==i||j>=i))j=-1}else j=this.match_main(b,h,g); -if(-1==j)e[f]=!1,d-=a[f].length2-a[f].length1;else if(e[f]=!0,d=j-g,g=-1==i?b.substring(j,j+h.length):b.substring(j,i+this.Match_MaxBits),h==g)b=b.substring(0,j)+this.diff_text2(a[f].diffs)+b.substring(j+h.length);else if(g=this.diff_main(h,g,!1),h.length>this.Match_MaxBits&&this.diff_levenshtein(g)/h.length>this.Patch_DeleteThreshold)e[f]=!1;else{this.diff_cleanupSemanticLossless(g);for(var h=0,k,i=0;ie[0][1].length){var f=b-e[0][1].length;e[0][1]=c.substring(e[0][1].length)+e[0][1];d.start1-=f;d.start2-=f;d.length1+=f;d.length2+=f}d=a[a.length-1];e=d.diffs;0==e.length||0!=e[e.length-1][0]?(e.push([0, -c]),d.length1+=b,d.length2+=b):b>e[e.length-1][1].length&&(f=b-e[e.length-1][1].length,e[e.length-1][1]+=c.substring(0,f),d.length1+=f,d.length2+=f);return c}; -diff_match_patch.prototype.patch_splitMax=function(a){for(var b=this.Match_MaxBits,c=0;c2*b?(h.length1+=i.length,e+=i.length,j=!1,h.diffs.push([g,i]),d.diffs.shift()):(i=i.substring(0,b-h.length1-this.Patch_Margin),h.length1+=i.length,e+=i.length,0===g?(h.length2+=i.length,f+=i.length):j=!1,h.diffs.push([g,i]),i==d.diffs[0][1]?d.diffs.shift():d.diffs[0][1]=d.diffs[0][1].substring(i.length))}g=this.diff_text2(h.diffs);g=g.substring(g.length-this.Patch_Margin);i=this.diff_text1(d.diffs).substring(0,this.Patch_Margin);""!==i&& -(h.length1+=i.length,h.length2+=i.length,0!==h.diffs.length&&0===h.diffs[h.diffs.length-1][0]?h.diffs[h.diffs.length-1][1]+=i:h.diffs.push([0,i]));j||a.splice(++c,0,h)}}};diff_match_patch.prototype.patch_toText=function(a){for(var b=[],c=0;c now) return false; - - var sInfo = editor.getScrollInfo(), halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen; - var mid = editor.lineAtHeight(midY, "local"); - var around = chunkBoundariesAround(dv.diff, mid, type == DIFF_INSERT); - var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig); - var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit); - var ratio = (midY - off.top) / (off.bot - off.top); - var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top); - - var botDist, mix; - // Some careful tweaking to make sure no space is left out of view - // when scrolling to top or bottom. - if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) { - targetPos = targetPos * mix + sInfo.top * (1 - mix); - } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) { - var otherInfo = other.getScrollInfo(); - var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos; - if (botDistOther > botDist && (mix = botDist / halfScreen) < 1) - targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix); - } - - other.scrollTo(sInfo.left, targetPos); - other.state.scrollSetAt = now; - other.state.scrollSetBy = dv; - return true; - } - - function getOffsets(editor, around) { - var bot = around.after; - if (bot == null) bot = editor.lastLine() + 1; - return {top: editor.heightAtLine(around.before || 0, "local"), - bot: editor.heightAtLine(bot, "local")}; - } - - function setScrollLock(dv, val, action) { - dv.lockScroll = val; - if (val && action != false) syncScroll(dv, DIFF_INSERT) && drawConnectors(dv); - dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db  \u21da"; - } - - // Updating the marks for editor content - - function clearMarks(editor, arr, classes) { - for (var i = 0; i < arr.length; ++i) { - var mark = arr[i]; - if (mark instanceof CodeMirror.TextMarker) { - mark.clear(); - } else { - editor.removeLineClass(mark, "background", classes.chunk); - editor.removeLineClass(mark, "background", classes.start); - editor.removeLineClass(mark, "background", classes.end); - } - } - arr.length = 0; - } - - // FIXME maybe add a margin around viewport to prevent too many updates - function updateMarks(editor, diff, state, type, classes) { - var vp = editor.getViewport(); - editor.operation(function() { - if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { - clearMarks(editor, state.marked, classes); - markChanges(editor, diff, type, state.marked, vp.from, vp.to, classes); - state.from = vp.from; state.to = vp.to; - } else { - if (vp.from < state.from) { - markChanges(editor, diff, type, state.marked, vp.from, state.from, classes); - state.from = vp.from; - } - if (vp.to > state.to) { - markChanges(editor, diff, type, state.marked, state.to, vp.to, classes); - state.to = vp.to; - } - } - }); - } - - function markChanges(editor, diff, type, marks, from, to, classes) { - var pos = Pos(0, 0); - var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1)); - var cls = type == DIFF_DELETE ? classes.del : classes.insert; - function markChunk(start, end) { - var bfrom = Math.max(from, start), bto = Math.min(to, end); - for (var i = bfrom; i < bto; ++i) { - var line = editor.addLineClass(i, "background", classes.chunk); - if (i == start) editor.addLineClass(line, "background", classes.start); - if (i == end - 1) editor.addLineClass(line, "background", classes.end); - marks.push(line); - } - // When the chunk is empty, make sure a horizontal line shows up - if (start == end && bfrom == end && bto == end) { - if (bfrom) - marks.push(editor.addLineClass(bfrom - 1, "background", classes.end)); - else - marks.push(editor.addLineClass(bfrom, "background", classes.start)); - } - } - - var chunkStart = 0; - for (var i = 0; i < diff.length; ++i) { - var part = diff[i], tp = part[0], str = part[1]; - if (tp == DIFF_EQUAL) { - var cleanFrom = pos.line + (startOfLineClean(diff, i) ? 0 : 1); - moveOver(pos, str); - var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0); - if (cleanTo > cleanFrom) { - if (i) markChunk(chunkStart, cleanFrom); - chunkStart = cleanTo; - } - } else { - if (tp == type) { - var end = moveOver(pos, str, true); - var a = posMax(top, pos), b = posMin(bot, end); - if (!posEq(a, b)) - marks.push(editor.markText(a, b, {className: cls})); - pos = end; - } - } - } - if (chunkStart <= pos.line) markChunk(chunkStart, pos.line + 1); - } - - // Updating the gap between editor and original - - function drawConnectors(dv) { - if (!dv.showDifferences) return; - - if (dv.svg) { - clear(dv.svg); - var w = dv.gap.offsetWidth; - attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight); - } - clear(dv.copyButtons); - - var flip = dv.type == "left"; - var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); - var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top; - iterateChunks(dv.diff, function(topOrig, botOrig, topEdit, botEdit) { - if (topEdit > vpEdit.to || botEdit < vpEdit.from || - topOrig > vpOrig.to || botOrig < vpOrig.from) - return; - var topLpx = dv.orig.heightAtLine(topOrig, "local") - sTopOrig, top = topLpx; - if (dv.svg) { - var topRpx = dv.edit.heightAtLine(topEdit, "local") - sTopEdit; - if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; } - var botLpx = dv.orig.heightAtLine(botOrig, "local") - sTopOrig; - var botRpx = dv.edit.heightAtLine(botEdit, "local") - sTopEdit; - if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; } - var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx; - var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx; - attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")), - "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z", - "class", dv.classes.connect); - } - var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", - "CodeMirror-merge-copy")); - copy.title = "Revert chunk"; - copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig}; - copy.style.top = top + "px"; - }); - } - - function copyChunk(dv, chunk) { - if (dv.diffOutOfDate) return; - dv.edit.replaceRange(dv.orig.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)), - Pos(chunk.topEdit, 0), Pos(chunk.botEdit, 0)); - } - - // Merge view, containing 0, 1, or 2 diff views. - - var MergeView = CodeMirror.MergeView = function(node, options) { - if (!(this instanceof MergeView)) return new MergeView(node, options); - - var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; - var hasLeft = origLeft != null, hasRight = origRight != null; - var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); - var wrap = [], left = this.left = null, right = this.right = null; - - if (hasLeft) { - left = this.left = new DiffView(this, "left"); - var leftPane = elt("div", null, "CodeMirror-merge-pane"); - wrap.push(leftPane); - wrap.push(buildGap(left)); - } - - var editPane = elt("div", null, "CodeMirror-merge-pane"); - wrap.push(editPane); - - if (hasRight) { - right = this.right = new DiffView(this, "right"); - wrap.push(buildGap(right)); - var rightPane = elt("div", null, "CodeMirror-merge-pane"); - wrap.push(rightPane); - } - - (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost"; - - wrap.push(elt("div", null, null, "height: 0; clear: both;")); - var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane")); - this.edit = CodeMirror(editPane, copyObj(options)); - - if (left) left.init(leftPane, origLeft, options); - if (right) right.init(rightPane, origRight, options); - - var onResize = function() { - if (left) drawConnectors(left); - if (right) drawConnectors(right); - }; - CodeMirror.on(window, "resize", onResize); - var resizeInterval = setInterval(function() { - for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {} - if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); } - }, 5000); - }; - - function buildGap(dv) { - var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock"); - lock.title = "Toggle locked scrolling"; - var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap"); - CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); }); - dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); - CodeMirror.on(dv.copyButtons, "click", function(e) { - var node = e.target || e.srcElement; - if (node.chunk) copyChunk(dv, node.chunk); - }); - var gapElts = [dv.copyButtons, lockWrap]; - var svg = document.createElementNS && document.createElementNS(svgNS, "svg"); - if (svg && !svg.createSVGRect) svg = null; - dv.svg = svg; - if (svg) gapElts.push(svg); - - return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap"); - } - - MergeView.prototype = { - constuctor: MergeView, - editor: function() { return this.edit; }, - rightOriginal: function() { return this.right && this.right.orig; }, - leftOriginal: function() { return this.left && this.left.orig; }, - setShowDifferences: function(val) { - if (this.right) this.right.setShowDifferences(val); - if (this.left) this.left.setShowDifferences(val); - }, - rightChunks: function() { - return this.right && getChunks(this.right.diff); - }, - leftChunks: function() { - return this.left && getChunks(this.left.diff); - } - }; - - function asString(obj) { - if (typeof obj == "string") return obj; - else return obj.getValue(); - } - - // Operations on diffs - - var dmp = new diff_match_patch(); - function getDiff(a, b) { - var diff = dmp.diff_main(a, b); - dmp.diff_cleanupSemantic(diff); - // The library sometimes leaves in empty parts, which confuse the algorithm - for (var i = 0; i < diff.length; ++i) { - var part = diff[i]; - if (!part[1]) { - diff.splice(i--, 1); - } else if (i && diff[i - 1][0] == part[0]) { - diff.splice(i--, 1); - diff[i][1] += part[1]; - } - } - return diff; - } - - function iterateChunks(diff, f) { - var startEdit = 0, startOrig = 0; - var edit = Pos(0, 0), orig = Pos(0, 0); - for (var i = 0; i < diff.length; ++i) { - var part = diff[i], tp = part[0]; - if (tp == DIFF_EQUAL) { - var startOff = startOfLineClean(diff, i) ? 0 : 1; - var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff; - moveOver(edit, part[1], null, orig); - var endOff = endOfLineClean(diff, i) ? 1 : 0; - var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff; - if (cleanToEdit > cleanFromEdit) { - if (i) f(startOrig, cleanFromOrig, startEdit, cleanFromEdit); - startEdit = cleanToEdit; startOrig = cleanToOrig; - } - } else { - moveOver(tp == DIFF_INSERT ? edit : orig, part[1]); - } - } - if (startEdit <= edit.line || startOrig <= orig.line) - f(startOrig, orig.line + 1, startEdit, edit.line + 1); - } - - function getChunks(diff) { - var collect = []; - iterateChunks(diff, function(topOrig, botOrig, topEdit, botEdit) { - collect.push({origFrom: topOrig, origTo: botOrig, - editFrom: topEdit, editTo: botEdit}); - }); - return collect; - } - - function endOfLineClean(diff, i) { - if (i == diff.length - 1) return true; - var next = diff[i + 1][1]; - if (next.length == 1 || next.charCodeAt(0) != 10) return false; - if (i == diff.length - 2) return true; - next = diff[i + 2][1]; - return next.length > 1 && next.charCodeAt(0) == 10; - } - - function startOfLineClean(diff, i) { - if (i == 0) return true; - var last = diff[i - 1][1]; - if (last.charCodeAt(last.length - 1) != 10) return false; - if (i == 1) return true; - last = diff[i - 2][1]; - return last.charCodeAt(last.length - 1) == 10; - } - - function chunkBoundariesAround(diff, n, nInEdit) { - var beforeE, afterE, beforeO, afterO; - iterateChunks(diff, function(fromOrig, toOrig, fromEdit, toEdit) { - var fromLocal = nInEdit ? fromEdit : fromOrig; - var toLocal = nInEdit ? toEdit : toOrig; - if (afterE == null) { - if (fromLocal > n) { afterE = fromEdit; afterO = fromOrig; } - else if (toLocal > n) { afterE = toEdit; afterO = toOrig; } - } - if (toLocal <= n) { beforeE = toEdit; beforeO = toOrig; } - else if (fromLocal <= n) { beforeE = fromEdit; beforeO = fromOrig; } - }); - return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}}; - } - - // General utilities - - function elt(tag, content, className, style) { - var e = document.createElement(tag); - if (className) e.className = className; - if (style) e.style.cssText = style; - if (typeof content == "string") e.appendChild(document.createTextNode(content)); - else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); - return e; - } - - function clear(node) { - for (var count = node.childNodes.length; count > 0; --count) - node.removeChild(node.firstChild); - } - - function attrs(elt) { - for (var i = 1; i < arguments.length; i += 2) - elt.setAttribute(arguments[i], arguments[i+1]); - } - - function copyObj(obj, target) { - if (!target) target = {}; - for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; - return target; - } - - function moveOver(pos, str, copy, other) { - var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0; - for (;;) { - var nl = str.indexOf("\n", at); - if (nl == -1) break; - ++out.line; - if (other) ++other.line; - at = nl + 1; - } - out.ch = (at ? 0 : out.ch) + (str.length - at); - if (other) other.ch = (at ? 0 : other.ch) + (str.length - at); - return out; - } - - function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; } - function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; } - function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } -}); diff --git a/applications/admin/static/codemirror/addon/mode/loadmode.js b/applications/admin/static/codemirror/addon/mode/loadmode.js index e08c2813..10117ec2 100644 --- a/applications/admin/static/codemirror/addon/mode/loadmode.js +++ b/applications/admin/static/codemirror/addon/mode/loadmode.js @@ -1,11 +1,14 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); + mod(require("../../lib/codemirror"), "cjs"); else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); + define(["../../lib/codemirror"], function(CM) { mod(CM, "amd"); }); else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { + mod(CodeMirror, "plain"); +})(function(CodeMirror, env) { if (!CodeMirror.modeURL) CodeMirror.modeURL = "../mode/%N/%N.js"; var loading = {}; @@ -32,21 +35,24 @@ if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont); if (loading.hasOwnProperty(mode)) return loading[mode].push(cont); - var script = document.createElement("script"); - script.src = CodeMirror.modeURL.replace(/%N/g, mode); - var others = document.getElementsByTagName("script")[0]; - others.parentNode.insertBefore(script, others); - var list = loading[mode] = [cont]; - var count = 0, poll = setInterval(function() { - if (++count > 100) return clearInterval(poll); - if (CodeMirror.modes.hasOwnProperty(mode)) { - clearInterval(poll); - loading[mode] = null; + var file = CodeMirror.modeURL.replace(/%N/g, mode); + if (env == "plain") { + var script = document.createElement("script"); + script.src = file; + var others = document.getElementsByTagName("script")[0]; + var list = loading[mode] = [cont]; + CodeMirror.on(script, "load", function() { ensureDeps(mode, function() { for (var i = 0; i < list.length; ++i) list[i](); }); - } - }, 200); + }); + others.parentNode.insertBefore(script, others); + } else if (env == "cjs") { + require(file); + cont(); + } else if (env == "amd") { + requirejs([file], cont); + } }; CodeMirror.autoLoadMode = function(instance, mode) { diff --git a/applications/admin/static/codemirror/addon/mode/multiplex.js b/applications/admin/static/codemirror/addon/mode/multiplex.js index 07385c35..6a95b323 100644 --- a/applications/admin/static/codemirror/addon/mode/multiplex.js +++ b/applications/admin/static/codemirror/addon/mode/multiplex.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); diff --git a/applications/admin/static/codemirror/addon/mode/multiplex_test.js b/applications/admin/static/codemirror/addon/mode/multiplex_test.js index c0656357..d3394342 100644 --- a/applications/admin/static/codemirror/addon/mode/multiplex_test.js +++ b/applications/admin/static/codemirror/addon/mode/multiplex_test.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function() { CodeMirror.defineMode("markdown_with_stex", function(){ var inner = CodeMirror.getMode({}, "stex"); diff --git a/applications/admin/static/codemirror/addon/mode/overlay.js b/applications/admin/static/codemirror/addon/mode/overlay.js index 6f556a13..e1b9ed37 100644 --- a/applications/admin/static/codemirror/addon/mode/overlay.js +++ b/applications/admin/static/codemirror/addon/mode/overlay.js @@ -1,10 +1,14 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + // Utility function that allows modes to be combined. The mode given // as the base argument takes care of most of the normal mode // functionality, but a second (typically simple) mode is used, which // can override the style of text. Both modes get to parse all of the // text, but when both assign a non-null style to a piece of code, the -// overlay wins, unless the combine argument was true, in which case -// the styles are combined. +// overlay wins, unless the combine argument was true and not overridden, +// or state.overlay.combineTokens was true, in which case the styles are +// combined. (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS @@ -23,7 +27,8 @@ CodeMirror.overlayMode = function(base, overlay, combine) { base: CodeMirror.startState(base), overlay: CodeMirror.startState(overlay), basePos: 0, baseCur: null, - overlayPos: 0, overlayCur: null + overlayPos: 0, overlayCur: null, + streamSeen: null }; }, copyState: function(state) { @@ -36,6 +41,12 @@ CodeMirror.overlayMode = function(base, overlay, combine) { }, token: function(stream, state) { + if (stream != state.streamSeen || + Math.min(state.basePos, state.overlayPos) < stream.start) { + state.streamSeen = stream; + state.basePos = state.overlayPos = stream.start; + } + if (stream.start == state.basePos) { state.baseCur = base.token(stream, state.base); state.basePos = stream.pos; @@ -46,10 +57,14 @@ CodeMirror.overlayMode = function(base, overlay, combine) { state.overlayPos = stream.pos; } stream.pos = Math.min(state.basePos, state.overlayPos); - if (stream.eol()) state.basePos = state.overlayPos = 0; + // state.overlay.combineTokens always takes precedence over combine, + // unless set to null if (state.overlayCur == null) return state.baseCur; - if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur; + else if (state.baseCur != null && + state.overlay.combineTokens || + combine && state.overlay.combineTokens == null) + return state.baseCur + " " + state.overlayCur; else return state.overlayCur; }, diff --git a/applications/admin/static/codemirror/addon/mode/simple.js b/applications/admin/static/codemirror/addon/mode/simple.js new file mode 100644 index 00000000..795328b8 --- /dev/null +++ b/applications/admin/static/codemirror/addon/mode/simple.js @@ -0,0 +1,213 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineSimpleMode = function(name, states) { + CodeMirror.defineMode(name, function(config) { + return CodeMirror.simpleMode(config, states); + }); + }; + + CodeMirror.simpleMode = function(config, states) { + ensureState(states, "start"); + var states_ = {}, meta = states.meta || {}, hasIndentation = false; + for (var state in states) if (state != meta && states.hasOwnProperty(state)) { + var list = states_[state] = [], orig = states[state]; + for (var i = 0; i < orig.length; i++) { + var data = orig[i]; + list.push(new Rule(data, states)); + if (data.indent || data.dedent) hasIndentation = true; + } + } + var mode = { + startState: function() { + return {state: "start", pending: null, + local: null, localState: null, + indent: hasIndentation ? [] : null}; + }, + copyState: function(state) { + var s = {state: state.state, pending: state.pending, + local: state.local, localState: null, + indent: state.indent && state.indent.slice(0)}; + if (state.localState) + s.localState = CodeMirror.copyState(state.local.mode, state.localState); + if (state.stack) + s.stack = state.stack.slice(0); + for (var pers = state.persistentStates; pers; pers = pers.next) + s.persistentStates = {mode: pers.mode, + spec: pers.spec, + state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state), + next: s.persistentStates}; + return s; + }, + token: tokenFunction(states_, config), + innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; }, + indent: indentFunction(states_, meta) + }; + if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop)) + mode[prop] = meta[prop]; + return mode; + }; + + function ensureState(states, name) { + if (!states.hasOwnProperty(name)) + throw new Error("Undefined state " + name + "in simple mode"); + } + + function toRegex(val, caret) { + if (!val) return /(?:)/; + var flags = ""; + if (val instanceof RegExp) { + if (val.ignoreCase) flags = "i"; + val = val.source; + } else { + val = String(val); + } + return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags); + } + + function asToken(val) { + if (!val) return null; + if (typeof val == "string") return val.replace(/\./g, " "); + var result = []; + for (var i = 0; i < val.length; i++) + result.push(val[i] && val[i].replace(/\./g, " ")); + return result; + } + + function Rule(data, states) { + if (data.next || data.push) ensureState(states, data.next || data.push); + this.regex = toRegex(data.regex); + this.token = asToken(data.token); + this.data = data; + } + + function tokenFunction(states, config) { + return function(stream, state) { + if (state.pending) { + var pend = state.pending.shift(); + if (state.pending.length == 0) state.pending = null; + stream.pos += pend.text.length; + return pend.token; + } + + if (state.local) { + if (state.local.end && stream.match(state.local.end)) { + var tok = state.local.endToken || null; + state.local = state.localState = null; + return tok; + } else { + var tok = state.local.mode.token(stream, state.localState), m; + if (state.local.endScan && (m = state.local.endScan.exec(stream.current()))) + stream.pos = stream.start + m.index; + return tok; + } + } + + var curState = states[state.state]; + for (var i = 0; i < curState.length; i++) { + var rule = curState[i]; + var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex); + if (matches) { + if (rule.data.next) { + state.state = rule.data.next; + } else if (rule.data.push) { + (state.stack || (state.stack = [])).push(state.state); + state.state = rule.data.push; + } else if (rule.data.pop && state.stack && state.stack.length) { + state.state = state.stack.pop(); + } + + if (rule.data.mode) + enterLocalMode(config, state, rule.data.mode, rule.token); + if (rule.data.indent) + state.indent.push(stream.indentation() + config.indentUnit); + if (rule.data.dedent) + state.indent.pop(); + if (matches.length > 2) { + state.pending = []; + for (var j = 2; j < matches.length; j++) + if (matches[j]) + state.pending.push({text: matches[j], token: rule.token[j - 1]}); + stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0)); + return rule.token[0]; + } else if (rule.token && rule.token.join) { + return rule.token[0]; + } else { + return rule.token; + } + } + } + stream.next(); + return null; + }; + } + + function cmp(a, b) { + if (a === b) return true; + if (!a || typeof a != "object" || !b || typeof b != "object") return false; + var props = 0; + for (var prop in a) if (a.hasOwnProperty(prop)) { + if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false; + props++; + } + for (var prop in b) if (b.hasOwnProperty(prop)) props--; + return props == 0; + } + + function enterLocalMode(config, state, spec, token) { + var pers; + if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next) + if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p; + var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec); + var lState = pers ? pers.state : CodeMirror.startState(mode); + if (spec.persistent && !pers) + state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates}; + + state.localState = lState; + state.local = {mode: mode, + end: spec.end && toRegex(spec.end), + endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false), + endToken: token && token.join ? token[token.length - 1] : token}; + } + + function indexOf(val, arr) { + for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true; + } + + function indentFunction(states, meta) { + return function(state, textAfter, line) { + if (state.local && state.local.mode.indent) + return state.local.mode.indent(state.localState, textAfter, line); + if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1) + return CodeMirror.Pass; + + var pos = state.indent.length - 1, rules = states[state.state]; + scan: for (;;) { + for (var i = 0; i < rules.length; i++) { + var rule = rules[i]; + if (rule.data.dedent && rule.data.dedentIfLineStart !== false) { + var m = rule.regex.exec(textAfter); + if (m && m[0]) { + pos--; + if (rule.next || rule.push) rules = states[rule.next || rule.push]; + textAfter = textAfter.slice(m[0].length); + continue scan; + } + } + } + break; + } + return pos < 0 ? 0 : state.indent[pos]; + }; + } +}); diff --git a/applications/admin/static/codemirror/addon/runmode/colorize.js b/applications/admin/static/codemirror/addon/runmode/colorize.js deleted file mode 100644 index 0f9530b1..00000000 --- a/applications/admin/static/codemirror/addon/runmode/colorize.js +++ /dev/null @@ -1,37 +0,0 @@ -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror"), require("./runmode")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror", "./runmode"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - - var isBlock = /^(p|li|div|h\\d|pre|blockquote|td)$/; - - function textContent(node, out) { - if (node.nodeType == 3) return out.push(node.nodeValue); - for (var ch = node.firstChild; ch; ch = ch.nextSibling) { - textContent(ch, out); - if (isBlock.test(node.nodeType)) out.push("\n"); - } - } - - CodeMirror.colorize = function(collection, defaultMode) { - if (!collection) collection = document.body.getElementsByTagName("pre"); - - for (var i = 0; i < collection.length; ++i) { - var node = collection[i]; - var mode = node.getAttribute("data-lang") || defaultMode; - if (!mode) continue; - - var text = []; - textContent(node, text); - node.innerHTML = ""; - CodeMirror.runMode(text.join(""), mode, node); - - node.className += " cm-s-default"; - } - }; -}); diff --git a/applications/admin/static/codemirror/addon/runmode/runmode-standalone.js b/applications/admin/static/codemirror/addon/runmode/runmode-standalone.js deleted file mode 100644 index eaa2b8f2..00000000 --- a/applications/admin/static/codemirror/addon/runmode/runmode-standalone.js +++ /dev/null @@ -1,149 +0,0 @@ -window.CodeMirror = {}; - -(function() { -"use strict"; - -function splitLines(string){ return string.split(/\r?\n|\r/); }; - -function StringStream(string) { - this.pos = this.start = 0; - this.string = string; - this.lineStart = 0; -} -StringStream.prototype = { - eol: function() {return this.pos >= this.string.length;}, - sol: function() {return this.pos == 0;}, - peek: function() {return this.string.charAt(this.pos) || null;}, - next: function() { - if (this.pos < this.string.length) - return this.string.charAt(this.pos++); - }, - eat: function(match) { - var ch = this.string.charAt(this.pos); - if (typeof match == "string") var ok = ch == match; - else var ok = ch && (match.test ? match.test(ch) : match(ch)); - if (ok) {++this.pos; return ch;} - }, - eatWhile: function(match) { - var start = this.pos; - while (this.eat(match)){} - return this.pos > start; - }, - eatSpace: function() { - var start = this.pos; - while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; - return this.pos > start; - }, - skipToEnd: function() {this.pos = this.string.length;}, - skipTo: function(ch) { - var found = this.string.indexOf(ch, this.pos); - if (found > -1) {this.pos = found; return true;} - }, - backUp: function(n) {this.pos -= n;}, - column: function() {return this.start - this.lineStart;}, - indentation: function() {return 0;}, - match: function(pattern, consume, caseInsensitive) { - if (typeof pattern == "string") { - var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - var substr = this.string.substr(this.pos, pattern.length); - if (cased(substr) == cased(pattern)) { - if (consume !== false) this.pos += pattern.length; - return true; - } - } else { - var match = this.string.slice(this.pos).match(pattern); - if (match && match.index > 0) return null; - if (match && consume !== false) this.pos += match[0].length; - return match; - } - }, - current: function(){return this.string.slice(this.start, this.pos);}, - hideFirstChars: function(n, inner) { - this.lineStart += n; - try { return inner(); } - finally { this.lineStart -= n; } - } -}; -CodeMirror.StringStream = StringStream; - -CodeMirror.startState = function (mode, a1, a2) { - return mode.startState ? mode.startState(a1, a2) : true; -}; - -var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; -CodeMirror.defineMode = function (name, mode) { modes[name] = mode; }; -CodeMirror.defineMIME = function (mime, spec) { mimeModes[mime] = spec; }; -CodeMirror.resolveMode = function(spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { - spec = mimeModes[spec]; - } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { - spec = mimeModes[spec.name]; - } - if (typeof spec == "string") return {name: spec}; - else return spec || {name: "null"}; -}; -CodeMirror.getMode = function (options, spec) { - spec = CodeMirror.resolveMode(spec); - var mfactory = modes[spec.name]; - if (!mfactory) throw new Error("Unknown mode: " + spec); - return mfactory(options, spec); -}; -CodeMirror.registerHelper = CodeMirror.registerGlobalHelper = Math.min; -CodeMirror.defineMode("null", function() { - return {token: function(stream) {stream.skipToEnd();}}; -}); -CodeMirror.defineMIME("text/plain", "null"); - -CodeMirror.runMode = function (string, modespec, callback, options) { - var mode = CodeMirror.getMode({ indentUnit: 2 }, modespec); - - if (callback.nodeType == 1) { - var tabSize = (options && options.tabSize) || 4; - var node = callback, col = 0; - node.innerHTML = ""; - callback = function (text, style) { - if (text == "\n") { - node.appendChild(document.createElement("br")); - col = 0; - return; - } - var content = ""; - // replace tabs - for (var pos = 0; ;) { - var idx = text.indexOf("\t", pos); - if (idx == -1) { - content += text.slice(pos); - col += text.length - pos; - break; - } else { - col += idx - pos; - content += text.slice(pos, idx); - var size = tabSize - col % tabSize; - col += size; - for (var i = 0; i < size; ++i) content += " "; - pos = idx + 1; - } - } - - if (style) { - var sp = node.appendChild(document.createElement("span")); - sp.className = "cm-" + style.replace(/ +/g, " cm-"); - sp.appendChild(document.createTextNode(content)); - } else { - node.appendChild(document.createTextNode(content)); - } - }; - } - - var lines = splitLines(string), state = (options && options.state) || CodeMirror.startState(mode); - for (var i = 0, e = lines.length; i < e; ++i) { - if (i) callback("\n"); - var stream = new CodeMirror.StringStream(lines[i]); - while (!stream.eol()) { - var style = mode.token(stream, state); - callback(stream.current(), style, i, stream.start, state); - stream.start = stream.pos; - } - } -}; -})(); diff --git a/applications/admin/static/codemirror/addon/runmode/runmode.js b/applications/admin/static/codemirror/addon/runmode/runmode.js deleted file mode 100644 index 351840e0..00000000 --- a/applications/admin/static/codemirror/addon/runmode/runmode.js +++ /dev/null @@ -1,68 +0,0 @@ -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { -"use strict"; - -CodeMirror.runMode = function(string, modespec, callback, options) { - var mode = CodeMirror.getMode(CodeMirror.defaults, modespec); - var ie = /MSIE \d/.test(navigator.userAgent); - var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); - - if (callback.nodeType == 1) { - var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize; - var node = callback, col = 0; - node.innerHTML = ""; - callback = function(text, style) { - if (text == "\n") { - // Emitting LF or CRLF on IE8 or earlier results in an incorrect display. - // Emitting a carriage return makes everything ok. - node.appendChild(document.createTextNode(ie_lt9 ? '\r' : text)); - col = 0; - return; - } - var content = ""; - // replace tabs - for (var pos = 0;;) { - var idx = text.indexOf("\t", pos); - if (idx == -1) { - content += text.slice(pos); - col += text.length - pos; - break; - } else { - col += idx - pos; - content += text.slice(pos, idx); - var size = tabSize - col % tabSize; - col += size; - for (var i = 0; i < size; ++i) content += " "; - pos = idx + 1; - } - } - - if (style) { - var sp = node.appendChild(document.createElement("span")); - sp.className = "cm-" + style.replace(/ +/g, " cm-"); - sp.appendChild(document.createTextNode(content)); - } else { - node.appendChild(document.createTextNode(content)); - } - }; - } - - var lines = CodeMirror.splitLines(string), state = (options && options.state) || CodeMirror.startState(mode); - for (var i = 0, e = lines.length; i < e; ++i) { - if (i) callback("\n"); - var stream = new CodeMirror.StringStream(lines[i]); - while (!stream.eol()) { - var style = mode.token(stream, state); - callback(stream.current(), style, i, stream.start, state); - stream.start = stream.pos; - } - } -}; - -}); diff --git a/applications/admin/static/codemirror/addon/runmode/runmode.node.js b/applications/admin/static/codemirror/addon/runmode/runmode.node.js deleted file mode 100644 index 74c39be7..00000000 --- a/applications/admin/static/codemirror/addon/runmode/runmode.node.js +++ /dev/null @@ -1,118 +0,0 @@ -/* Just enough of CodeMirror to run runMode under node.js */ - -// declare global: StringStream - -function splitLines(string){ return string.split(/\r?\n|\r/); }; - -function StringStream(string) { - this.pos = this.start = 0; - this.string = string; - this.lineStart = 0; -} -StringStream.prototype = { - eol: function() {return this.pos >= this.string.length;}, - sol: function() {return this.pos == 0;}, - peek: function() {return this.string.charAt(this.pos) || null;}, - next: function() { - if (this.pos < this.string.length) - return this.string.charAt(this.pos++); - }, - eat: function(match) { - var ch = this.string.charAt(this.pos); - if (typeof match == "string") var ok = ch == match; - else var ok = ch && (match.test ? match.test(ch) : match(ch)); - if (ok) {++this.pos; return ch;} - }, - eatWhile: function(match) { - var start = this.pos; - while (this.eat(match)){} - return this.pos > start; - }, - eatSpace: function() { - var start = this.pos; - while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos; - return this.pos > start; - }, - skipToEnd: function() {this.pos = this.string.length;}, - skipTo: function(ch) { - var found = this.string.indexOf(ch, this.pos); - if (found > -1) {this.pos = found; return true;} - }, - backUp: function(n) {this.pos -= n;}, - column: function() {return this.start - this.lineStart;}, - indentation: function() {return 0;}, - match: function(pattern, consume, caseInsensitive) { - if (typeof pattern == "string") { - var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - var substr = this.string.substr(this.pos, pattern.length); - if (cased(substr) == cased(pattern)) { - if (consume !== false) this.pos += pattern.length; - return true; - } - } else { - var match = this.string.slice(this.pos).match(pattern); - if (match && match.index > 0) return null; - if (match && consume !== false) this.pos += match[0].length; - return match; - } - }, - current: function(){return this.string.slice(this.start, this.pos);}, - hideFirstChars: function(n, inner) { - this.lineStart += n; - try { return inner(); } - finally { this.lineStart -= n; } - } -}; -exports.StringStream = StringStream; - -exports.startState = function(mode, a1, a2) { - return mode.startState ? mode.startState(a1, a2) : true; -}; - -var modes = exports.modes = {}, mimeModes = exports.mimeModes = {}; -exports.defineMode = function(name, mode) { - if (arguments.length > 2) { - mode.dependencies = []; - for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); - } - modes[name] = mode; -}; -exports.defineMIME = function(mime, spec) { mimeModes[mime] = spec; }; - -exports.defineMode("null", function() { - return {token: function(stream) {stream.skipToEnd();}}; -}); -exports.defineMIME("text/plain", "null"); - -exports.resolveMode = function(spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { - spec = mimeModes[spec]; - } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { - spec = mimeModes[spec.name]; - } - if (typeof spec == "string") return {name: spec}; - else return spec || {name: "null"}; -}; -exports.getMode = function(options, spec) { - spec = exports.resolveMode(spec); - var mfactory = modes[spec.name]; - if (!mfactory) throw new Error("Unknown mode: " + spec); - return mfactory(options, spec); -}; -exports.registerHelper = exports.registerGlobalHelper = Math.min; - -exports.runMode = function(string, modespec, callback, options) { - var mode = exports.getMode({indentUnit: 2}, modespec); - var lines = splitLines(string), state = (options && options.state) || exports.startState(mode); - for (var i = 0, e = lines.length; i < e; ++i) { - if (i) callback("\n"); - var stream = new exports.StringStream(lines[i]); - while (!stream.eol()) { - var style = mode.token(stream, state); - callback(stream.current(), style, i, stream.start, state); - stream.start = stream.pos; - } - } -}; - -require.cache[require.resolve("../../lib/codemirror")] = require.cache[require.resolve("./runmode.node")]; diff --git a/applications/admin/static/codemirror/addon/search/match-highlighter.js b/applications/admin/static/codemirror/addon/search/match-highlighter.js index d9c818b8..e9a22721 100644 --- a/applications/admin/static/codemirror/addon/search/match-highlighter.js +++ b/applications/admin/static/codemirror/addon/search/match-highlighter.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + // Highlighting text that matches the selection // // Defines an option highlightSelectionMatches, which, when enabled, @@ -5,12 +8,15 @@ // document. // // The option can be set to true to simply enable it, or to a -// {minChars, style, showToken} object to explicitly configure it. -// minChars is the minimum amount of characters that should be +// {minChars, style, wordsOnly, showToken, delay} object to explicitly +// configure it. minChars is the minimum amount of characters that should be // selected for the behavior to occur, and style is the token style to // apply to the matches. This will be prefixed by "cm-" to create an -// actual CSS class name. showToken, when enabled, will cause the -// current token to be highlighted when nothing is selected. +// actual CSS class name. If wordsOnly is enabled, the matches will be +// highlighted only if the selected text is a word. showToken, when enabled, +// will cause the current token to be highlighted when nothing is selected. +// delay is used to specify how much time to wait, in milliseconds, before +// highlighting the matches. (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS @@ -25,6 +31,7 @@ var DEFAULT_MIN_CHARS = 2; var DEFAULT_TOKEN_STYLE = "matchhighlight"; var DEFAULT_DELAY = 100; + var DEFAULT_WORDS_ONLY = false; function State(options) { if (typeof options == "object") { @@ -32,10 +39,12 @@ this.style = options.style; this.showToken = options.showToken; this.delay = options.delay; + this.wordsOnly = options.wordsOnly; } if (this.style == null) this.style = DEFAULT_TOKEN_STYLE; if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS; if (this.delay == null) this.delay = DEFAULT_DELAY; + if (this.wordsOnly == null) this.wordsOnly = DEFAULT_WORDS_ONLY; this.overlay = this.timeout = null; } @@ -76,13 +85,32 @@ cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style)); return; } - if (cm.getCursor("head").line != cm.getCursor("anchor").line) return; - var selection = cm.getSelections()[0].replace(/^\s+|\s+$/g, ""); + var from = cm.getCursor("from"), to = cm.getCursor("to"); + if (from.line != to.line) return; + if (state.wordsOnly && !isWord(cm, from, to)) return; + var selection = cm.getRange(from, to).replace(/^\s+|\s+$/g, ""); if (selection.length >= state.minChars) cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style)); }); } + function isWord(cm, from, to) { + var str = cm.getRange(from, to); + if (str.match(/^\w+$/) !== null) { + if (from.ch > 0) { + var pos = {line: from.line, ch: from.ch - 1}; + var chr = cm.getRange(pos, from); + if (chr.match(/\W/) === null) return false; + } + if (to.ch < cm.getLine(from.line).length) { + var pos = {line: to.line, ch: to.ch + 1}; + var chr = cm.getRange(to, pos); + if (chr.match(/\W/) === null) return false; + } + return true; + } else return false; + } + function boundariesAround(stream, re) { return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) && (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos))); diff --git a/applications/admin/static/codemirror/addon/search/matchesonscrollbar.css b/applications/admin/static/codemirror/addon/search/matchesonscrollbar.css new file mode 100644 index 00000000..77932cc9 --- /dev/null +++ b/applications/admin/static/codemirror/addon/search/matchesonscrollbar.css @@ -0,0 +1,8 @@ +.CodeMirror-search-match { + background: gold; + border-top: 1px solid orange; + border-bottom: 1px solid orange; + -moz-box-sizing: border-box; + box-sizing: border-box; + opacity: .5; +} diff --git a/applications/admin/static/codemirror/addon/search/matchesonscrollbar.js b/applications/admin/static/codemirror/addon/search/matchesonscrollbar.js new file mode 100644 index 00000000..dbd67a4a --- /dev/null +++ b/applications/admin/static/codemirror/addon/search/matchesonscrollbar.js @@ -0,0 +1,95 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("./searchcursor"), require("../scroll/annotatescrollbar")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "./searchcursor", "../scroll/annotatescrollbar"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) { + if (typeof options == "string") options = {className: options}; + if (!options) options = {}; + return new SearchAnnotation(this, query, caseFold, options); + }); + + function SearchAnnotation(cm, query, caseFold, options) { + this.cm = cm; + var annotateOptions = {listenForChanges: false}; + for (var prop in options) annotateOptions[prop] = options[prop]; + if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match"; + this.annotation = cm.annotateScrollbar(annotateOptions); + this.query = query; + this.caseFold = caseFold; + this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1}; + this.matches = []; + this.update = null; + + this.findMatches(); + this.annotation.update(this.matches); + + var self = this; + cm.on("change", this.changeHandler = function(_cm, change) { self.onChange(change); }); + } + + var MAX_MATCHES = 1000; + + SearchAnnotation.prototype.findMatches = function() { + if (!this.gap) return; + for (var i = 0; i < this.matches.length; i++) { + var match = this.matches[i]; + if (match.from.line >= this.gap.to) break; + if (match.to.line >= this.gap.from) this.matches.splice(i--, 1); + } + var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), this.caseFold); + while (cursor.findNext()) { + var match = {from: cursor.from(), to: cursor.to()}; + if (match.from.line >= this.gap.to) break; + this.matches.splice(i++, 0, match); + if (this.matches.length > MAX_MATCHES) break; + } + this.gap = null; + }; + + function offsetLine(line, changeStart, sizeChange) { + if (line <= changeStart) return line; + return Math.max(changeStart, line + sizeChange); + } + + SearchAnnotation.prototype.onChange = function(change) { + var startLine = change.from.line; + var endLine = CodeMirror.changeEnd(change).line; + var sizeChange = endLine - change.to.line; + if (this.gap) { + this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line); + this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line); + } else { + this.gap = {from: change.from.line, to: endLine + 1}; + } + + if (sizeChange) for (var i = 0; i < this.matches.length; i++) { + var match = this.matches[i]; + var newFrom = offsetLine(match.from.line, startLine, sizeChange); + if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch); + var newTo = offsetLine(match.to.line, startLine, sizeChange); + if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch); + } + clearTimeout(this.update); + var self = this; + this.update = setTimeout(function() { self.updateAfterChange(); }, 250); + }; + + SearchAnnotation.prototype.updateAfterChange = function() { + this.findMatches(); + this.annotation.update(this.matches); + }; + + SearchAnnotation.prototype.clear = function() { + this.cm.off("change", this.changeHandler); + this.annotation.clear(); + }; +}); diff --git a/applications/admin/static/codemirror/addon/search/search.js b/applications/admin/static/codemirror/addon/search/search.js index 19f51f1d..0251067a 100644 --- a/applications/admin/static/codemirror/addon/search/search.js +++ b/applications/admin/static/codemirror/addon/search/search.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + // Define search commands. Depends on dialog.js or another // implementation of the openDialog method. @@ -16,21 +19,21 @@ })(function(CodeMirror) { "use strict"; function searchOverlay(query, caseInsensitive) { - var startChar; - if (typeof query == "string") { - startChar = query.charAt(0); - query = new RegExp("^" + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), - caseInsensitive ? "i" : ""); - } else { - query = new RegExp("^(?:" + query.source + ")", query.ignoreCase ? "i" : ""); - } + if (typeof query == "string") + query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g"); + else if (!query.global) + query = new RegExp(query.source, query.ignoreCase ? "gi" : "g"); + return {token: function(stream) { - if (stream.match(query)) return "searching"; - while (!stream.eol()) { - stream.next(); - if (startChar && !caseInsensitive) - stream.skipTo(startChar) || stream.skipToEnd(); - if (stream.match(query, false)) break; + query.lastIndex = stream.pos; + var match = query.exec(stream.string); + if (match && match.index == stream.pos) { + stream.pos += match[0].length; + return "searching"; + } else if (match) { + stream.pos = match.index; + } else { + stream.skipToEnd(); } }}; } @@ -60,15 +63,15 @@ function parseQuery(query) { var isRE = query.match(/^\/(.*)\/([a-z]*)$/); if (isRE) { - query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); - if (query.test("")) query = /x^/; - } else if (query == "") { - query = /x^/; + try { query = new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i"); } + catch(e) {} // Not a regular expression after all, do a string search } + if (typeof query == "string" ? query == "" : query.test("")) + query = /x^/; return query; } var queryDialog = - 'Search: (Use /re/ syntax for regexp search)'; + 'Search: (Use /re/ syntax for regexp search)'; function doSearch(cm, rev) { var state = getSearchState(cm); if (state.query) return findNext(cm, rev); @@ -79,6 +82,10 @@ cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); cm.addOverlay(state.overlay); + if (cm.showMatchesOnScrollbar) { + if (state.annotate) { state.annotate.clear(); state.annotate = null; } + state.annotate = cm.showMatchesOnScrollbar(state.query, queryCaseInsensitive(state.query)); + } state.posFrom = state.posTo = cm.getCursor(); findNext(cm, rev); }); @@ -100,13 +107,15 @@ if (!state.query) return; state.query = null; cm.removeOverlay(state.overlay); + if (state.annotate) { state.annotate.clear(); state.annotate = null; } });} var replaceQueryDialog = - 'Replace: (Use /re/ syntax for regexp search)'; - var replacementQueryDialog = 'With: '; + 'Replace: (Use /re/ syntax for regexp search)'; + var replacementQueryDialog = 'With: '; var doReplaceConfirm = "Replace? "; function replace(cm, all) { + if (cm.getOption("readOnly")) return; dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) { if (!query) return; query = parseQuery(query); diff --git a/applications/admin/static/codemirror/addon/search/searchcursor.js b/applications/admin/static/codemirror/addon/search/searchcursor.js index 899f44c4..55c108b5 100644 --- a/applications/admin/static/codemirror/addon/search/searchcursor.js +++ b/applications/admin/static/codemirror/addon/search/searchcursor.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -104,7 +107,7 @@ var from = Pos(pos.line, cut); for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln) if (target[i] != fold(doc.getLine(ln))) return; - if (doc.getLine(ln).slice(0, origTarget[last].length) != target[last]) return; + if (fold(doc.getLine(ln).slice(0, origTarget[last].length)) != target[last]) return; return {from: from, to: Pos(ln, origTarget[last].length)}; } }; diff --git a/applications/admin/static/codemirror/addon/selection/active-line.js b/applications/admin/static/codemirror/addon/selection/active-line.js index a818f109..22da2e0a 100644 --- a/applications/admin/static/codemirror/addon/selection/active-line.js +++ b/applications/admin/static/codemirror/addon/selection/active-line.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + // Because sometimes you need to style the cursor's line. // // Adds an option 'styleActiveLine' which, when enabled, gives the @@ -46,7 +49,9 @@ function updateActiveLines(cm, ranges) { var active = []; for (var i = 0; i < ranges.length; i++) { - var line = cm.getLineHandleVisualStart(ranges[i].head.line); + var range = ranges[i]; + if (!range.empty()) continue; + var line = cm.getLineHandleVisualStart(range.head.line); if (active[active.length - 1] != line) active.push(line); } if (sameArray(cm.state.activeLines, active)) return; diff --git a/applications/admin/static/codemirror/addon/selection/mark-selection.js b/applications/admin/static/codemirror/addon/selection/mark-selection.js index ae0d3931..5c42d21e 100644 --- a/applications/admin/static/codemirror/addon/selection/mark-selection.js +++ b/applications/admin/static/codemirror/addon/selection/mark-selection.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + // Because sometimes you need to mark the selected *text*. // // Adds an option 'styleSelectedText' which, when enabled, gives diff --git a/applications/admin/static/codemirror/addon/selection/selection-pointer.js b/applications/admin/static/codemirror/addon/selection/selection-pointer.js new file mode 100644 index 00000000..ef5e404a --- /dev/null +++ b/applications/admin/static/codemirror/addon/selection/selection-pointer.js @@ -0,0 +1,98 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineOption("selectionPointer", false, function(cm, val) { + var data = cm.state.selectionPointer; + if (data) { + CodeMirror.off(cm.getWrapperElement(), "mousemove", data.mousemove); + CodeMirror.off(cm.getWrapperElement(), "mouseout", data.mouseout); + CodeMirror.off(window, "scroll", data.windowScroll); + cm.off("cursorActivity", reset); + cm.off("scroll", reset); + cm.state.selectionPointer = null; + cm.display.lineDiv.style.cursor = ""; + } + if (val) { + data = cm.state.selectionPointer = { + value: typeof val == "string" ? val : "default", + mousemove: function(event) { mousemove(cm, event); }, + mouseout: function(event) { mouseout(cm, event); }, + windowScroll: function() { reset(cm); }, + rects: null, + mouseX: null, mouseY: null, + willUpdate: false + }; + CodeMirror.on(cm.getWrapperElement(), "mousemove", data.mousemove); + CodeMirror.on(cm.getWrapperElement(), "mouseout", data.mouseout); + CodeMirror.on(window, "scroll", data.windowScroll); + cm.on("cursorActivity", reset); + cm.on("scroll", reset); + } + }); + + function mousemove(cm, event) { + var data = cm.state.selectionPointer; + if (event.buttons == null ? event.which : event.buttons) { + data.mouseX = data.mouseY = null; + } else { + data.mouseX = event.clientX; + data.mouseY = event.clientY; + } + scheduleUpdate(cm); + } + + function mouseout(cm, event) { + if (!cm.getWrapperElement().contains(event.relatedTarget)) { + var data = cm.state.selectionPointer; + data.mouseX = data.mouseY = null; + scheduleUpdate(cm); + } + } + + function reset(cm) { + cm.state.selectionPointer.rects = null; + scheduleUpdate(cm); + } + + function scheduleUpdate(cm) { + if (!cm.state.selectionPointer.willUpdate) { + cm.state.selectionPointer.willUpdate = true; + setTimeout(function() { + update(cm); + cm.state.selectionPointer.willUpdate = false; + }, 50); + } + } + + function update(cm) { + var data = cm.state.selectionPointer; + if (!data) return; + if (data.rects == null && data.mouseX != null) { + data.rects = []; + if (cm.somethingSelected()) { + for (var sel = cm.display.selectionDiv.firstChild; sel; sel = sel.nextSibling) + data.rects.push(sel.getBoundingClientRect()); + } + } + var inside = false; + if (data.mouseX != null) for (var i = 0; i < data.rects.length; i++) { + var rect = data.rects[i]; + if (rect.left <= data.mouseX && rect.right >= data.mouseX && + rect.top <= data.mouseY && rect.bottom >= data.mouseY) + inside = true; + } + var cursor = inside ? data.value : ""; + if (cm.display.lineDiv.style.cursor != cursor) + cm.display.lineDiv.style.cursor = cursor; + } +}); diff --git a/applications/admin/static/codemirror/bin/compress b/applications/admin/static/codemirror/bin/compress deleted file mode 100755 index 809fbe83..00000000 --- a/applications/admin/static/codemirror/bin/compress +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env node - -// Compression helper for CodeMirror -// -// Example: -// -// bin/compress codemirror runmode javascript xml -// -// Will take lib/codemirror.js, addon/runmode/runmode.js, -// mode/javascript/javascript.js, and mode/xml/xml.js, run them though -// the online minifier at http://marijnhaverbeke.nl/uglifyjs, and spit -// out the result. -// -// bin/compress codemirror --local /path/to/bin/UglifyJS -// -// Will use a local minifier instead of the online default one. -// -// Script files are specified without .js ending. Prefixing them with -// their full (local) path is optional. So you may say lib/codemirror -// or mode/xml/xml to be more precise. In fact, even the .js suffix -// may be speficied, if wanted. - -"use strict"; - -var fs = require("fs"); - -function help(ok) { - console.log("usage: " + process.argv[1] + " [--local /path/to/uglifyjs] files..."); - process.exit(ok ? 0 : 1); -} - -var local = null, args = [], extraArgs = null, files = [], blob = ""; - -for (var i = 2; i < process.argv.length; ++i) { - var arg = process.argv[i]; - if (arg == "--local" && i + 1 < process.argv.length) { - var parts = process.argv[++i].split(/\s+/); - local = parts[0]; - extraArgs = parts.slice(1); - if (!extraArgs.length) extraArgs = ["-c", "-m"]; - } else if (arg == "--help") { - help(true); - } else if (arg[0] != "-") { - files.push({name: arg, re: new RegExp("(?:\\/|^)" + arg + (/\.js$/.test(arg) ? "$" : "\\.js$"))}); - } else help(false); -} - -function walk(dir) { - fs.readdirSync(dir).forEach(function(fname) { - if (/^[_\.]/.test(fname)) return; - var file = dir + fname; - if (fs.statSync(file).isDirectory()) return walk(file + "/"); - if (files.some(function(spec, i) { - var match = spec.re.test(file); - if (match) files.splice(i, 1); - return match; - })) { - if (local) args.push(file); - else blob += fs.readFileSync(file, "utf8"); - } - }); -} - -walk("lib/"); -walk("addon/"); -walk("mode/"); - -if (!local && !blob) help(false); - -if (files.length) { - console.log("Some speficied files were not found: " + - files.map(function(a){return a.name;}).join(", ")); - process.exit(1); -} - -if (local) { - require("child_process").spawn(local, args.concat(extraArgs), {stdio: ["ignore", process.stdout, process.stderr]}); -} else { - var data = new Buffer("js_code=" + require("querystring").escape(blob), "utf8"); - var req = require("http").request({ - host: "marijnhaverbeke.nl", - port: 80, - method: "POST", - path: "/uglifyjs", - headers: {"content-type": "application/x-www-form-urlencoded", - "content-length": data.length} - }); - req.on("response", function(resp) { - resp.on("data", function (chunk) { process.stdout.write(chunk); }); - }); - req.end(data); -} diff --git a/applications/admin/static/codemirror/bin/lint b/applications/admin/static/codemirror/bin/lint deleted file mode 100755 index 4f70994c..00000000 --- a/applications/admin/static/codemirror/bin/lint +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env node - -var lint = require("../test/lint/lint"), - path = require("path"); - -if (process.argv.length > 2) { - lint.checkDir(process.argv[2]); -} else { - process.chdir(path.resolve(__dirname, "..")); - lint.checkDir("lib"); - lint.checkDir("mode"); - lint.checkDir("addon"); - lint.checkDir("keymap"); -} - -process.exit(lint.success() ? 0 : 1); diff --git a/applications/admin/static/codemirror/bin/source-highlight b/applications/admin/static/codemirror/bin/source-highlight deleted file mode 100755 index 6d15f1ae..00000000 --- a/applications/admin/static/codemirror/bin/source-highlight +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env node - -// Simple command-line code highlighting tool. Reads code from stdin, -// spits html to stdout. For example: -// -// echo 'function foo(a) { return a; }' | bin/source-highlight -s javascript -// bin/source-highlight -s - -var fs = require("fs"); - -var CodeMirror = require("../addon/runmode/runmode.node.js"); -require("../mode/meta.js"); - -var sPos = process.argv.indexOf("-s"); -if (sPos == -1 || sPos == process.argv.length - 1) { - console.error("Usage: source-highlight -s language"); - process.exit(1); -} -var lang = process.argv[sPos + 1].toLowerCase(), modeName = lang; -CodeMirror.modeInfo.forEach(function(info) { - if (info.mime == lang) { - modeName = info.mode; - } else if (info.name.toLowerCase() == lang) { - modeName = info.mode; - lang = info.mime; - } -}); - -if (!CodeMirror.modes[modeName]) - require("../mode/" + modeName + "/" + modeName + ".js"); - -function esc(str) { - return str.replace(/[<&]/g, function(ch) { return ch == "&" ? "&" : "<"; }); -} - -var code = fs.readFileSync("/dev/stdin", "utf8"); -var curStyle = null, accum = ""; -function flush() { - if (curStyle) process.stdout.write("" + esc(accum) + ""); - else process.stdout.write(esc(accum)); -} - -CodeMirror.runMode(code, lang, function(text, style) { - if (style != curStyle) { - flush(); - curStyle = style; accum = text; - } else { - accum += text; - } -}); -flush(); diff --git a/applications/admin/static/codemirror/keymap/emacs.js b/applications/admin/static/codemirror/keymap/emacs.js index 8d3ab62d..c4135237 100644 --- a/applications/admin/static/codemirror/keymap/emacs.js +++ b/applications/admin/static/codemirror/keymap/emacs.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../lib/codemirror")); @@ -129,8 +132,8 @@ }; } - function findEnd(cm, by, dir) { - var pos = cm.getCursor(), prefix = getPrefix(cm); + function findEnd(cm, pos, by, dir) { + var prefix = getPrefix(cm); if (prefix < 0) { dir = -dir; prefix = -prefix; } for (var i = 0; i < prefix; ++i) { var newPos = by(cm, pos, dir); @@ -142,14 +145,31 @@ function move(by, dir) { var f = function(cm) { - cm.extendSelection(findEnd(cm, by, dir)); + cm.extendSelection(findEnd(cm, cm.getCursor(), by, dir)); }; f.motion = true; return f; } function killTo(cm, by, dir) { - kill(cm, cm.getCursor(), findEnd(cm, by, dir), true); + var selections = cm.listSelections(), cursor; + var i = selections.length; + while (i--) { + cursor = selections[i].head; + kill(cm, cursor, findEnd(cm, cursor, by, dir), true); + } + } + + function killRegion(cm) { + if (cm.somethingSelected()) { + var selections = cm.listSelections(), selection; + var i = selections.length; + while (i--) { + selection = selections[i]; + kill(cm, selection.anchor, selection.head); + } + return true; + } } function addPrefix(cm, digit) { @@ -253,7 +273,7 @@ // Actual keymap - var keyMap = CodeMirror.keyMap.emacs = { + var keyMap = CodeMirror.keyMap.emacs = CodeMirror.normalizeKeyMap({ "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));}, "Ctrl-K": repeated(function(cm) { var start = cm.getCursor(), end = cm.clipPos(Pos(start.line)); @@ -280,9 +300,9 @@ "Ctrl-F": move(byChar, 1), "Ctrl-B": move(byChar, -1), "Right": move(byChar, 1), "Left": move(byChar, -1), "Ctrl-D": function(cm) { killTo(cm, byChar, 1); }, - "Delete": function(cm) { killTo(cm, byChar, 1); }, + "Delete": function(cm) { killRegion(cm) || killTo(cm, byChar, 1); }, "Ctrl-H": function(cm) { killTo(cm, byChar, -1); }, - "Backspace": function(cm) { killTo(cm, byChar, -1); }, + "Backspace": function(cm) { killRegion(cm) || killTo(cm, byChar, -1); }, "Alt-F": move(byWord, 1), "Alt-B": move(byWord, -1), "Alt-D": function(cm) { killTo(cm, byWord, 1); }, @@ -306,7 +326,8 @@ "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1), "Shift-Ctrl-Alt-2": function(cm) { - cm.setSelection(findEnd(cm, byExpr, 1), cm.getCursor()); + var cursor = cm.getCursor(); + cm.setSelection(findEnd(cm, cursor, byExpr, 1), cursor); }, "Ctrl-Alt-T": function(cm) { var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1); @@ -324,13 +345,7 @@ }, "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }), "Ctrl-T": repeated(function(cm) { - var pos = cm.getCursor(); - if (pos.ch < cm.getLine(pos.line).length) pos = Pos(pos.line, pos.ch + 1); - var from = cm.findPosH(pos, -2, "char"); - var range = cm.getRange(from, pos); - if (range.length != 2) return; - cm.setSelection(from, pos); - cm.replaceSelection(range.charAt(1) + range.charAt(0), null, "+transpose"); + cm.execCommand("transposeChars"); }), "Alt-C": repeated(function(cm) { @@ -356,27 +371,7 @@ "Alt-/": "autocomplete", "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto", - "Alt-G": function(cm) {cm.setOption("keyMap", "emacs-Alt-G");}, - "Ctrl-X": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-X");}, - "Ctrl-Q": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-Q");}, - "Ctrl-U": addPrefixMap - }; - - CodeMirror.keyMap["emacs-Ctrl-X"] = { - "Tab": function(cm) { - cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit")); - }, - "Ctrl-X": function(cm) { - cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor")); - }, - - "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": repeated("undo"), "K": "close", - "Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); }, - auto: "emacs", nofallthrough: true, disableInput: true - }; - - CodeMirror.keyMap["emacs-Alt-G"] = { - "G": function(cm) { + "Alt-G G": function(cm) { var prefix = getPrefix(cm, true); if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1); @@ -386,13 +381,25 @@ cm.setCursor(num - 1); }); }, - auto: "emacs", nofallthrough: true, disableInput: true - }; - CodeMirror.keyMap["emacs-Ctrl-Q"] = { - "Tab": repeated("insertTab"), - auto: "emacs", nofallthrough: true - }; + "Ctrl-X Tab": function(cm) { + cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit")); + }, + "Ctrl-X Ctrl-X": function(cm) { + cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor")); + }, + "Ctrl-X Ctrl-S": "save", + "Ctrl-X Ctrl-W": "save", + "Ctrl-X S": "saveAll", + "Ctrl-X F": "open", + "Ctrl-X U": repeated("undo"), + "Ctrl-X K": "close", + "Ctrl-X Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); }, + "Ctrl-X H": "selectAll", + + "Ctrl-Q Tab": repeated("insertTab"), + "Ctrl-U": addPrefixMap + }); var prefixMap = {"Ctrl-G": clearPrefix}; function regPrefix(d) { diff --git a/applications/admin/static/codemirror/keymap/sublime.js b/applications/admin/static/codemirror/keymap/sublime.js new file mode 100644 index 00000000..45936c36 --- /dev/null +++ b/applications/admin/static/codemirror/keymap/sublime.js @@ -0,0 +1,540 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// A rough approximation of Sublime Text's keybindings +// Depends on addon/search/searchcursor.js and optionally addon/dialog/dialogs.js + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/edit/matchbrackets")); + else if (typeof define == "function" && define.amd) // AMD + define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/edit/matchbrackets"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var map = CodeMirror.keyMap.sublime = {fallthrough: "default"}; + var cmds = CodeMirror.commands; + var Pos = CodeMirror.Pos; + var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault; + var ctrl = mac ? "Cmd-" : "Ctrl-"; + + // This is not exactly Sublime's algorithm. I couldn't make heads or tails of that. + function findPosSubword(doc, start, dir) { + if (dir < 0 && start.ch == 0) return doc.clipPos(Pos(start.line - 1)); + var line = doc.getLine(start.line); + if (dir > 0 && start.ch >= line.length) return doc.clipPos(Pos(start.line + 1, 0)); + var state = "start", type; + for (var pos = start.ch, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) { + var next = line.charAt(dir < 0 ? pos - 1 : pos); + var cat = next != "_" && CodeMirror.isWordChar(next) ? "w" : "o"; + if (cat == "w" && next.toUpperCase() == next) cat = "W"; + if (state == "start") { + if (cat != "o") { state = "in"; type = cat; } + } else if (state == "in") { + if (type != cat) { + if (type == "w" && cat == "W" && dir < 0) pos--; + if (type == "W" && cat == "w" && dir > 0) { type = "w"; continue; } + break; + } + } + } + return Pos(start.line, pos); + } + + function moveSubword(cm, dir) { + cm.extendSelectionsBy(function(range) { + if (cm.display.shift || cm.doc.extend || range.empty()) + return findPosSubword(cm.doc, range.head, dir); + else + return dir < 0 ? range.from() : range.to(); + }); + } + + cmds[map["Alt-Left"] = "goSubwordLeft"] = function(cm) { moveSubword(cm, -1); }; + cmds[map["Alt-Right"] = "goSubwordRight"] = function(cm) { moveSubword(cm, 1); }; + + cmds[map[ctrl + "Up"] = "scrollLineUp"] = function(cm) { + var info = cm.getScrollInfo(); + if (!cm.somethingSelected()) { + var visibleBottomLine = cm.lineAtHeight(info.top + info.clientHeight, "local"); + if (cm.getCursor().line >= visibleBottomLine) + cm.execCommand("goLineUp"); + } + cm.scrollTo(null, info.top - cm.defaultTextHeight()); + }; + cmds[map[ctrl + "Down"] = "scrollLineDown"] = function(cm) { + var info = cm.getScrollInfo(); + if (!cm.somethingSelected()) { + var visibleTopLine = cm.lineAtHeight(info.top, "local")+1; + if (cm.getCursor().line <= visibleTopLine) + cm.execCommand("goLineDown"); + } + cm.scrollTo(null, info.top + cm.defaultTextHeight()); + }; + + cmds[map["Shift-" + ctrl + "L"] = "splitSelectionByLine"] = function(cm) { + var ranges = cm.listSelections(), lineRanges = []; + for (var i = 0; i < ranges.length; i++) { + var from = ranges[i].from(), to = ranges[i].to(); + for (var line = from.line; line <= to.line; ++line) + if (!(to.line > from.line && line == to.line && to.ch == 0)) + lineRanges.push({anchor: line == from.line ? from : Pos(line, 0), + head: line == to.line ? to : Pos(line)}); + } + cm.setSelections(lineRanges, 0); + }; + + map["Shift-Tab"] = "indentLess"; + + cmds[map["Esc"] = "singleSelectionTop"] = function(cm) { + var range = cm.listSelections()[0]; + cm.setSelection(range.anchor, range.head, {scroll: false}); + }; + + cmds[map[ctrl + "L"] = "selectLine"] = function(cm) { + var ranges = cm.listSelections(), extended = []; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + extended.push({anchor: Pos(range.from().line, 0), + head: Pos(range.to().line + 1, 0)}); + } + cm.setSelections(extended); + }; + + map["Shift-" + ctrl + "K"] = "deleteLine"; + + function insertLine(cm, above) { + cm.operation(function() { + var len = cm.listSelections().length, newSelection = [], last = -1; + for (var i = 0; i < len; i++) { + var head = cm.listSelections()[i].head; + if (head.line <= last) continue; + var at = Pos(head.line + (above ? 0 : 1), 0); + cm.replaceRange("\n", at, null, "+insertLine"); + cm.indentLine(at.line, null, true); + newSelection.push({head: at, anchor: at}); + last = head.line + 1; + } + cm.setSelections(newSelection); + }); + } + + cmds[map[ctrl + "Enter"] = "insertLineAfter"] = function(cm) { insertLine(cm, false); }; + + cmds[map["Shift-" + ctrl + "Enter"] = "insertLineBefore"] = function(cm) { insertLine(cm, true); }; + + function wordAt(cm, pos) { + var start = pos.ch, end = start, line = cm.getLine(pos.line); + while (start && CodeMirror.isWordChar(line.charAt(start - 1))) --start; + while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) ++end; + return {from: Pos(pos.line, start), to: Pos(pos.line, end), word: line.slice(start, end)}; + } + + cmds[map[ctrl + "D"] = "selectNextOccurrence"] = function(cm) { + var from = cm.getCursor("from"), to = cm.getCursor("to"); + var fullWord = cm.state.sublimeFindFullWord == cm.doc.sel; + if (CodeMirror.cmpPos(from, to) == 0) { + var word = wordAt(cm, from); + if (!word.word) return; + cm.setSelection(word.from, word.to); + fullWord = true; + } else { + var text = cm.getRange(from, to); + var query = fullWord ? new RegExp("\\b" + text + "\\b") : text; + var cur = cm.getSearchCursor(query, to); + if (cur.findNext()) { + cm.addSelection(cur.from(), cur.to()); + } else { + cur = cm.getSearchCursor(query, Pos(cm.firstLine(), 0)); + if (cur.findNext()) + cm.addSelection(cur.from(), cur.to()); + } + } + if (fullWord) + cm.state.sublimeFindFullWord = cm.doc.sel; + }; + + var mirror = "(){}[]"; + function selectBetweenBrackets(cm) { + var pos = cm.getCursor(), opening = cm.scanForBracket(pos, -1); + if (!opening) return; + for (;;) { + var closing = cm.scanForBracket(pos, 1); + if (!closing) return; + if (closing.ch == mirror.charAt(mirror.indexOf(opening.ch) + 1)) { + cm.setSelection(Pos(opening.pos.line, opening.pos.ch + 1), closing.pos, false); + return true; + } + pos = Pos(closing.pos.line, closing.pos.ch + 1); + } + } + + cmds[map["Shift-" + ctrl + "Space"] = "selectScope"] = function(cm) { + selectBetweenBrackets(cm) || cm.execCommand("selectAll"); + }; + cmds[map["Shift-" + ctrl + "M"] = "selectBetweenBrackets"] = function(cm) { + if (!selectBetweenBrackets(cm)) return CodeMirror.Pass; + }; + + cmds[map[ctrl + "M"] = "goToBracket"] = function(cm) { + cm.extendSelectionsBy(function(range) { + var next = cm.scanForBracket(range.head, 1); + if (next && CodeMirror.cmpPos(next.pos, range.head) != 0) return next.pos; + var prev = cm.scanForBracket(range.head, -1); + return prev && Pos(prev.pos.line, prev.pos.ch + 1) || range.head; + }); + }; + + var swapLineCombo = mac ? "Cmd-Ctrl-" : "Shift-Ctrl-"; + + cmds[map[swapLineCombo + "Up"] = "swapLineUp"] = function(cm) { + var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = []; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i], from = range.from().line - 1, to = range.to().line; + newSels.push({anchor: Pos(range.anchor.line - 1, range.anchor.ch), + head: Pos(range.head.line - 1, range.head.ch)}); + if (range.to().ch == 0 && !range.empty()) --to; + if (from > at) linesToMove.push(from, to); + else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to; + at = to; + } + cm.operation(function() { + for (var i = 0; i < linesToMove.length; i += 2) { + var from = linesToMove[i], to = linesToMove[i + 1]; + var line = cm.getLine(from); + cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine"); + if (to > cm.lastLine()) + cm.replaceRange("\n" + line, Pos(cm.lastLine()), null, "+swapLine"); + else + cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine"); + } + cm.setSelections(newSels); + cm.scrollIntoView(); + }); + }; + + cmds[map[swapLineCombo + "Down"] = "swapLineDown"] = function(cm) { + var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1; + for (var i = ranges.length - 1; i >= 0; i--) { + var range = ranges[i], from = range.to().line + 1, to = range.from().line; + if (range.to().ch == 0 && !range.empty()) from--; + if (from < at) linesToMove.push(from, to); + else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to; + at = to; + } + cm.operation(function() { + for (var i = linesToMove.length - 2; i >= 0; i -= 2) { + var from = linesToMove[i], to = linesToMove[i + 1]; + var line = cm.getLine(from); + if (from == cm.lastLine()) + cm.replaceRange("", Pos(from - 1), Pos(from), "+swapLine"); + else + cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine"); + cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine"); + } + cm.scrollIntoView(); + }); + }; + + map[ctrl + "/"] = "toggleComment"; + + cmds[map[ctrl + "J"] = "joinLines"] = function(cm) { + var ranges = cm.listSelections(), joined = []; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i], from = range.from(); + var start = from.line, end = range.to().line; + while (i < ranges.length - 1 && ranges[i + 1].from().line == end) + end = ranges[++i].to().line; + joined.push({start: start, end: end, anchor: !range.empty() && from}); + } + cm.operation(function() { + var offset = 0, ranges = []; + for (var i = 0; i < joined.length; i++) { + var obj = joined[i]; + var anchor = obj.anchor && Pos(obj.anchor.line - offset, obj.anchor.ch), head; + for (var line = obj.start; line <= obj.end; line++) { + var actual = line - offset; + if (line == obj.end) head = Pos(actual, cm.getLine(actual).length + 1); + if (actual < cm.lastLine()) { + cm.replaceRange(" ", Pos(actual), Pos(actual + 1, /^\s*/.exec(cm.getLine(actual + 1))[0].length)); + ++offset; + } + } + ranges.push({anchor: anchor || head, head: head}); + } + cm.setSelections(ranges, 0); + }); + }; + + cmds[map["Shift-" + ctrl + "D"] = "duplicateLine"] = function(cm) { + cm.operation(function() { + var rangeCount = cm.listSelections().length; + for (var i = 0; i < rangeCount; i++) { + var range = cm.listSelections()[i]; + if (range.empty()) + cm.replaceRange(cm.getLine(range.head.line) + "\n", Pos(range.head.line, 0)); + else + cm.replaceRange(cm.getRange(range.from(), range.to()), range.from()); + } + cm.scrollIntoView(); + }); + }; + + map[ctrl + "T"] = "transposeChars"; + + function sortLines(cm, caseSensitive) { + var ranges = cm.listSelections(), toSort = [], selected; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (range.empty()) continue; + var from = range.from().line, to = range.to().line; + while (i < ranges.length - 1 && ranges[i + 1].from().line == to) + to = range[++i].to().line; + toSort.push(from, to); + } + if (toSort.length) selected = true; + else toSort.push(cm.firstLine(), cm.lastLine()); + + cm.operation(function() { + var ranges = []; + for (var i = 0; i < toSort.length; i += 2) { + var from = toSort[i], to = toSort[i + 1]; + var start = Pos(from, 0), end = Pos(to); + var lines = cm.getRange(start, end, false); + if (caseSensitive) + lines.sort(); + else + lines.sort(function(a, b) { + var au = a.toUpperCase(), bu = b.toUpperCase(); + if (au != bu) { a = au; b = bu; } + return a < b ? -1 : a == b ? 0 : 1; + }); + cm.replaceRange(lines, start, end); + if (selected) ranges.push({anchor: start, head: end}); + } + if (selected) cm.setSelections(ranges, 0); + }); + } + + cmds[map["F9"] = "sortLines"] = function(cm) { sortLines(cm, true); }; + cmds[map[ctrl + "F9"] = "sortLinesInsensitive"] = function(cm) { sortLines(cm, false); }; + + cmds[map["F2"] = "nextBookmark"] = function(cm) { + var marks = cm.state.sublimeBookmarks; + if (marks) while (marks.length) { + var current = marks.shift(); + var found = current.find(); + if (found) { + marks.push(current); + return cm.setSelection(found.from, found.to); + } + } + }; + + cmds[map["Shift-F2"] = "prevBookmark"] = function(cm) { + var marks = cm.state.sublimeBookmarks; + if (marks) while (marks.length) { + marks.unshift(marks.pop()); + var found = marks[marks.length - 1].find(); + if (!found) + marks.pop(); + else + return cm.setSelection(found.from, found.to); + } + }; + + cmds[map[ctrl + "F2"] = "toggleBookmark"] = function(cm) { + var ranges = cm.listSelections(); + var marks = cm.state.sublimeBookmarks || (cm.state.sublimeBookmarks = []); + for (var i = 0; i < ranges.length; i++) { + var from = ranges[i].from(), to = ranges[i].to(); + var found = cm.findMarks(from, to); + for (var j = 0; j < found.length; j++) { + if (found[j].sublimeBookmark) { + found[j].clear(); + for (var k = 0; k < marks.length; k++) + if (marks[k] == found[j]) + marks.splice(k--, 1); + break; + } + } + if (j == found.length) + marks.push(cm.markText(from, to, {sublimeBookmark: true, clearWhenEmpty: false})); + } + }; + + cmds[map["Shift-" + ctrl + "F2"] = "clearBookmarks"] = function(cm) { + var marks = cm.state.sublimeBookmarks; + if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear(); + marks.length = 0; + }; + + cmds[map["Alt-F2"] = "selectBookmarks"] = function(cm) { + var marks = cm.state.sublimeBookmarks, ranges = []; + if (marks) for (var i = 0; i < marks.length; i++) { + var found = marks[i].find(); + if (!found) + marks.splice(i--, 0); + else + ranges.push({anchor: found.from, head: found.to}); + } + if (ranges.length) + cm.setSelections(ranges, 0); + }; + + map["Alt-Q"] = "wrapLines"; + + var cK = ctrl + "K "; + + function modifyWordOrSelection(cm, mod) { + cm.operation(function() { + var ranges = cm.listSelections(), indices = [], replacements = []; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (range.empty()) { indices.push(i); replacements.push(""); } + else replacements.push(mod(cm.getRange(range.from(), range.to()))); + } + cm.replaceSelections(replacements, "around", "case"); + for (var i = indices.length - 1, at; i >= 0; i--) { + var range = ranges[indices[i]]; + if (at && CodeMirror.cmpPos(range.head, at) > 0) continue; + var word = wordAt(cm, range.head); + at = word.from; + cm.replaceRange(mod(word.word), word.from, word.to); + } + }); + } + + map[cK + ctrl + "Backspace"] = "delLineLeft"; + + cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) { + cm.operation(function() { + var ranges = cm.listSelections(); + for (var i = ranges.length - 1; i >= 0; i--) + cm.replaceRange("", ranges[i].anchor, Pos(ranges[i].to().line), "+delete"); + cm.scrollIntoView(); + }); + }; + + cmds[map[cK + ctrl + "U"] = "upcaseAtCursor"] = function(cm) { + modifyWordOrSelection(cm, function(str) { return str.toUpperCase(); }); + }; + cmds[map[cK + ctrl + "L"] = "downcaseAtCursor"] = function(cm) { + modifyWordOrSelection(cm, function(str) { return str.toLowerCase(); }); + }; + + cmds[map[cK + ctrl + "Space"] = "setSublimeMark"] = function(cm) { + if (cm.state.sublimeMark) cm.state.sublimeMark.clear(); + cm.state.sublimeMark = cm.setBookmark(cm.getCursor()); + }; + cmds[map[cK + ctrl + "A"] = "selectToSublimeMark"] = function(cm) { + var found = cm.state.sublimeMark && cm.state.sublimeMark.find(); + if (found) cm.setSelection(cm.getCursor(), found); + }; + cmds[map[cK + ctrl + "W"] = "deleteToSublimeMark"] = function(cm) { + var found = cm.state.sublimeMark && cm.state.sublimeMark.find(); + if (found) { + var from = cm.getCursor(), to = found; + if (CodeMirror.cmpPos(from, to) > 0) { var tmp = to; to = from; from = tmp; } + cm.state.sublimeKilled = cm.getRange(from, to); + cm.replaceRange("", from, to); + } + }; + cmds[map[cK + ctrl + "X"] = "swapWithSublimeMark"] = function(cm) { + var found = cm.state.sublimeMark && cm.state.sublimeMark.find(); + if (found) { + cm.state.sublimeMark.clear(); + cm.state.sublimeMark = cm.setBookmark(cm.getCursor()); + cm.setCursor(found); + } + }; + cmds[map[cK + ctrl + "Y"] = "sublimeYank"] = function(cm) { + if (cm.state.sublimeKilled != null) + cm.replaceSelection(cm.state.sublimeKilled, null, "paste"); + }; + + map[cK + ctrl + "G"] = "clearBookmarks"; + cmds[map[cK + ctrl + "C"] = "showInCenter"] = function(cm) { + var pos = cm.cursorCoords(null, "local"); + cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2); + }; + + cmds[map["Shift-Alt-Up"] = "selectLinesUpward"] = function(cm) { + cm.operation(function() { + var ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (range.head.line > cm.firstLine()) + cm.addSelection(Pos(range.head.line - 1, range.head.ch)); + } + }); + }; + cmds[map["Shift-Alt-Down"] = "selectLinesDownward"] = function(cm) { + cm.operation(function() { + var ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (range.head.line < cm.lastLine()) + cm.addSelection(Pos(range.head.line + 1, range.head.ch)); + } + }); + }; + + function getTarget(cm) { + var from = cm.getCursor("from"), to = cm.getCursor("to"); + if (CodeMirror.cmpPos(from, to) == 0) { + var word = wordAt(cm, from); + if (!word.word) return; + from = word.from; + to = word.to; + } + return {from: from, to: to, query: cm.getRange(from, to), word: word}; + } + + function findAndGoTo(cm, forward) { + var target = getTarget(cm); + if (!target) return; + var query = target.query; + var cur = cm.getSearchCursor(query, forward ? target.to : target.from); + + if (forward ? cur.findNext() : cur.findPrevious()) { + cm.setSelection(cur.from(), cur.to()); + } else { + cur = cm.getSearchCursor(query, forward ? Pos(cm.firstLine(), 0) + : cm.clipPos(Pos(cm.lastLine()))); + if (forward ? cur.findNext() : cur.findPrevious()) + cm.setSelection(cur.from(), cur.to()); + else if (target.word) + cm.setSelection(target.from, target.to); + } + }; + cmds[map[ctrl + "F3"] = "findUnder"] = function(cm) { findAndGoTo(cm, true); }; + cmds[map["Shift-" + ctrl + "F3"] = "findUnderPrevious"] = function(cm) { findAndGoTo(cm,false); }; + cmds[map["Alt-F3"] = "findAllUnder"] = function(cm) { + var target = getTarget(cm); + if (!target) return; + var cur = cm.getSearchCursor(target.query); + var matches = []; + var primaryIndex = -1; + while (cur.findNext()) { + matches.push({anchor: cur.from(), head: cur.to()}); + if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch) + primaryIndex++; + } + cm.setSelections(matches, primaryIndex); + }; + + map["Shift-" + ctrl + "["] = "fold"; + map["Shift-" + ctrl + "]"] = "unfold"; + map[cK + ctrl + "0"] = map[cK + ctrl + "j"] = "unfoldAll"; + + map[ctrl + "I"] = "findIncremental"; + map["Shift-" + ctrl + "I"] = "findIncrementalReverse"; + map[ctrl + "H"] = "replace"; + map["F3"] = "findNext"; + map["Shift-F3"] = "findPrev"; + + CodeMirror.normalizeKeyMap(map); +}); diff --git a/applications/admin/static/codemirror/keymap/vim.js b/applications/admin/static/codemirror/keymap/vim.js index f9cdfd54..682eb7a8 100644 --- a/applications/admin/static/codemirror/keymap/vim.js +++ b/applications/admin/static/codemirror/keymap/vim.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + /** * Supported keybindings: * @@ -58,9 +61,9 @@ (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/dialog/dialog")); + mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/dialog/dialog"), require("../addon/edit/matchbrackets.js")); else if (typeof define == "function" && define.amd) // AMD - define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog"], mod); + define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/dialog/dialog", "../addon/edit/matchbrackets"], mod); else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { @@ -69,304 +72,256 @@ var defaultKeymap = [ // Key to key mapping. This goes first to make it possible to override // existing mappings. - { keys: [''], type: 'keyToKey', toKeys: ['h'] }, - { keys: [''], type: 'keyToKey', toKeys: ['l'] }, - { keys: [''], type: 'keyToKey', toKeys: ['k'] }, - { keys: [''], type: 'keyToKey', toKeys: ['j'] }, - { keys: [''], type: 'keyToKey', toKeys: ['l'] }, - { keys: [''], type: 'keyToKey', toKeys: ['h'] }, - { keys: [''], type: 'keyToKey', toKeys: ['W'] }, - { keys: [''], type: 'keyToKey', toKeys: ['B'] }, - { keys: [''], type: 'keyToKey', toKeys: ['w'] }, - { keys: [''], type: 'keyToKey', toKeys: ['b'] }, - { keys: [''], type: 'keyToKey', toKeys: ['j'] }, - { keys: [''], type: 'keyToKey', toKeys: ['k'] }, - { keys: [''], type: 'keyToKey', toKeys: [''] }, - { keys: [''], type: 'keyToKey', toKeys: [''] }, - { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'], context: 'normal' }, - { keys: ['s'], type: 'keyToKey', toKeys: ['x', 'i'], context: 'visual'}, - { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'], context: 'normal' }, - { keys: ['S'], type: 'keyToKey', toKeys: ['d', 'c', 'c'], context: 'visual' }, - { keys: [''], type: 'keyToKey', toKeys: ['0'] }, - { keys: [''], type: 'keyToKey', toKeys: ['$'] }, - { keys: [''], type: 'keyToKey', toKeys: [''] }, - { keys: [''], type: 'keyToKey', toKeys: [''] }, - { keys: [''], type: 'keyToKey', toKeys: ['j', '^'], context: 'normal' }, + { keys: '', type: 'keyToKey', toKeys: 'h' }, + { keys: '', type: 'keyToKey', toKeys: 'l' }, + { keys: '', type: 'keyToKey', toKeys: 'k' }, + { keys: '', type: 'keyToKey', toKeys: 'j' }, + { keys: '', type: 'keyToKey', toKeys: 'l' }, + { keys: '', type: 'keyToKey', toKeys: 'h', context: 'normal'}, + { keys: '', type: 'keyToKey', toKeys: 'W' }, + { keys: '', type: 'keyToKey', toKeys: 'B', context: 'normal' }, + { keys: '', type: 'keyToKey', toKeys: 'w' }, + { keys: '', type: 'keyToKey', toKeys: 'b', context: 'normal' }, + { keys: '', type: 'keyToKey', toKeys: 'j' }, + { keys: '', type: 'keyToKey', toKeys: 'k' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, + { keys: '', type: 'keyToKey', toKeys: '', context: 'insert' }, + { keys: 's', type: 'keyToKey', toKeys: 'cl', context: 'normal' }, + { keys: 's', type: 'keyToKey', toKeys: 'xi', context: 'visual'}, + { keys: 'S', type: 'keyToKey', toKeys: 'cc', context: 'normal' }, + { keys: 'S', type: 'keyToKey', toKeys: 'dcc', context: 'visual' }, + { keys: '', type: 'keyToKey', toKeys: '0' }, + { keys: '', type: 'keyToKey', toKeys: '$' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: '' }, + { keys: '', type: 'keyToKey', toKeys: 'j^', context: 'normal' }, // Motions - { keys: ['H'], type: 'motion', - motion: 'moveToTopLine', - motionArgs: { linewise: true, toJumplist: true }}, - { keys: ['M'], type: 'motion', - motion: 'moveToMiddleLine', - motionArgs: { linewise: true, toJumplist: true }}, - { keys: ['L'], type: 'motion', - motion: 'moveToBottomLine', - motionArgs: { linewise: true, toJumplist: true }}, - { keys: ['h'], type: 'motion', - motion: 'moveByCharacters', - motionArgs: { forward: false }}, - { keys: ['l'], type: 'motion', - motion: 'moveByCharacters', - motionArgs: { forward: true }}, - { keys: ['j'], type: 'motion', - motion: 'moveByLines', - motionArgs: { forward: true, linewise: true }}, - { keys: ['k'], type: 'motion', - motion: 'moveByLines', - motionArgs: { forward: false, linewise: true }}, - { keys: ['g','j'], type: 'motion', - motion: 'moveByDisplayLines', - motionArgs: { forward: true }}, - { keys: ['g','k'], type: 'motion', - motion: 'moveByDisplayLines', - motionArgs: { forward: false }}, - { keys: ['w'], type: 'motion', - motion: 'moveByWords', - motionArgs: { forward: true, wordEnd: false }}, - { keys: ['W'], type: 'motion', - motion: 'moveByWords', - motionArgs: { forward: true, wordEnd: false, bigWord: true }}, - { keys: ['e'], type: 'motion', - motion: 'moveByWords', - motionArgs: { forward: true, wordEnd: true, inclusive: true }}, - { keys: ['E'], type: 'motion', - motion: 'moveByWords', - motionArgs: { forward: true, wordEnd: true, bigWord: true, - inclusive: true }}, - { keys: ['b'], type: 'motion', - motion: 'moveByWords', - motionArgs: { forward: false, wordEnd: false }}, - { keys: ['B'], type: 'motion', - motion: 'moveByWords', - motionArgs: { forward: false, wordEnd: false, bigWord: true }}, - { keys: ['g', 'e'], type: 'motion', - motion: 'moveByWords', - motionArgs: { forward: false, wordEnd: true, inclusive: true }}, - { keys: ['g', 'E'], type: 'motion', - motion: 'moveByWords', - motionArgs: { forward: false, wordEnd: true, bigWord: true, - inclusive: true }}, - { keys: ['{'], type: 'motion', motion: 'moveByParagraph', - motionArgs: { forward: false, toJumplist: true }}, - { keys: ['}'], type: 'motion', motion: 'moveByParagraph', - motionArgs: { forward: true, toJumplist: true }}, - { keys: [''], type: 'motion', - motion: 'moveByPage', motionArgs: { forward: true }}, - { keys: [''], type: 'motion', - motion: 'moveByPage', motionArgs: { forward: false }}, - { keys: [''], type: 'motion', - motion: 'moveByScroll', - motionArgs: { forward: true, explicitRepeat: true }}, - { keys: [''], type: 'motion', - motion: 'moveByScroll', - motionArgs: { forward: false, explicitRepeat: true }}, - { keys: ['g', 'g'], type: 'motion', - motion: 'moveToLineOrEdgeOfDocument', - motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }}, - { keys: ['G'], type: 'motion', - motion: 'moveToLineOrEdgeOfDocument', - motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }}, - { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' }, - { keys: ['^'], type: 'motion', - motion: 'moveToFirstNonWhiteSpaceCharacter' }, - { keys: ['+'], type: 'motion', - motion: 'moveByLines', - motionArgs: { forward: true, toFirstChar:true }}, - { keys: ['-'], type: 'motion', - motion: 'moveByLines', - motionArgs: { forward: false, toFirstChar:true }}, - { keys: ['_'], type: 'motion', - motion: 'moveByLines', - motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }}, - { keys: ['$'], type: 'motion', - motion: 'moveToEol', - motionArgs: { inclusive: true }}, - { keys: ['%'], type: 'motion', - motion: 'moveToMatchedSymbol', - motionArgs: { inclusive: true, toJumplist: true }}, - { keys: ['f', 'character'], type: 'motion', - motion: 'moveToCharacter', - motionArgs: { forward: true , inclusive: true }}, - { keys: ['F', 'character'], type: 'motion', - motion: 'moveToCharacter', - motionArgs: { forward: false }}, - { keys: ['t', 'character'], type: 'motion', - motion: 'moveTillCharacter', - motionArgs: { forward: true, inclusive: true }}, - { keys: ['T', 'character'], type: 'motion', - motion: 'moveTillCharacter', - motionArgs: { forward: false }}, - { keys: [';'], type: 'motion', motion: 'repeatLastCharacterSearch', - motionArgs: { forward: true }}, - { keys: [','], type: 'motion', motion: 'repeatLastCharacterSearch', - motionArgs: { forward: false }}, - { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark', - motionArgs: {toJumplist: true}}, - { keys: ['`', 'character'], type: 'motion', motion: 'goToMark', - motionArgs: {toJumplist: true}}, - { keys: [']', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } }, - { keys: ['[', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } }, - { keys: [']', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } }, - { keys: ['[', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } }, - { keys: [']', 'character'], type: 'motion', - motion: 'moveToSymbol', - motionArgs: { forward: true, toJumplist: true}}, - { keys: ['[', 'character'], type: 'motion', - motion: 'moveToSymbol', - motionArgs: { forward: false, toJumplist: true}}, - { keys: ['|'], type: 'motion', - motion: 'moveToColumn', - motionArgs: { }}, - { keys: ['o'], type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: { },context:'visual'}, + { keys: 'H', type: 'motion', motion: 'moveToTopLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'M', type: 'motion', motion: 'moveToMiddleLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'L', type: 'motion', motion: 'moveToBottomLine', motionArgs: { linewise: true, toJumplist: true }}, + { keys: 'h', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }}, + { keys: 'l', type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: true }}, + { keys: 'j', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, linewise: true }}, + { keys: 'k', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, linewise: true }}, + { keys: 'gj', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: true }}, + { keys: 'gk', type: 'motion', motion: 'moveByDisplayLines', motionArgs: { forward: false }}, + { keys: 'w', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false }}, + { keys: 'W', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: false, bigWord: true }}, + { keys: 'e', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, inclusive: true }}, + { keys: 'E', type: 'motion', motion: 'moveByWords', motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true }}, + { keys: 'b', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }}, + { keys: 'B', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false, bigWord: true }}, + { keys: 'ge', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, inclusive: true }}, + { keys: 'gE', type: 'motion', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }}, + { keys: '{', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: false, toJumplist: true }}, + { keys: '}', type: 'motion', motion: 'moveByParagraph', motionArgs: { forward: true, toJumplist: true }}, + { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }}, + { keys: '', type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }}, + { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: true, explicitRepeat: true }}, + { keys: '', type: 'motion', motion: 'moveByScroll', motionArgs: { forward: false, explicitRepeat: true }}, + { keys: 'gg', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }}, + { keys: 'G', type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }}, + { keys: '0', type: 'motion', motion: 'moveToStartOfLine' }, + { keys: '^', type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: '+', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true }}, + { keys: '-', type: 'motion', motion: 'moveByLines', motionArgs: { forward: false, toFirstChar:true }}, + { keys: '_', type: 'motion', motion: 'moveByLines', motionArgs: { forward: true, toFirstChar:true, repeatOffset:-1 }}, + { keys: '$', type: 'motion', motion: 'moveToEol', motionArgs: { inclusive: true }}, + { keys: '%', type: 'motion', motion: 'moveToMatchedSymbol', motionArgs: { inclusive: true, toJumplist: true }}, + { keys: 'f', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }}, + { keys: 'F', type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: false }}, + { keys: 't', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: true, inclusive: true }}, + { keys: 'T', type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }}, + { keys: ';', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: true }}, + { keys: ',', type: 'motion', motion: 'repeatLastCharacterSearch', motionArgs: { forward: false }}, + { keys: '\'', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true, linewise: true}}, + { keys: '`', type: 'motion', motion: 'goToMark', motionArgs: {toJumplist: true}}, + { keys: ']`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } }, + { keys: '[`', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } }, + { keys: ']\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } }, + { keys: '[\'', type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } }, + // the next two aren't motions but must come before more general motion declarations + { keys: ']p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true}}, + { keys: '[p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true}}, + { keys: ']', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: true, toJumplist: true}}, + { keys: '[', type: 'motion', motion: 'moveToSymbol', motionArgs: { forward: false, toJumplist: true}}, + { keys: '|', type: 'motion', motion: 'moveToColumn'}, + { keys: 'o', type: 'motion', motion: 'moveToOtherHighlightedEnd', context:'visual'}, + { keys: 'O', type: 'motion', motion: 'moveToOtherHighlightedEnd', motionArgs: {sameLine: true}, context:'visual'}, // Operators - { keys: ['d'], type: 'operator', operator: 'delete' }, - { keys: ['y'], type: 'operator', operator: 'yank' }, - { keys: ['c'], type: 'operator', operator: 'change' }, - { keys: ['>'], type: 'operator', operator: 'indent', - operatorArgs: { indentRight: true }}, - { keys: ['<'], type: 'operator', operator: 'indent', - operatorArgs: { indentRight: false }}, - { keys: ['g', '~'], type: 'operator', operator: 'swapcase' }, - { keys: ['n'], type: 'motion', motion: 'findNext', - motionArgs: { forward: true, toJumplist: true }}, - { keys: ['N'], type: 'motion', motion: 'findNext', - motionArgs: { forward: false, toJumplist: true }}, + { keys: 'd', type: 'operator', operator: 'delete' }, + { keys: 'y', type: 'operator', operator: 'yank' }, + { keys: 'c', type: 'operator', operator: 'change' }, + { keys: '>', type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }}, + { keys: '<', type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }}, + { keys: 'g~', type: 'operator', operator: 'changeCase' }, + { keys: 'gu', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, isEdit: true }, + { keys: 'gU', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, isEdit: true }, + { keys: 'n', type: 'motion', motion: 'findNext', motionArgs: { forward: true, toJumplist: true }}, + { keys: 'N', type: 'motion', motion: 'findNext', motionArgs: { forward: false, toJumplist: true }}, // Operator-Motion dual commands - { keys: ['x'], type: 'operatorMotion', operator: 'delete', - motion: 'moveByCharacters', motionArgs: { forward: true }, - operatorMotionArgs: { visualLine: false }}, - { keys: ['X'], type: 'operatorMotion', operator: 'delete', - motion: 'moveByCharacters', motionArgs: { forward: false }, - operatorMotionArgs: { visualLine: true }}, - { keys: ['D'], type: 'operatorMotion', operator: 'delete', - motion: 'moveToEol', motionArgs: { inclusive: true }, - operatorMotionArgs: { visualLine: true }}, - { keys: ['Y'], type: 'operatorMotion', operator: 'yank', - motion: 'moveToEol', motionArgs: { inclusive: true }, - operatorMotionArgs: { visualLine: true }}, - { keys: ['C'], type: 'operatorMotion', - operator: 'change', - motion: 'moveToEol', motionArgs: { inclusive: true }, - operatorMotionArgs: { visualLine: true }}, - { keys: ['~'], type: 'operatorMotion', - operator: 'swapcase', operatorArgs: { shouldMoveCursor: true }, - motion: 'moveByCharacters', motionArgs: { forward: true }}, + { keys: 'x', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false }}, + { keys: 'X', type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true }}, + { keys: 'D', type: 'operatorMotion', operator: 'delete', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, + { keys: 'D', type: 'operator', operator: 'delete', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: 'Y', type: 'operatorMotion', operator: 'yank', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, + { keys: 'Y', type: 'operator', operator: 'yank', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: 'C', type: 'operatorMotion', operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, context: 'normal'}, + { keys: 'C', type: 'operator', operator: 'change', operatorArgs: { linewise: true }, context: 'visual'}, + { keys: '~', type: 'operatorMotion', operator: 'changeCase', motion: 'moveByCharacters', motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: 'normal'}, + { keys: '~', type: 'operator', operator: 'changeCase', context: 'visual'}, + { keys: '', type: 'operatorMotion', operator: 'delete', motion: 'moveByWords', motionArgs: { forward: false, wordEnd: false }, context: 'insert' }, // Actions - { keys: [''], type: 'action', action: 'jumpListWalk', - actionArgs: { forward: true }}, - { keys: [''], type: 'action', action: 'jumpListWalk', - actionArgs: { forward: false }}, - { keys: [''], type: 'action', - action: 'scroll', - actionArgs: { forward: true, linewise: true }}, - { keys: [''], type: 'action', - action: 'scroll', - actionArgs: { forward: false, linewise: true }}, - { keys: ['a'], type: 'action', action: 'enterInsertMode', isEdit: true, - actionArgs: { insertAt: 'charAfter' }}, - { keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true, - actionArgs: { insertAt: 'eol' }}, - { keys: ['i'], type: 'action', action: 'enterInsertMode', isEdit: true, - actionArgs: { insertAt: 'inplace' }}, - { keys: ['I'], type: 'action', action: 'enterInsertMode', isEdit: true, - actionArgs: { insertAt: 'firstNonBlank' }}, - { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode', - isEdit: true, interlaceInsertRepeat: true, - actionArgs: { after: true }}, - { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode', - isEdit: true, interlaceInsertRepeat: true, - actionArgs: { after: false }}, - { keys: ['v'], type: 'action', action: 'toggleVisualMode' }, - { keys: ['V'], type: 'action', action: 'toggleVisualMode', - actionArgs: { linewise: true }}, - { keys: ['g', 'v'], type: 'action', action: 'reselectLastSelection' }, - { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true }, - { keys: ['p'], type: 'action', action: 'paste', isEdit: true, - actionArgs: { after: true, isEdit: true }}, - { keys: ['P'], type: 'action', action: 'paste', isEdit: true, - actionArgs: { after: false, isEdit: true }}, - { keys: ['r', 'character'], type: 'action', action: 'replace', isEdit: true }, - { keys: ['@', 'character'], type: 'action', action: 'replayMacro' }, - { keys: ['q', 'character'], type: 'action', action: 'enterMacroRecordMode' }, + { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: true }}, + { keys: '', type: 'action', action: 'jumpListWalk', actionArgs: { forward: false }}, + { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: true, linewise: true }}, + { keys: '', type: 'action', action: 'scroll', actionArgs: { forward: false, linewise: true }}, + { keys: 'a', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }, context: 'normal' }, + { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' }, + { keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' }, + { keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' }, + { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' }, + { keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' }, + { keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' }, + { keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' }, + { keys: 'v', type: 'action', action: 'toggleVisualMode' }, + { keys: 'V', type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }}, + { keys: '', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }}, + { keys: 'gv', type: 'action', action: 'reselectLastSelection' }, + { keys: 'J', type: 'action', action: 'joinLines', isEdit: true }, + { keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }}, + { keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }}, + { keys: 'r', type: 'action', action: 'replace', isEdit: true }, + { keys: '@', type: 'action', action: 'replayMacro' }, + { keys: 'q', type: 'action', action: 'enterMacroRecordMode' }, // Handle Replace-mode as a special case of insert mode. - { keys: ['R'], type: 'action', action: 'enterInsertMode', isEdit: true, - actionArgs: { replace: true }}, - { keys: ['u'], type: 'action', action: 'undo' }, - { keys: [''], type: 'action', action: 'redo' }, - { keys: ['m', 'character'], type: 'action', action: 'setMark' }, - { keys: ['"', 'character'], type: 'action', action: 'setRegister' }, - { keys: ['z', 'z'], type: 'action', action: 'scrollToCursor', - actionArgs: { position: 'center' }}, - { keys: ['z', '.'], type: 'action', action: 'scrollToCursor', - actionArgs: { position: 'center' }, - motion: 'moveToFirstNonWhiteSpaceCharacter' }, - { keys: ['z', 't'], type: 'action', action: 'scrollToCursor', - actionArgs: { position: 'top' }}, - { keys: ['z', ''], type: 'action', action: 'scrollToCursor', - actionArgs: { position: 'top' }, - motion: 'moveToFirstNonWhiteSpaceCharacter' }, - { keys: ['z', '-'], type: 'action', action: 'scrollToCursor', - actionArgs: { position: 'bottom' }}, - { keys: ['z', 'b'], type: 'action', action: 'scrollToCursor', - actionArgs: { position: 'bottom' }, - motion: 'moveToFirstNonWhiteSpaceCharacter' }, - { keys: ['.'], type: 'action', action: 'repeatLastEdit' }, - { keys: [''], type: 'action', action: 'incrementNumberToken', - isEdit: true, - actionArgs: {increase: true, backtrack: false}}, - { keys: [''], type: 'action', action: 'incrementNumberToken', - isEdit: true, - actionArgs: {increase: false, backtrack: false}}, + { keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }}, + { keys: 'u', type: 'action', action: 'undo', context: 'normal' }, + { keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true }, + { keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true }, + { keys: '', type: 'action', action: 'redo' }, + { keys: 'm', type: 'action', action: 'setMark' }, + { keys: '"', type: 'action', action: 'setRegister' }, + { keys: 'zz', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }}, + { keys: 'z.', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: 'zt', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }}, + { keys: 'z', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: 'z-', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }}, + { keys: 'zb', type: 'action', action: 'scrollToCursor', actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: '.', type: 'action', action: 'repeatLastEdit' }, + { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: true, backtrack: false}}, + { keys: '', type: 'action', action: 'incrementNumberToken', isEdit: true, actionArgs: {increase: false, backtrack: false}}, // Text object motions - { keys: ['a', 'character'], type: 'motion', - motion: 'textObjectManipulation' }, - { keys: ['i', 'character'], type: 'motion', - motion: 'textObjectManipulation', - motionArgs: { textObjectInner: true }}, + { keys: 'a', type: 'motion', motion: 'textObjectManipulation' }, + { keys: 'i', type: 'motion', motion: 'textObjectManipulation', motionArgs: { textObjectInner: true }}, // Search - { keys: ['/'], type: 'search', - searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }}, - { keys: ['?'], type: 'search', - searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }}, - { keys: ['*'], type: 'search', - searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }}, - { keys: ['#'], type: 'search', - searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }}, + { keys: '/', type: 'search', searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }}, + { keys: '?', type: 'search', searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }}, + { keys: '*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }}, + { keys: '#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', wholeWordOnly: true, toJumplist: true }}, + { keys: 'g*', type: 'search', searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }}, + { keys: 'g#', type: 'search', searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }}, // Ex command - { keys: [':'], type: 'ex' } + { keys: ':', type: 'ex' } ]; var Pos = CodeMirror.Pos; var Vim = function() { - CodeMirror.defineOption('vimMode', false, function(cm, val) { - if (val) { - cm.setOption('keyMap', 'vim'); - cm.setOption('disableInput', true); - CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); - cm.on('beforeSelectionChange', beforeSelectionChange); - maybeInitVimState(cm); - CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm)); - } else if (cm.state.vim) { - cm.setOption('keyMap', 'default'); - cm.setOption('disableInput', false); - cm.off('beforeSelectionChange', beforeSelectionChange); - CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm)); - cm.state.vim = null; - } - }); - function beforeSelectionChange(cm, obj) { - var vim = cm.state.vim; - if (vim.insertMode || vim.exMode) return; - - var head = obj.ranges[0].head; - var anchor = obj.ranges[0].anchor; - if (head.ch && head.ch == cm.doc.getLine(head.line).length) { - var pos = Pos(head.line, head.ch - 1); - obj.update([{anchor: cursorEqual(head, anchor) ? pos : anchor, - head: pos}]); - } + function enterVimMode(cm) { + cm.setOption('disableInput', true); + cm.setOption('showCursorWhenSelecting', false); + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + cm.on('cursorActivity', onCursorActivity); + maybeInitVimState(cm); + CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm)); } + + function leaveVimMode(cm) { + cm.setOption('disableInput', false); + cm.off('cursorActivity', onCursorActivity); + CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm)); + cm.state.vim = null; + } + + function detachVimMap(cm, next) { + if (this == CodeMirror.keyMap.vim) + CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor"); + + if (!next || next.attach != attachVimMap) + leaveVimMode(cm, false); + } + function attachVimMap(cm, prev) { + if (this == CodeMirror.keyMap.vim) + CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor"); + + if (!prev || prev.attach != attachVimMap) + enterVimMode(cm); + } + + // Deprecated, simply setting the keymap works again. + CodeMirror.defineOption('vimMode', false, function(cm, val, prev) { + if (val && cm.getOption("keyMap") != "vim") + cm.setOption("keyMap", "vim"); + else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap"))) + cm.setOption("keyMap", "default"); + }); + + function cmKey(key, cm) { + if (!cm) { return undefined; } + var vimKey = cmKeyToVimKey(key); + if (!vimKey) { + return false; + } + var cmd = CodeMirror.Vim.findKey(cm, vimKey); + if (typeof cmd == 'function') { + CodeMirror.signal(cm, 'vim-keypress', vimKey); + } + return cmd; + } + + var modifiers = {'Shift': 'S', 'Ctrl': 'C', 'Alt': 'A', 'Cmd': 'D', 'Mod': 'A'}; + var specialKeys = {Enter:'CR',Backspace:'BS',Delete:'Del'}; + function cmKeyToVimKey(key) { + if (key.charAt(0) == '\'') { + // Keypress character binding of format "'a'" + return key.charAt(1); + } + var pieces = key.split('-'); + if (/-$/.test(key)) { + // If the - key was typed, split will result in 2 extra empty strings + // in the array. Replace them with 1 '-'. + pieces.splice(-2, 2, '-'); + } + var lastPiece = pieces[pieces.length - 1]; + if (pieces.length == 1 && pieces[0].length == 1) { + // No-modifier bindings use literal character bindings above. Skip. + return false; + } else if (pieces.length == 2 && pieces[0] == 'Shift' && lastPiece.length == 1) { + // Ignore Shift+char bindings as they should be handled by literal character. + return false; + } + var hasCharacter = false; + for (var i = 0; i < pieces.length; i++) { + var piece = pieces[i]; + if (piece in modifiers) { pieces[i] = modifiers[piece]; } + else { hasCharacter = true; } + if (piece in specialKeys) { pieces[i] = specialKeys[piece]; } + } + if (!hasCharacter) { + // Vim does not support modifier only keys. + return false; + } + // TODO: Current bindings expect the character to be lower case, but + // it looks like vim key notation uses upper case. + if (isUpperCase(lastPiece)) { + pieces[pieces.length - 1] = lastPiece.toLowerCase(); + } + return '<' + pieces.join('-') + '>'; + } + function getOnPasteFn(cm) { var vim = cm.state.vim; if (!vim.onPasteFn) { @@ -392,11 +347,8 @@ var upperCaseAlphabet = makeKeyRange(65, 26); var lowerCaseAlphabet = makeKeyRange(97, 26); var numbers = makeKeyRange(48, 10); - var specialSymbols = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;"\''.split(''); - var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace', - 'Esc', 'Home', 'End', 'PageUp', 'PageDown', 'Enter']; var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']); - var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"']); + var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"', '.', ':', '/']); function isLine(cm, line) { return line >= cm.firstLine() && line <= cm.lastLine(); @@ -549,13 +501,16 @@ this.latestRegister = undefined; this.isPlaying = false; this.isRecording = false; + this.replaySearchQueries = []; this.onRecordingDone = undefined; this.lastInsertModeChanges = createInsertModeChanges(); } MacroModeState.prototype = { exitMacroRecordMode: function() { var macroModeState = vimGlobalState.macroModeState; - macroModeState.onRecordingDone(); // close dialog + if (macroModeState.onRecordingDone) { + macroModeState.onRecordingDone(); // close dialog + } macroModeState.onRecordingDone = undefined; macroModeState.isRecording = false; }, @@ -565,8 +520,10 @@ if (register) { register.clear(); this.latestRegister = registerName; - this.onRecordingDone = cm.openDialog( - '(recording)['+registerName+']', null, {bottom:true}); + if (cm.openDialog) { + this.onRecordingDone = cm.openDialog( + '(recording)['+registerName+']', null, {bottom:true}); + } this.isRecording = true; } } @@ -595,6 +552,8 @@ // executed in between. lastMotion: null, marks: {}, + // Mark for rendering fake cursor for visual mode. + fakeCursor: null, insertMode: false, // Repeat count for changes made in insert mode, triggered by key // sequences like 3,i. Only exists when insertMode is true. @@ -602,7 +561,11 @@ visualMode: false, // If we are in visual line mode. No effect if visualMode is false. visualLine: false, - lastSelection: null + visualBlock: false, + lastSelection: null, + lastPastedText: null, + sel: { + } }; } return cm.state.vim; @@ -614,11 +577,17 @@ searchQuery: null, // Whether we are searching backwards. searchIsReversed: false, + // Replace part of the last substituted pattern + lastSubstituteReplacePart: undefined, jumpList: createCircularJumpList(), macroModeState: new MacroModeState, // Recording latest f, t, F or T motion command. lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''}, - registerController: new RegisterController({}) + registerController: new RegisterController({}), + // search history buffer + searchHistoryController: new HistoryController({}), + // ex Command history buffer + exCommandHistoryController : new HistoryController({}) }; for (var optionName in options) { var option = options[optionName]; @@ -626,6 +595,7 @@ } } + var lastInsertModeKeyTimer; var vimApi= { buildKeyMap: function() { // TODO: Convert keymap into dictionary format for fast lookup. @@ -646,6 +616,8 @@ // Testing hook. maybeInitVimState_: maybeInitVimState, + suppressErrorLogging: false, + InsertModeKey: InsertModeKey, map: function(lhs, rhs, ctx) { // Add user defined key bindings. @@ -661,64 +633,159 @@ exCommands[name]=func; exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'}; }, - // This is the outermost function called by CodeMirror, after keys have - // been mapped to their Vim equivalents. - handleKey: function(cm, key) { - var command; + handleKey: function (cm, key, origin) { + var command = this.findKey(cm, key, origin); + if (typeof command === 'function') { + return command(); + } + }, + /** + * This is the outermost function called by CodeMirror, after keys have + * been mapped to their Vim equivalents. + * + * Finds a command based on the key (and cached keys if there is a + * multi-key sequence). Returns `undefined` if no key is matched, a noop + * function if a partial match is found (multi-key), and a function to + * execute the bound command if a a key is matched. The function always + * returns true. + */ + findKey: function(cm, key, origin) { var vim = maybeInitVimState(cm); - var macroModeState = vimGlobalState.macroModeState; - if (macroModeState.isRecording) { - if (key == 'q') { - macroModeState.exitMacroRecordMode(); - vim.inputState = new InputState(); - return; - } - } - if (key == '') { - // Clear input state and get back to normal mode. - vim.inputState = new InputState(); - if (vim.visualMode) { - exitVisualMode(cm); - } - return; - } - // Enter visual mode when the mouse selects text. - if (!vim.visualMode && - !cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) { - vim.visualMode = true; - vim.visualLine = false; - CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); - cm.on('mousedown', exitVisualMode); - } - if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) { - // Have to special case 0 since it's both a motion and a number. - command = commandDispatcher.matchCommand(key, defaultKeymap, vim); - } - if (!command) { - if (isNumber(key)) { - // Increment count unless count is 0 and key is 0. - vim.inputState.pushRepeatDigit(key); - } + function handleMacroRecording() { + var macroModeState = vimGlobalState.macroModeState; if (macroModeState.isRecording) { - logKey(macroModeState, key); + if (key == 'q') { + macroModeState.exitMacroRecordMode(); + clearInputState(cm); + return true; + } + if (origin != 'mapping') { + logKey(macroModeState, key); + } } - return; } - if (command.type == 'keyToKey') { + function handleEsc() { + if (key == '') { + // Clear input state and get back to normal mode. + clearInputState(cm); + if (vim.visualMode) { + exitVisualMode(cm); + } else if (vim.insertMode) { + exitInsertMode(cm); + } + return true; + } + } + function doKeyToKey(keys) { // TODO: prevent infinite recursion. - for (var i = 0; i < command.toKeys.length; i++) { - this.handleKey(cm, command.toKeys[i]); + var match; + while (keys) { + // Pull off one command key, which is either a single character + // or a special sequence wrapped in '<' and '>', e.g. ''. + match = (/<\w+-.+?>|<\w+>|./).exec(keys); + key = match[0]; + keys = keys.substring(match.index + key.length); + CodeMirror.Vim.handleKey(cm, key, 'mapping'); } + } + + function handleKeyInsertMode() { + if (handleEsc()) { return true; } + var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key; + var keysAreChars = key.length == 1; + var match = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert'); + // Need to check all key substrings in insert mode. + while (keys.length > 1 && match.type != 'full') { + var keys = vim.inputState.keyBuffer = keys.slice(1); + var thisMatch = commandDispatcher.matchCommand(keys, defaultKeymap, vim.inputState, 'insert'); + if (thisMatch.type != 'none') { match = thisMatch; } + } + if (match.type == 'none') { clearInputState(cm); return false; } + else if (match.type == 'partial') { + if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); } + lastInsertModeKeyTimer = window.setTimeout( + function() { if (vim.insertMode && vim.inputState.keyBuffer) { clearInputState(cm); } }, + getOption('insertModeEscKeysTimeout')); + return !keysAreChars; + } + + if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); } + if (keysAreChars) { + var here = cm.getCursor(); + cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input'); + } + clearInputState(cm); + return match.command; + } + + function handleKeyNonInsertMode() { + if (handleMacroRecording() || handleEsc()) { return true; }; + + var keys = vim.inputState.keyBuffer = vim.inputState.keyBuffer + key; + if (/^[1-9]\d*$/.test(keys)) { return true; } + + var keysMatcher = /^(\d*)(.*)$/.exec(keys); + if (!keysMatcher) { clearInputState(cm); return false; } + var context = vim.visualMode ? 'visual' : + 'normal'; + var match = commandDispatcher.matchCommand(keysMatcher[2] || keysMatcher[1], defaultKeymap, vim.inputState, context); + if (match.type == 'none') { clearInputState(cm); return false; } + else if (match.type == 'partial') { return true; } + + vim.inputState.keyBuffer = ''; + var keysMatcher = /^(\d*)(.*)$/.exec(keys); + if (keysMatcher[1] && keysMatcher[1] != '0') { + vim.inputState.pushRepeatDigit(keysMatcher[1]); + } + return match.command; + } + + var command; + if (vim.insertMode) { command = handleKeyInsertMode(); } + else { command = handleKeyNonInsertMode(); } + if (command === false) { + return undefined; + } else if (command === true) { + // TODO: Look into using CodeMirror's multi-key handling. + // Return no-op since we are caching the key. Counts as handled, but + // don't want act on it just yet. + return function() {}; } else { - if (macroModeState.isRecording) { - logKey(macroModeState, key); - } - commandDispatcher.processCommand(cm, vim, command); + return function() { + return cm.operation(function() { + cm.curOp.isVimOp = true; + try { + if (command.type == 'keyToKey') { + doKeyToKey(command.toKeys); + } else { + commandDispatcher.processCommand(cm, vim, command); + } + } catch (e) { + // clear VIM state in case it's in a bad state. + cm.state.vim = undefined; + maybeInitVimState(cm); + if (!CodeMirror.Vim.suppressErrorLogging) { + console['log'](e); + } + throw e; + } + return true; + }); + }; } }, handleEx: function(cm, input) { exCommandDispatcher.processCommand(cm, input); - } + }, + + defineMotion: defineMotion, + defineAction: defineAction, + defineOperator: defineOperator, + mapCommand: mapCommand, + _mapCommand: _mapCommand, + + exitVisualMode: exitVisualMode, + exitInsertMode: exitInsertMode }; // Represents the current input state. @@ -754,27 +821,37 @@ return repeat; }; + function clearInputState(cm, reason) { + cm.state.vim.inputState = new InputState(); + CodeMirror.signal(cm, 'vim-command-done', reason); + } + /* * Register stores information about copy and paste registers. Besides * text, a register must store whether it is linewise (i.e., when it is * pasted, should it insert itself into a new line, or should the text be * inserted at the cursor position.) */ - function Register(text, linewise) { + function Register(text, linewise, blockwise) { this.clear(); this.keyBuffer = [text || '']; this.insertModeChanges = []; + this.searchQueries = []; this.linewise = !!linewise; + this.blockwise = !!blockwise; } Register.prototype = { - setText: function(text, linewise) { + setText: function(text, linewise, blockwise) { this.keyBuffer = [text || '']; this.linewise = !!linewise; + this.blockwise = !!blockwise; }, pushText: function(text, linewise) { // if this register has ever been set to linewise, use linewise. - if (linewise || this.linewise) { - this.keyBuffer.push('\n'); + if (linewise) { + if (!this.linewise) { + this.keyBuffer.push('\n'); + } this.linewise = true; } this.keyBuffer.push(text); @@ -782,9 +859,13 @@ pushInsertModeChanges: function(changes) { this.insertModeChanges.push(createInsertModeChanges(changes)); }, + pushSearchQuery: function(query) { + this.searchQueries.push(query); + }, clear: function() { this.keyBuffer = []; this.insertModeChanges = []; + this.searchQueries = []; this.linewise = false; }, toString: function() { @@ -803,9 +884,12 @@ function RegisterController(registers) { this.registers = registers; this.unnamedRegister = registers['"'] = new Register(); + registers['.'] = new Register(); + registers[':'] = new Register(); + registers['/'] = new Register(); } RegisterController.prototype = { - pushText: function(registerName, operator, text, linewise) { + pushText: function(registerName, operator, text, linewise, blockwise) { if (linewise && text.charAt(0) == '\n') { text = text.slice(1) + '\n'; } @@ -822,7 +906,7 @@ switch (operator) { case 'yank': // The 0 register contains the text from the most recent yank. - this.registers['0'] = new Register(text, linewise); + this.registers['0'] = new Register(text, linewise, blockwise); break; case 'delete': case 'change': @@ -838,21 +922,20 @@ break; } // Make sure the unnamed register is set to what just happened - this.unnamedRegister.setText(text, linewise); + this.unnamedRegister.setText(text, linewise, blockwise); return; } // If we've gotten to this point, we've actually specified a register var append = isUpperCase(registerName); if (append) { - register.append(text, linewise); - // The unnamed register always has the same value as the last used - // register. - this.unnamedRegister.append(text, linewise); + register.pushText(text, linewise); } else { - register.setText(text, linewise); - this.unnamedRegister.setText(text, linewise); + register.setText(text, linewise, blockwise); } + // The unnamed register always has the same value as the last used + // register. + this.unnamedRegister.setText(register.toString(), linewise); }, // Gets the register named @name. If one of @name doesn't already exist, // create it. If @name is invalid, return the unnamedRegister. @@ -875,85 +958,65 @@ } } }; - + function HistoryController() { + this.historyBuffer = []; + this.iterator; + this.initialPrefix = null; + } + HistoryController.prototype = { + // the input argument here acts a user entered prefix for a small time + // until we start autocompletion in which case it is the autocompleted. + nextMatch: function (input, up) { + var historyBuffer = this.historyBuffer; + var dir = up ? -1 : 1; + if (this.initialPrefix === null) this.initialPrefix = input; + for (var i = this.iterator + dir; up ? i >= 0 : i < historyBuffer.length; i+= dir) { + var element = historyBuffer[i]; + for (var j = 0; j <= element.length; j++) { + if (this.initialPrefix == element.substring(0, j)) { + this.iterator = i; + return element; + } + } + } + // should return the user input in case we reach the end of buffer. + if (i >= historyBuffer.length) { + this.iterator = historyBuffer.length; + return this.initialPrefix; + } + // return the last autocompleted query or exCommand as it is. + if (i < 0 ) return input; + }, + pushInput: function(input) { + var index = this.historyBuffer.indexOf(input); + if (index > -1) this.historyBuffer.splice(index, 1); + if (input.length) this.historyBuffer.push(input); + }, + reset: function() { + this.initialPrefix = null; + this.iterator = this.historyBuffer.length; + } + }; var commandDispatcher = { - matchCommand: function(key, keyMap, vim) { - var inputState = vim.inputState; - var keys = inputState.keyBuffer.concat(key); - var matchedCommands = []; - var selectedCharacter; - for (var i = 0; i < keyMap.length; i++) { - var command = keyMap[i]; - if (matchKeysPartial(keys, command.keys)) { - if (inputState.operator && command.type == 'action') { - // Ignore matched action commands after an operator. Operators - // only operate on motions. This check is really for text - // objects since aW, a[ etcs conflicts with a. - continue; - } - // Match commands that take as an argument. - if (command.keys[keys.length - 1] == 'character') { - selectedCharacter = keys[keys.length - 1]; - if (selectedCharacter.length>1){ - switch(selectedCharacter){ - case '': - selectedCharacter='\n'; - break; - case '': - selectedCharacter=' '; - break; - default: - continue; - } - } - } - // Add the command to the list of matched commands. Choose the best - // command later. - matchedCommands.push(command); - } + matchCommand: function(keys, keyMap, inputState, context) { + var matches = commandMatches(keys, keyMap, context, inputState); + if (!matches.full && !matches.partial) { + return {type: 'none'}; + } else if (!matches.full && matches.partial) { + return {type: 'partial'}; } - // Returns the command if it is a full match, or null if not. - function getFullyMatchedCommandOrNull(command) { - if (keys.length < command.keys.length) { - // Matches part of a multi-key command. Buffer and wait for next - // stroke. - inputState.keyBuffer.push(key); - return null; - } else { - if (command.keys[keys.length - 1] == 'character') { - inputState.selectedCharacter = selectedCharacter; - } - // Clear the buffer since a full match was found. - inputState.keyBuffer = []; - return command; + var bestMatch; + for (var i = 0; i < matches.full.length; i++) { + var match = matches.full[i]; + if (!bestMatch) { + bestMatch = match; } } - - if (!matchedCommands.length) { - // Clear the buffer since there were no matches. - inputState.keyBuffer = []; - return null; - } else if (matchedCommands.length == 1) { - return getFullyMatchedCommandOrNull(matchedCommands[0]); - } else { - // Find the best match in the list of matchedCommands. - var context = vim.visualMode ? 'visual' : 'normal'; - var bestMatch; // Default to first in the list. - for (var i = 0; i < matchedCommands.length; i++) { - var current = matchedCommands[i]; - if (current.context == context) { - bestMatch = current; - break; - } else if (!bestMatch && !current.context) { - // Only set an imperfect match to best match if no best match is - // set and the imperfect match is not restricted to another - // context. - bestMatch = current; - } - } - return getFullyMatchedCommandOrNull(bestMatch); + if (bestMatch.keys.slice(-11) == '') { + inputState.selectedCharacter = lastChar(keys); } + return {type: 'full', command: bestMatch}; }, processCommand: function(cm, vim, command) { vim.inputState.repeatOverride = command.repeatOverride; @@ -972,10 +1035,12 @@ break; case 'search': this.processSearch(cm, vim, command); + clearInputState(cm); break; case 'ex': case 'keyToEx': this.processEx(cm, vim, command); + clearInputState(cm); break; default: break; @@ -998,7 +1063,7 @@ return; } else { // 2 different operators in a row doesn't make sense. - vim.inputState = new InputState(); + clearInputState(cm); } } inputState.operator = command.operator; @@ -1043,7 +1108,7 @@ actionArgs.repeat = repeat || 1; actionArgs.repeatIsExplicit = repeatIsExplicit; actionArgs.registerName = inputState.registerName; - vim.inputState = new InputState(); + clearInputState(cm); vim.lastMotion = null; if (command.isEdit) { this.recordLastEdit(vim, inputState, command); @@ -1056,11 +1121,14 @@ return; } var forward = command.searchArgs.forward; + var wholeWordOnly = command.searchArgs.wholeWordOnly; getSearchState(cm).setReversed(!forward); var promptPrefix = (forward) ? '/' : '?'; var originalQuery = getSearchState(cm).getQuery(); var originalScrollPos = cm.getScrollInfo(); function handleQuery(query, ignoreCase, smartCase) { + vimGlobalState.searchHistoryController.pushInput(query); + vimGlobalState.searchHistoryController.reset(); try { updateSearchQuery(cm, query, ignoreCase, smartCase); } catch (e) { @@ -1076,8 +1144,21 @@ function onPromptClose(query) { cm.scrollTo(originalScrollPos.left, originalScrollPos.top); handleQuery(query, true /** ignoreCase */, true /** smartCase */); + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isRecording) { + logSearchQuery(macroModeState, query); + } } - function onPromptKeyUp(_e, query) { + function onPromptKeyUp(e, query, close) { + var keyName = CodeMirror.keyName(e), up; + if (keyName == 'Up' || keyName == 'Down') { + up = keyName == 'Up' ? true : false; + query = vimGlobalState.searchHistoryController.nextMatch(query, up) || ''; + close(query); + } else { + if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift') + vimGlobalState.searchHistoryController.reset(); + } var parsedQuery; try { parsedQuery = updateSearchQuery(cm, query, @@ -1092,13 +1173,14 @@ cm.scrollTo(originalScrollPos.left, originalScrollPos.top); } } - function onPromptKeyDown(e, _query, close) { + function onPromptKeyDown(e, query, close) { var keyName = CodeMirror.keyName(e); if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') { + vimGlobalState.searchHistoryController.pushInput(query); + vimGlobalState.searchHistoryController.reset(); updateSearchQuery(cm, originalQuery); clearSearchHighlight(cm); cm.scrollTo(originalScrollPos.left, originalScrollPos.top); - CodeMirror.e_stop(e); close(); cm.focus(); @@ -1106,13 +1188,19 @@ } switch (command.searchArgs.querySrc) { case 'prompt': - showPrompt(cm, { - onClose: onPromptClose, - prefix: promptPrefix, - desc: searchPromptDesc, - onKeyUp: onPromptKeyUp, - onKeyDown: onPromptKeyDown - }); + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { + var query = macroModeState.replaySearchQueries.shift(); + handleQuery(query, true /** ignoreCase */, false /** smartCase */); + } else { + showPrompt(cm, { + onClose: onPromptClose, + prefix: promptPrefix, + desc: searchPromptDesc, + onKeyUp: onPromptKeyUp, + onKeyDown: onPromptKeyDown + }); + } break; case 'wordUnderCursor': var word = expandWordUnderCursor(cm, false /** inclusive */, @@ -1130,8 +1218,8 @@ } var query = cm.getLine(word.start.line).substring(word.start.ch, word.end.ch); - if (isKeyword) { - query = '\\b' + query + '\\b'; + if (isKeyword && wholeWordOnly) { + query = '\\b' + query + '\\b'; } else { query = escapeRegex(query); } @@ -1150,15 +1238,27 @@ function onPromptClose(input) { // Give the prompt some time to close so that if processCommand shows // an error, the elements don't overlap. + vimGlobalState.exCommandHistoryController.pushInput(input); + vimGlobalState.exCommandHistoryController.reset(); exCommandDispatcher.processCommand(cm, input); } - function onPromptKeyDown(e, _input, close) { - var keyName = CodeMirror.keyName(e); + function onPromptKeyDown(e, input, close) { + var keyName = CodeMirror.keyName(e), up; if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') { + vimGlobalState.exCommandHistoryController.pushInput(input); + vimGlobalState.exCommandHistoryController.reset(); CodeMirror.e_stop(e); close(); cm.focus(); } + if (keyName == 'Up' || keyName == 'Down') { + up = keyName == 'Up' ? true : false; + input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || ''; + close(input); + } else { + if ( keyName != 'Left' && keyName != 'Right' && keyName != 'Ctrl' && keyName != 'Alt' && keyName != 'Shift') + vimGlobalState.exCommandHistoryController.reset(); + } } if (command.type == 'keyToEx') { // Handle user defined Ex to Ex mappings @@ -1182,13 +1282,13 @@ var operator = inputState.operator; var operatorArgs = inputState.operatorArgs || {}; var registerName = inputState.registerName; - var selectionEnd = copyCursor(cm.getCursor('head')); - var selectionStart = copyCursor(cm.getCursor('anchor')); - // The difference between cur and selection cursors are that cur is - // being operated on and ignores that there is a selection. - var curStart = copyCursor(selectionEnd); - var curOriginal = copyCursor(curStart); - var curEnd; + var sel = vim.sel; + // TODO: Make sure cm and vim selections are identical outside visual mode. + var origHead = copyCursor(vim.visualMode ? sel.head: cm.getCursor('head')); + var origAnchor = copyCursor(vim.visualMode ? sel.anchor : cm.getCursor('anchor')); + var oldHead = copyCursor(origHead); + var oldAnchor = copyCursor(origAnchor); + var newHead, newAnchor; var repeat; if (operator) { this.recordLastEdit(vim, inputState); @@ -1213,9 +1313,9 @@ inputState.selectedCharacter; } motionArgs.repeat = repeat; - vim.inputState = new InputState(); + clearInputState(cm); if (motion) { - var motionResult = motions[motion](cm, motionArgs, vim); + var motionResult = motions[motion](cm, origHead, motionArgs, vim); vim.lastMotion = motions[motion]; if (!motionResult) { return; @@ -1228,100 +1328,140 @@ recordJumpPosition(cm, cachedCursor, motionResult); delete jumpList.cachedCursor; } else { - recordJumpPosition(cm, curOriginal, motionResult); + recordJumpPosition(cm, origHead, motionResult); } } if (motionResult instanceof Array) { - curStart = motionResult[0]; - curEnd = motionResult[1]; + newAnchor = motionResult[0]; + newHead = motionResult[1]; } else { - curEnd = motionResult; + newHead = motionResult; } // TODO: Handle null returns from motion commands better. - if (!curEnd) { - curEnd = Pos(curStart.line, curStart.ch); + if (!newHead) { + newHead = copyCursor(origHead); } if (vim.visualMode) { - // Check if the selection crossed over itself. Will need to shift - // the start point if that happened. - if (cursorIsBefore(selectionStart, selectionEnd) && - (cursorEqual(selectionStart, curEnd) || - cursorIsBefore(curEnd, selectionStart))) { - // The end of the selection has moved from after the start to - // before the start. We will shift the start right by 1. - selectionStart.ch += 1; - } else if (cursorIsBefore(selectionEnd, selectionStart) && - (cursorEqual(selectionStart, curEnd) || - cursorIsBefore(selectionStart, curEnd))) { - // The opposite happened. We will shift the start left by 1. - selectionStart.ch -= 1; + if (!(vim.visualBlock && newHead.ch === Infinity)) { + newHead = clipCursorToContent(cm, newHead, vim.visualBlock); } - selectionEnd = curEnd; - selectionStart = (motionResult instanceof Array) ? curStart : selectionStart; - if (vim.visualLine) { - if (cursorIsBefore(selectionStart, selectionEnd)) { - selectionStart.ch = 0; - - var lastLine = cm.lastLine(); - if (selectionEnd.line > lastLine) { - selectionEnd.line = lastLine; - } - selectionEnd.ch = lineLength(cm, selectionEnd.line); - } else { - selectionEnd.ch = 0; - selectionStart.ch = lineLength(cm, selectionStart.line); - } + if (newAnchor) { + newAnchor = clipCursorToContent(cm, newAnchor, true); } - cm.setSelection(selectionStart, selectionEnd); + newAnchor = newAnchor || oldAnchor; + sel.anchor = newAnchor; + sel.head = newHead; + updateCmSelection(cm); updateMark(cm, vim, '<', - cursorIsBefore(selectionStart, selectionEnd) ? selectionStart - : selectionEnd); + cursorIsBefore(newAnchor, newHead) ? newAnchor + : newHead); updateMark(cm, vim, '>', - cursorIsBefore(selectionStart, selectionEnd) ? selectionEnd - : selectionStart); + cursorIsBefore(newAnchor, newHead) ? newHead + : newAnchor); } else if (!operator) { - curEnd = clipCursorToContent(cm, curEnd); - cm.setCursor(curEnd.line, curEnd.ch); + newHead = clipCursorToContent(cm, newHead); + cm.setCursor(newHead.line, newHead.ch); } } - if (operator) { - var inverted = false; - vim.lastMotion = null; - operatorArgs.repeat = repeat; // Indent in visual mode needs this. + if (operatorArgs.lastSel) { + // Replaying a visual mode operation + newAnchor = oldAnchor; + var lastSel = operatorArgs.lastSel; + var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line); + var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch); + if (lastSel.visualLine) { + // Linewise Visual mode: The same number of lines. + newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch); + } else if (lastSel.visualBlock) { + // Blockwise Visual mode: The same number of lines and columns. + newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset); + } else if (lastSel.head.line == lastSel.anchor.line) { + // Normal Visual mode within one line: The same number of characters. + newHead = Pos(oldAnchor.line, oldAnchor.ch + chOffset); + } else { + // Normal Visual mode with several lines: The same number of lines, in the + // last line the same number of characters as in the last line the last time. + newHead = Pos(oldAnchor.line + lineOffset, oldAnchor.ch); + } + vim.visualMode = true; + vim.visualLine = lastSel.visualLine; + vim.visualBlock = lastSel.visualBlock; + sel = vim.sel = { + anchor: newAnchor, + head: newHead + }; + updateCmSelection(cm); + } else if (vim.visualMode) { + operatorArgs.lastSel = { + anchor: copyCursor(sel.anchor), + head: copyCursor(sel.head), + visualBlock: vim.visualBlock, + visualLine: vim.visualLine + }; + } + var curStart, curEnd, linewise, mode; + var cmSel; if (vim.visualMode) { - curStart = selectionStart; - curEnd = selectionEnd; - motionArgs.inclusive = true; - } - // Swap start and end if motion was backward. - if (cursorIsBefore(curEnd, curStart)) { - var tmp = curStart; - curStart = curEnd; - curEnd = tmp; - inverted = true; - } - if (motionArgs.inclusive && !(vim.visualMode && inverted)) { - // Move the selection end one to the right to include the last - // character. - curEnd.ch++; - } - var linewise = motionArgs.linewise || - (vim.visualMode && vim.visualLine); - if (linewise) { - // Expand selection to entire line. - expandSelectionToLine(cm, curStart, curEnd); - } else if (motionArgs.forward) { - // Clip to trailing newlines only if the motion goes forward. - clipToLine(cm, curStart, curEnd); + // Init visual op + curStart = cursorMin(sel.head, sel.anchor); + curEnd = cursorMax(sel.head, sel.anchor); + linewise = vim.visualLine || operatorArgs.linewise; + mode = vim.visualBlock ? 'block' : + linewise ? 'line' : + 'char'; + cmSel = makeCmSelection(cm, { + anchor: curStart, + head: curEnd + }, mode); + if (linewise) { + var ranges = cmSel.ranges; + if (mode == 'block') { + // Linewise operators in visual block mode extend to end of line + for (var i = 0; i < ranges.length; i++) { + ranges[i].head.ch = lineLength(cm, ranges[i].head.line); + } + } else if (mode == 'line') { + ranges[0].head = Pos(ranges[0].head.line + 1, 0); + } + } + } else { + // Init motion op + curStart = copyCursor(newAnchor || oldAnchor); + curEnd = copyCursor(newHead || oldHead); + if (cursorIsBefore(curEnd, curStart)) { + var tmp = curStart; + curStart = curEnd; + curEnd = tmp; + } + linewise = motionArgs.linewise || operatorArgs.linewise; + if (linewise) { + // Expand selection to entire line. + expandSelectionToLine(cm, curStart, curEnd); + } else if (motionArgs.forward) { + // Clip to trailing newlines only if the motion goes forward. + clipToLine(cm, curStart, curEnd); + } + mode = 'char'; + var exclusive = !motionArgs.inclusive || linewise; + cmSel = makeCmSelection(cm, { + anchor: curStart, + head: curEnd + }, mode, exclusive); } + cm.setSelections(cmSel.ranges, cmSel.primary); + vim.lastMotion = null; + operatorArgs.repeat = repeat; // For indent in visual mode. operatorArgs.registerName = registerName; // Keep track of linewise as it affects how paste and change behave. operatorArgs.linewise = linewise; - operators[operator](cm, operatorArgs, vim, curStart, - curEnd, curOriginal); + var operatorMoveTo = operators[operator]( + cm, operatorArgs, cmSel.ranges, oldAnchor, newHead); if (vim.visualMode) { - exitVisualMode(cm); + exitVisualMode(cm, operatorMoveTo != null); + } + if (operatorMoveTo) { + cm.setCursor(operatorMoveTo); } } }, @@ -1341,7 +1481,7 @@ */ // All of the functions below return Cursor objects. var motions = { - moveToTopLine: function(cm, motionArgs) { + moveToTopLine: function(cm, _head, motionArgs) { var line = getUserVisibleLines(cm).top + motionArgs.repeat -1; return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); }, @@ -1350,17 +1490,17 @@ var line = Math.floor((range.top + range.bottom) * 0.5); return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); }, - moveToBottomLine: function(cm, motionArgs) { + moveToBottomLine: function(cm, _head, motionArgs) { var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1; return Pos(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); }, - expandToLine: function(cm, motionArgs) { + expandToLine: function(_cm, head, motionArgs) { // Expands forward to end of line, and then to next line if repeat is // >1. Does not handle backward motion! - var cur = cm.getCursor(); + var cur = head; return Pos(cur.line + motionArgs.repeat - 1, Infinity); }, - findNext: function(cm, motionArgs) { + findNext: function(cm, _head, motionArgs) { var state = getSearchState(cm); var query = state.getQuery(); if (!query) { @@ -1372,25 +1512,27 @@ highlightSearchMatches(cm, query); return findNext(cm, prev/** prev */, query, motionArgs.repeat); }, - goToMark: function(_cm, motionArgs, vim) { + goToMark: function(cm, _head, motionArgs, vim) { var mark = vim.marks[motionArgs.selectedCharacter]; if (mark) { - return mark.find(); + var pos = mark.find(); + return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos; } return null; }, - moveToOtherHighlightedEnd: function(cm) { - var curEnd = copyCursor(cm.getCursor('head')); - var curStart = copyCursor(cm.getCursor('anchor')); - if (cursorIsBefore(curStart, curEnd)) { - curEnd.ch += 1; - } else if (cursorIsBefore(curEnd, curStart)) { - curStart.ch -= 1; + moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim) { + if (vim.visualBlock && motionArgs.sameLine) { + var sel = vim.sel; + return [ + clipCursorToContent(cm, Pos(sel.anchor.line, sel.head.ch)), + clipCursorToContent(cm, Pos(sel.head.line, sel.anchor.ch)) + ]; + } else { + return ([vim.sel.head, vim.sel.anchor]); } - return ([curEnd,curStart]); }, - jumpToMark: function(cm, motionArgs, vim) { - var best = cm.getCursor(); + jumpToMark: function(cm, head, motionArgs, vim) { + var best = head; for (var i = 0; i < motionArgs.repeat; i++) { var cursor = best; for (var key in vim.marks) { @@ -1410,8 +1552,8 @@ var equal = cursorEqual(cursor, best); var between = (motionArgs.forward) ? - cusrorIsBetween(cursor, mark, best) : - cusrorIsBetween(best, mark, cursor); + cursorIsBetween(cursor, mark, best) : + cursorIsBetween(best, mark, cursor); if (equal || between) { best = mark; @@ -1427,14 +1569,14 @@ } return best; }, - moveByCharacters: function(cm, motionArgs) { - var cur = cm.getCursor(); + moveByCharacters: function(_cm, head, motionArgs) { + var cur = head; var repeat = motionArgs.repeat; var ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat; return Pos(cur.line, ch); }, - moveByLines: function(cm, motionArgs, vim) { - var cur = cm.getCursor(); + moveByLines: function(cm, head, motionArgs, vim) { + var cur = head; var endCh = cur.ch; // Depending what our last motion was, we may want to do different // things. If our last motion was moving vertically, we want to @@ -1469,8 +1611,8 @@ vim.lastHSPos = cm.charCoords(Pos(line, endCh),'div').left; return Pos(line, endCh); }, - moveByDisplayLines: function(cm, motionArgs, vim) { - var cur = cm.getCursor(); + moveByDisplayLines: function(cm, head, motionArgs, vim) { + var cur = head; switch (vim.lastMotion) { case this.moveByDisplayLines: case this.moveByScroll: @@ -1497,43 +1639,28 @@ vim.lastHPos = res.ch; return res; }, - moveByPage: function(cm, motionArgs) { + moveByPage: function(cm, head, motionArgs) { // CodeMirror only exposes functions that move the cursor page down, so // doing this bad hack to move the cursor and move it back. evalInput // will move the cursor to where it should be in the end. - var curStart = cm.getCursor(); + var curStart = head; var repeat = motionArgs.repeat; - cm.moveV((motionArgs.forward ? repeat : -repeat), 'page'); - var curEnd = cm.getCursor(); - cm.setCursor(curStart); - return curEnd; + return cm.findPosV(curStart, (motionArgs.forward ? repeat : -repeat), 'page'); }, - moveByParagraph: function(cm, motionArgs) { - var line = cm.getCursor().line; - var repeat = motionArgs.repeat; - var inc = motionArgs.forward ? 1 : -1; - for (var i = 0; i < repeat; i++) { - if ((!motionArgs.forward && line === cm.firstLine() ) || - (motionArgs.forward && line == cm.lastLine())) { - break; - } - line += inc; - while (line !== cm.firstLine() && line != cm.lastLine() && cm.getLine(line)) { - line += inc; - } - } - return Pos(line, 0); + moveByParagraph: function(cm, head, motionArgs) { + var dir = motionArgs.forward ? 1 : -1; + return findParagraph(cm, head, motionArgs.repeat, dir); }, - moveByScroll: function(cm, motionArgs, vim) { + moveByScroll: function(cm, head, motionArgs, vim) { var scrollbox = cm.getScrollInfo(); var curEnd = null; var repeat = motionArgs.repeat; if (!repeat) { repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight()); } - var orig = cm.charCoords(cm.getCursor(), 'local'); + var orig = cm.charCoords(head, 'local'); motionArgs.repeat = repeat; - var curEnd = motions.moveByDisplayLines(cm, motionArgs, vim); + var curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim); if (!curEnd) { return null; } @@ -1541,11 +1668,11 @@ cm.scrollTo(null, scrollbox.top + dest.top - orig.top); return curEnd; }, - moveByWords: function(cm, motionArgs) { - return moveToWord(cm, motionArgs.repeat, !!motionArgs.forward, + moveByWords: function(cm, head, motionArgs) { + return moveToWord(cm, head, motionArgs.repeat, !!motionArgs.forward, !!motionArgs.wordEnd, !!motionArgs.bigWord); }, - moveTillCharacter: function(cm, motionArgs) { + moveTillCharacter: function(cm, _head, motionArgs) { var repeat = motionArgs.repeat; var curEnd = moveToCharacter(cm, repeat, motionArgs.forward, motionArgs.selectedCharacter); @@ -1555,26 +1682,26 @@ curEnd.ch += increment; return curEnd; }, - moveToCharacter: function(cm, motionArgs) { + moveToCharacter: function(cm, head, motionArgs) { var repeat = motionArgs.repeat; recordLastCharacterSearch(0, motionArgs); return moveToCharacter(cm, repeat, motionArgs.forward, - motionArgs.selectedCharacter) || cm.getCursor(); + motionArgs.selectedCharacter) || head; }, - moveToSymbol: function(cm, motionArgs) { + moveToSymbol: function(cm, head, motionArgs) { var repeat = motionArgs.repeat; return findSymbol(cm, repeat, motionArgs.forward, - motionArgs.selectedCharacter) || cm.getCursor(); + motionArgs.selectedCharacter) || head; }, - moveToColumn: function(cm, motionArgs, vim) { + moveToColumn: function(cm, head, motionArgs, vim) { var repeat = motionArgs.repeat; // repeat is equivalent to which column we want to move to! vim.lastHPos = repeat - 1; - vim.lastHSPos = cm.charCoords(cm.getCursor(),'div').left; + vim.lastHSPos = cm.charCoords(head,'div').left; return moveToColumn(cm, repeat); }, - moveToEol: function(cm, motionArgs, vim) { - var cur = cm.getCursor(); + moveToEol: function(cm, head, motionArgs, vim) { + var cur = head; vim.lastHPos = Infinity; var retval= Pos(cur.line + motionArgs.repeat - 1, Infinity); var end=cm.clipPos(retval); @@ -1582,42 +1709,39 @@ vim.lastHSPos = cm.charCoords(end,'div').left; return retval; }, - moveToFirstNonWhiteSpaceCharacter: function(cm) { + moveToFirstNonWhiteSpaceCharacter: function(cm, head) { // Go to the start of the line where the text begins, or the end for // whitespace-only lines - var cursor = cm.getCursor(); + var cursor = head; return Pos(cursor.line, findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line))); }, - moveToMatchedSymbol: function(cm) { - var cursor = cm.getCursor(); + moveToMatchedSymbol: function(cm, head) { + var cursor = head; var line = cursor.line; var ch = cursor.ch; var lineText = cm.getLine(line); var symbol; - var startContext = cm.getTokenAt(cursor).type; - var startCtxLevel = getContextLevel(startContext); do { symbol = lineText.charAt(ch++); if (symbol && isMatchableSymbol(symbol)) { - var endContext = cm.getTokenAt(Pos(line, ch)).type; - var endCtxLevel = getContextLevel(endContext); - if (startCtxLevel >= endCtxLevel) { + var style = cm.getTokenTypeAt(Pos(line, ch)); + if (style !== "string" && style !== "comment") { break; } } } while (symbol); if (symbol) { - return findMatchedSymbol(cm, Pos(line, ch-1), symbol); + var matched = cm.findMatchingBracket(Pos(line, ch)); + return matched.to; } else { return cursor; } }, - moveToStartOfLine: function(cm) { - var cursor = cm.getCursor(); - return Pos(cursor.line, 0); + moveToStartOfLine: function(_cm, head) { + return Pos(head.line, 0); }, - moveToLineOrEdgeOfDocument: function(cm, motionArgs) { + moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) { var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine(); if (motionArgs.repeatIsExplicit) { lineNum = motionArgs.repeat - cm.getOption('firstLineNumber'); @@ -1625,7 +1749,7 @@ return Pos(lineNum, findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum))); }, - textObjectManipulation: function(cm, motionArgs) { + textObjectManipulation: function(cm, head, motionArgs, vim) { // TODO: lots of possible exceptions that can be thrown here. Try da( // outside of a () block. @@ -1637,6 +1761,13 @@ var selfPaired = {'\'': true, '"': true}; var character = motionArgs.selectedCharacter; + // 'b' refers to '()' block. + // 'B' refers to '{}' block. + if (character == 'b') { + character = '('; + } else if (character == 'B') { + character = '{'; + } // Inclusive is the difference between a and i // TODO: Instead of using the additional text object map to perform text @@ -1647,24 +1778,38 @@ var tmp; if (mirroredPairs[character]) { - tmp = selectCompanionObject(cm, mirroredPairs[character], inclusive); + tmp = selectCompanionObject(cm, head, character, inclusive); } else if (selfPaired[character]) { - tmp = findBeginningAndEnd(cm, character, inclusive); + tmp = findBeginningAndEnd(cm, head, character, inclusive); } else if (character === 'W') { tmp = expandWordUnderCursor(cm, inclusive, true /** forward */, true /** bigWord */); } else if (character === 'w') { tmp = expandWordUnderCursor(cm, inclusive, true /** forward */, false /** bigWord */); + } else if (character === 'p') { + tmp = findParagraph(cm, head, motionArgs.repeat, 0, inclusive); + motionArgs.linewise = true; + if (vim.visualMode) { + if (!vim.visualLine) { vim.visualLine = true; } + } else { + var operatorArgs = vim.inputState.operatorArgs; + if (operatorArgs) { operatorArgs.linewise = true; } + tmp.end.line--; + } } else { // No text object defined for this, don't move. return null; } - return [tmp.start, tmp.end]; + if (!cm.state.vim.visualMode) { + return [tmp.start, tmp.end]; + } else { + return expandSelection(cm, tmp.start, tmp.end); + } }, - repeatLastCharacterSearch: function(cm, motionArgs) { + repeatLastCharacterSearch: function(cm, head, motionArgs) { var lastSearch = vimGlobalState.lastChararacterSearch; var repeat = motionArgs.repeat; var forward = motionArgs.forward === lastSearch.forward; @@ -1674,65 +1819,111 @@ var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter); if (!curEnd) { cm.moveH(increment, 'char'); - return cm.getCursor(); + return head; } curEnd.ch += increment; return curEnd; } }; + function defineMotion(name, fn) { + motions[name] = fn; + } + + function fillArray(val, times) { + var arr = []; + for (var i = 0; i < times; i++) { + arr.push(val); + } + return arr; + } + /** + * An operator acts on a text selection. It receives the list of selections + * as input. The corresponding CodeMirror selection is guaranteed to + * match the input selection. + */ var operators = { - change: function(cm, operatorArgs, _vim, curStart, curEnd) { - vimGlobalState.registerController.pushText( - operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd), - operatorArgs.linewise); - if (operatorArgs.linewise) { - // Push the next line back down, if there is a next line. - var replacement = curEnd.line > cm.lastLine() ? '' : '\n'; - cm.replaceRange(replacement, curStart, curEnd); - cm.indentLine(curStart.line, 'smart'); - // null ch so setCursor moves to end of line. - curStart.ch = null; - } else { - // Exclude trailing whitespace if the range is not all whitespace. - var text = cm.getRange(curStart, curEnd); + change: function(cm, args, ranges) { + var finalHead, text; + var vim = cm.state.vim; + vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock = vim.visualBlock; + if (!vim.visualMode) { + var anchor = ranges[0].anchor, + head = ranges[0].head; + text = cm.getRange(anchor, head); if (!isWhiteSpaceString(text)) { + // Exclude trailing whitespace if the range is not all whitespace. var match = (/\s+$/).exec(text); if (match) { - curEnd = offsetCursor(curEnd, 0, - match[0].length); + head = offsetCursor(head, 0, - match[0].length); + text = text.slice(0, - match[0].length); } } - cm.replaceRange('', curStart, curEnd); - } - actions.enterInsertMode(cm, {}, cm.state.vim); - cm.setCursor(curStart); - }, - // delete is a javascript keyword. - 'delete': function(cm, operatorArgs, _vim, curStart, curEnd) { - // If the ending line is past the last line, inclusive, instead of - // including the trailing \n, include the \n before the starting line - if (operatorArgs.linewise && - curEnd.line > cm.lastLine() && curStart.line > cm.firstLine()) { - curStart.line--; - curStart.ch = lineLength(cm, curStart.line); + var wasLastLine = head.line - 1 == cm.lastLine(); + cm.replaceRange('', anchor, head); + if (args.linewise && !wasLastLine) { + // Push the next line back down, if there is a next line. + CodeMirror.commands.newlineAndIndent(cm); + // null ch so setCursor moves to end of line. + anchor.ch = null; + } + finalHead = anchor; + } else { + text = cm.getSelection(); + var replacement = fillArray('', ranges.length); + cm.replaceSelections(replacement); + finalHead = cursorMin(ranges[0].head, ranges[0].anchor); } vimGlobalState.registerController.pushText( - operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd), - operatorArgs.linewise); - cm.replaceRange('', curStart, curEnd); - if (operatorArgs.linewise) { - cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); - } else { - cm.setCursor(curStart); - } + args.registerName, 'change', text, + args.linewise, ranges.length > 1); + actions.enterInsertMode(cm, {head: finalHead}, cm.state.vim); }, - indent: function(cm, operatorArgs, vim, curStart, curEnd) { - var startLine = curStart.line; - var endLine = curEnd.line; + // delete is a javascript keyword. + 'delete': function(cm, args, ranges) { + var finalHead, text; + var vim = cm.state.vim; + if (!vim.visualBlock) { + var anchor = ranges[0].anchor, + head = ranges[0].head; + if (args.linewise && + head.line != cm.firstLine() && + anchor.line == cm.lastLine() && + anchor.line == head.line - 1) { + // Special case for dd on last line (and first line). + if (anchor.line == cm.firstLine()) { + anchor.ch = 0; + } else { + anchor = Pos(anchor.line - 1, lineLength(cm, anchor.line - 1)); + } + } + text = cm.getRange(anchor, head); + cm.replaceRange('', anchor, head); + finalHead = anchor; + if (args.linewise) { + finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor); + } + } else { + text = cm.getSelection(); + var replacement = fillArray('', ranges.length); + cm.replaceSelections(replacement); + finalHead = ranges[0].anchor; + } + vimGlobalState.registerController.pushText( + args.registerName, 'delete', text, + args.linewise, vim.visualBlock); + return clipCursorToContent(cm, finalHead); + }, + indent: function(cm, args, ranges) { + var vim = cm.state.vim; + var startLine = ranges[0].anchor.line; + var endLine = vim.visualBlock ? + ranges[ranges.length - 1].anchor.line : + ranges[0].head.line; // In visual mode, n> shifts the selection right n times, instead of // shifting n lines right once. - var repeat = (vim.visualMode) ? operatorArgs.repeat : 1; - if (operatorArgs.linewise) { + var repeat = (vim.visualMode) ? args.repeat : 1; + if (args.linewise) { // The only way to delete a newline is to delete until the start of // the next line, so in linewise mode evalInput will include the next // line. We don't want this in indent, so we go back a line. @@ -1740,33 +1931,59 @@ } for (var i = startLine; i <= endLine; i++) { for (var j = 0; j < repeat; j++) { - cm.indentLine(i, operatorArgs.indentRight); + cm.indentLine(i, args.indentRight); } } - cm.setCursor(curStart); - cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); + return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor); }, - swapcase: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) { - var toSwap = cm.getRange(curStart, curEnd); - var swapped = ''; - for (var i = 0; i < toSwap.length; i++) { - var character = toSwap.charAt(i); - swapped += isUpperCase(character) ? character.toLowerCase() : - character.toUpperCase(); + changeCase: function(cm, args, ranges, oldAnchor, newHead) { + var selections = cm.getSelections(); + var swapped = []; + var toLower = args.toLower; + for (var j = 0; j < selections.length; j++) { + var toSwap = selections[j]; + var text = ''; + if (toLower === true) { + text = toSwap.toLowerCase(); + } else if (toLower === false) { + text = toSwap.toUpperCase(); + } else { + for (var i = 0; i < toSwap.length; i++) { + var character = toSwap.charAt(i); + text += isUpperCase(character) ? character.toLowerCase() : + character.toUpperCase(); + } + } + swapped.push(text); } - cm.replaceRange(swapped, curStart, curEnd); - if (!operatorArgs.shouldMoveCursor) { - cm.setCursor(curOriginal); + cm.replaceSelections(swapped); + if (args.shouldMoveCursor){ + return newHead; + } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) { + return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor); + } else if (args.linewise){ + return oldAnchor; + } else { + return cursorMin(ranges[0].anchor, ranges[0].head); } }, - yank: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) { + yank: function(cm, args, ranges, oldAnchor) { + var vim = cm.state.vim; + var text = cm.getSelection(); + var endPos = vim.visualMode + ? cursorMin(vim.sel.anchor, vim.sel.head, ranges[0].head, ranges[0].anchor) + : oldAnchor; vimGlobalState.registerController.pushText( - operatorArgs.registerName, 'yank', - cm.getRange(curStart, curEnd), operatorArgs.linewise); - cm.setCursor(curOriginal); + args.registerName, 'yank', + text, args.linewise, vim.visualBlock); + return endPos; } }; + function defineOperator(name, fn) { + operators[name] = fn; + } + var actions = { jumpListWalk: function(cm, actionArgs, vim) { if (vim.visualMode) { @@ -1855,14 +2072,45 @@ vim.insertMode = true; vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1; var insertAt = (actionArgs) ? actionArgs.insertAt : null; + var sel = vim.sel; + var head = actionArgs.head || cm.getCursor('head'); + var height = cm.listSelections().length; if (insertAt == 'eol') { - var cursor = cm.getCursor(); - cursor = Pos(cursor.line, lineLength(cm, cursor.line)); - cm.setCursor(cursor); + head = Pos(head.line, lineLength(cm, head.line)); } else if (insertAt == 'charAfter') { - cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); + head = offsetCursor(head, 0, 1); } else if (insertAt == 'firstNonBlank') { - cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); + head = motions.moveToFirstNonWhiteSpaceCharacter(cm, head); + } else if (insertAt == 'startOfSelectedArea') { + if (!vim.visualBlock) { + if (sel.head.line < sel.anchor.line) { + head = sel.head; + } else { + head = Pos(sel.anchor.line, 0); + } + } else { + head = Pos( + Math.min(sel.head.line, sel.anchor.line), + Math.min(sel.head.ch, sel.anchor.ch)); + height = Math.abs(sel.head.line - sel.anchor.line) + 1; + } + } else if (insertAt == 'endOfSelectedArea') { + if (!vim.visualBlock) { + if (sel.head.line >= sel.anchor.line) { + head = offsetCursor(sel.head, 0, 1); + } else { + head = Pos(sel.anchor.line, 0); + } + } else { + head = Pos( + Math.min(sel.head.line, sel.anchor.line), + Math.max(sel.head.ch + 1, sel.anchor.ch)); + height = Math.abs(sel.head.line - sel.anchor.line) + 1; + } + } else if (insertAt == 'inplace') { + if (vim.visualMode){ + return; + } } cm.setOption('keyMap', 'vim-insert'); cm.setOption('disableInput', false); @@ -1878,82 +2126,73 @@ if (!vimGlobalState.macroModeState.isPlaying) { // Only record if not replaying. cm.on('change', onChange); - cm.on('cursorActivity', onCursorActivity); CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); } + if (vim.visualMode) { + exitVisualMode(cm); + } + selectForInsert(cm, head, height); }, toggleVisualMode: function(cm, actionArgs, vim) { var repeat = actionArgs.repeat; - var curStart = cm.getCursor(); - var curEnd; + var anchor = cm.getCursor(); + var head; // TODO: The repeat should actually select number of characters/lines // equal to the repeat times the size of the previous visual // operation. if (!vim.visualMode) { - cm.on('mousedown', exitVisualMode); + // Entering visual mode vim.visualMode = true; vim.visualLine = !!actionArgs.linewise; - if (vim.visualLine) { - curStart.ch = 0; - curEnd = clipCursorToContent( - cm, Pos(curStart.line + repeat - 1, lineLength(cm, curStart.line)), + vim.visualBlock = !!actionArgs.blockwise; + head = clipCursorToContent( + cm, Pos(anchor.line, anchor.ch + repeat - 1), true /** includeLineBreak */); - } else { - curEnd = clipCursorToContent( - cm, Pos(curStart.line, curStart.ch + repeat), - true /** includeLineBreak */); - } - // Make the initial selection. - if (!actionArgs.repeatIsExplicit && !vim.visualLine) { - // This is a strange case. Here the implicit repeat is 1. The - // following commands lets the cursor hover over the 1 character - // selection. - cm.setCursor(curEnd); - cm.setSelection(curEnd, curStart); - } else { - cm.setSelection(curStart, curEnd); - } - CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""}); + vim.sel = { + anchor: anchor, + head: head + }; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""}); + updateCmSelection(cm); + updateMark(cm, vim, '<', cursorMin(anchor, head)); + updateMark(cm, vim, '>', cursorMax(anchor, head)); + } else if (vim.visualLine ^ actionArgs.linewise || + vim.visualBlock ^ actionArgs.blockwise) { + // Toggling between modes + vim.visualLine = !!actionArgs.linewise; + vim.visualBlock = !!actionArgs.blockwise; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : vim.visualBlock ? "blockwise" : ""}); + updateCmSelection(cm); } else { - curStart = cm.getCursor('anchor'); - curEnd = cm.getCursor('head'); - if (!vim.visualLine && actionArgs.linewise) { - // Shift-V pressed in characterwise visual mode. Switch to linewise - // visual mode instead of exiting visual mode. - vim.visualLine = true; - curStart.ch = cursorIsBefore(curStart, curEnd) ? 0 : - lineLength(cm, curStart.line); - curEnd.ch = cursorIsBefore(curStart, curEnd) ? - lineLength(cm, curEnd.line) : 0; - cm.setSelection(curStart, curEnd); - CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"}); - } else if (vim.visualLine && !actionArgs.linewise) { - // v pressed in linewise visual mode. Switch to characterwise visual - // mode instead of exiting visual mode. - vim.visualLine = false; - CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); - } else { - exitVisualMode(cm); - } + exitVisualMode(cm); } - updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart - : curEnd); - updateMark(cm, vim, '>', cursorIsBefore(curStart, curEnd) ? curEnd - : curStart); }, reselectLastSelection: function(cm, _actionArgs, vim) { - if (vim.lastSelection) { - var lastSelection = vim.lastSelection; - cm.setSelection(lastSelection.curStart, lastSelection.curEnd); - if (lastSelection.visualLine) { - vim.visualMode = true; - vim.visualLine = true; + var lastSelection = vim.lastSelection; + if (vim.visualMode) { + updateLastSelection(cm, vim); + } + if (lastSelection) { + var anchor = lastSelection.anchorMark.find(); + var head = lastSelection.headMark.find(); + if (!anchor || !head) { + // If the marks have been destroyed due to edits, do nothing. + return; } - else { - vim.visualMode = true; - vim.visualLine = false; - } - CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""}); + vim.sel = { + anchor: anchor, + head: head + }; + vim.visualMode = true; + vim.visualLine = lastSelection.visualLine; + vim.visualBlock = lastSelection.visualBlock; + updateCmSelection(cm); + updateMark(cm, vim, '<', cursorMin(anchor, head)); + updateMark(cm, vim, '>', cursorMax(anchor, head)); + CodeMirror.signal(cm, 'vim-mode-change', { + mode: 'visual', + subMode: vim.visualLine ? 'linewise' : + vim.visualBlock ? 'blockwise' : ''}); } }, joinLines: function(cm, actionArgs, vim) { @@ -1961,6 +2200,11 @@ if (vim.visualMode) { curStart = cm.getCursor('anchor'); curEnd = cm.getCursor('head'); + if (cursorIsBefore(curEnd, curStart)) { + var tmp = curEnd; + curEnd = curStart; + curStart = tmp; + } curEnd.ch = lineLength(cm, curEnd.line) - 1; } else { // Repeat is the number of lines to join. Minimum 2 lines. @@ -1970,18 +2214,19 @@ Infinity)); } var finalCh = 0; - cm.operation(function() { - for (var i = curStart.line; i < curEnd.line; i++) { - finalCh = lineLength(cm, curStart.line); - var tmp = Pos(curStart.line + 1, - lineLength(cm, curStart.line + 1)); - var text = cm.getRange(curStart, tmp); - text = text.replace(/\n\s*/g, ' '); - cm.replaceRange(text, curStart, tmp); - } - var curFinalPos = Pos(curStart.line, finalCh); - cm.setCursor(curFinalPos); - }); + for (var i = curStart.line; i < curEnd.line; i++) { + finalCh = lineLength(cm, curStart.line); + var tmp = Pos(curStart.line + 1, + lineLength(cm, curStart.line + 1)); + var text = cm.getRange(curStart, tmp); + text = text.replace(/\n\s*/g, ' '); + cm.replaceRange(text, curStart, tmp); + } + var curFinalPos = Pos(curStart.line, finalCh); + if (vim.visualMode) { + exitVisualMode(cm, false); + } + cm.setCursor(curFinalPos); }, newLineAndEnterInsertMode: function(cm, actionArgs, vim) { vim.insertMode = true; @@ -2001,7 +2246,7 @@ } this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim); }, - paste: function(cm, actionArgs) { + paste: function(cm, actionArgs, vim) { var cur = copyCursor(cm.getCursor()); var register = vimGlobalState.registerController.getRegister( actionArgs.registerName); @@ -2009,12 +2254,44 @@ if (!text) { return; } + if (actionArgs.matchIndent) { + var tabSize = cm.getOption("tabSize"); + // length that considers tabs and tabSize + var whitespaceLength = function(str) { + var tabs = (str.split("\t").length - 1); + var spaces = (str.split(" ").length - 1); + return tabs * tabSize + spaces * 1; + }; + var currentLine = cm.getLine(cm.getCursor().line); + var indent = whitespaceLength(currentLine.match(/^\s*/)[0]); + // chomp last newline b/c don't want it to match /^\s*/gm + var chompedText = text.replace(/\n$/, ''); + var wasChomped = text !== chompedText; + var firstIndent = whitespaceLength(text.match(/^\s*/)[0]); + var text = chompedText.replace(/^\s*/gm, function(wspace) { + var newIndent = indent + (whitespaceLength(wspace) - firstIndent); + if (newIndent < 0) { + return ""; + } + else if (cm.getOption("indentWithTabs")) { + var quotient = Math.floor(newIndent / tabSize); + return Array(quotient + 1).join('\t'); + } + else { + return Array(newIndent + 1).join(' '); + } + }); + text += wasChomped ? "\n" : ""; + } if (actionArgs.repeat > 1) { var text = Array(actionArgs.repeat + 1).join(text); } var linewise = register.linewise; + var blockwise = register.blockwise; if (linewise) { - if (actionArgs.after) { + if(vim.visualMode) { + text = vim.visualLine ? text.slice(0, -1) : '\n' + text.slice(0, text.length - 1) + '\n'; + } else if (actionArgs.after) { // Move the newline at the end to the start instead, and paste just // before the newline character of the line we are on right now. text = '\n' + text.slice(0, text.length - 1); @@ -2023,26 +2300,96 @@ cur.ch = 0; } } else { + if (blockwise) { + text = text.split('\n'); + for (var i = 0; i < text.length; i++) { + text[i] = (text[i] == '') ? ' ' : text[i]; + } + } cur.ch += actionArgs.after ? 1 : 0; } - cm.replaceRange(text, cur); - // Now fine tune the cursor to where we want it. var curPosFinal; var idx; - if (linewise && actionArgs.after) { - curPosFinal = Pos( - cur.line + 1, - findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1))); - } else if (linewise && !actionArgs.after) { - curPosFinal = Pos( - cur.line, - findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line))); - } else if (!linewise && actionArgs.after) { - idx = cm.indexFromPos(cur); - curPosFinal = cm.posFromIndex(idx + text.length - 1); + if (vim.visualMode) { + // save the pasted text for reselection if the need arises + vim.lastPastedText = text; + var lastSelectionCurEnd; + var selectedArea = getSelectedAreaRange(cm, vim); + var selectionStart = selectedArea[0]; + var selectionEnd = selectedArea[1]; + var selectedText = cm.getSelection(); + var selections = cm.listSelections(); + var emptyStrings = new Array(selections.length).join('1').split('1'); + // save the curEnd marker before it get cleared due to cm.replaceRange. + if (vim.lastSelection) { + lastSelectionCurEnd = vim.lastSelection.headMark.find(); + } + // push the previously selected text to unnamed register + vimGlobalState.registerController.unnamedRegister.setText(selectedText); + if (blockwise) { + // first delete the selected text + cm.replaceSelections(emptyStrings); + // Set new selections as per the block length of the yanked text + selectionEnd = Pos(selectionStart.line + text.length-1, selectionStart.ch); + cm.setCursor(selectionStart); + selectBlock(cm, selectionEnd); + cm.replaceSelections(text); + curPosFinal = selectionStart; + } else if (vim.visualBlock) { + cm.replaceSelections(emptyStrings); + cm.setCursor(selectionStart); + cm.replaceRange(text, selectionStart, selectionStart); + curPosFinal = selectionStart; + } else { + cm.replaceRange(text, selectionStart, selectionEnd); + curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1); + } + // restore the the curEnd marker + if(lastSelectionCurEnd) { + vim.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd); + } + if (linewise) { + curPosFinal.ch=0; + } } else { - idx = cm.indexFromPos(cur); - curPosFinal = cm.posFromIndex(idx + text.length); + if (blockwise) { + cm.setCursor(cur); + for (var i = 0; i < text.length; i++) { + var line = cur.line+i; + if (line > cm.lastLine()) { + cm.replaceRange('\n', Pos(line, 0)); + } + var lastCh = lineLength(cm, line); + if (lastCh < cur.ch) { + extendLineToColumn(cm, line, cur.ch); + } + } + cm.setCursor(cur); + selectBlock(cm, Pos(cur.line + text.length-1, cur.ch)); + cm.replaceSelections(text); + curPosFinal = cur; + } else { + cm.replaceRange(text, cur); + // Now fine tune the cursor to where we want it. + if (linewise && actionArgs.after) { + curPosFinal = Pos( + cur.line + 1, + findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line + 1))); + } else if (linewise && !actionArgs.after) { + curPosFinal = Pos( + cur.line, + findFirstNonWhiteSpaceCharacter(cm.getLine(cur.line))); + } else if (!linewise && actionArgs.after) { + idx = cm.indexFromPos(cur); + curPosFinal = cm.posFromIndex(idx + text.length - 1); + } else { + idx = cm.indexFromPos(cur); + curPosFinal = cm.posFromIndex(idx + text.length); + } + } + } + if (vim.visualMode) { + exitVisualMode(cm, false); } cm.setCursor(curPosFinal); }, @@ -2067,13 +2414,11 @@ var curStart = cm.getCursor(); var replaceTo; var curEnd; - if (vim.visualMode){ - curStart=cm.getCursor('start'); - curEnd=cm.getCursor('end'); - // workaround to catch the character under the cursor - // existing workaround doesn't cover actions - curEnd=cm.clipPos(Pos(curEnd.line, curEnd.ch+1)); - }else{ + var selections = cm.listSelections(); + if (vim.visualMode) { + curStart = cm.getCursor('start'); + curEnd = cm.getCursor('end'); + } else { var line = cm.getLine(curStart.line); replaceTo = curStart.ch + actionArgs.repeat; if (replaceTo > line.length) { @@ -2081,19 +2426,29 @@ } curEnd = Pos(curStart.line, replaceTo); } - if (replaceWith=='\n'){ + if (replaceWith=='\n') { if (!vim.visualMode) cm.replaceRange('', curStart, curEnd); // special case, where vim help says to replace by just one line-break (CodeMirror.commands.newlineAndIndentContinueComment || CodeMirror.commands.newlineAndIndent)(cm); - }else { - var replaceWithStr=cm.getRange(curStart, curEnd); + } else { + var replaceWithStr = cm.getRange(curStart, curEnd); //replace all characters in range by selected, but keep linebreaks - replaceWithStr=replaceWithStr.replace(/[^\n]/g,replaceWith); - cm.replaceRange(replaceWithStr, curStart, curEnd); - if (vim.visualMode){ + replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith); + if (vim.visualBlock) { + // Tabs are split in visua block before replacing + var spaces = new Array(cm.getOption("tabSize")+1).join(' '); + replaceWithStr = cm.getSelection(); + replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split('\n'); + cm.replaceSelections(replaceWithStr); + } else { + cm.replaceRange(replaceWithStr, curStart, curEnd); + } + if (vim.visualMode) { + curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ? + selections[0].anchor : selections[0].head; cm.setCursor(curStart); - exitVisualMode(cm); - }else{ + exitVisualMode(cm, false); + } else { cm.setCursor(offsetCursor(curEnd, 0, -1)); } } @@ -2136,9 +2491,14 @@ repeat = vim.lastEditInputState.repeatOverride || repeat; } repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */); - } + }, + exitInsertMode: exitInsertMode }; + function defineAction(name, fn) { + actions[name] = fn; + } + /* * Below are miscellaneous utility functions used by vim.js */ @@ -2164,16 +2524,66 @@ return ret; } function offsetCursor(cur, offsetLine, offsetCh) { + if (typeof offsetLine === 'object') { + offsetCh = offsetLine.ch; + offsetLine = offsetLine.line; + } return Pos(cur.line + offsetLine, cur.ch + offsetCh); } - function matchKeysPartial(pressed, mapped) { - for (var i = 0; i < pressed.length; i++) { - // 'character' means any character. For mark, register commads, etc. - if (pressed[i] != mapped[i] && mapped[i] != 'character') { - return false; + function getOffset(anchor, head) { + return { + line: head.line - anchor.line, + ch: head.line - anchor.line + }; + } + function commandMatches(keys, keyMap, context, inputState) { + // Partial matches are not applied. They inform the key handler + // that the current key sequence is a subsequence of a valid key + // sequence, so that the key buffer is not cleared. + var match, partial = [], full = []; + for (var i = 0; i < keyMap.length; i++) { + var command = keyMap[i]; + if (context == 'insert' && command.context != 'insert' || + command.context && command.context != context || + inputState.operator && command.type == 'action' || + !(match = commandMatch(keys, command.keys))) { continue; } + if (match == 'partial') { partial.push(command); } + if (match == 'full') { full.push(command); } + } + return { + partial: partial.length && partial, + full: full.length && full + }; + } + function commandMatch(pressed, mapped) { + if (mapped.slice(-11) == '') { + // Last character matches anything. + var prefixLen = mapped.length - 11; + var pressedPrefix = pressed.slice(0, prefixLen); + var mappedPrefix = mapped.slice(0, prefixLen); + return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? 'full' : + mappedPrefix.indexOf(pressedPrefix) == 0 ? 'partial' : false; + } else { + return pressed == mapped ? 'full' : + mapped.indexOf(pressed) == 0 ? 'partial' : false; + } + } + function lastChar(keys) { + var match = /^.*(<[\w\-]+>)$/.exec(keys); + var selectedCharacter = match ? match[1] : keys.slice(-1); + if (selectedCharacter.length > 1){ + switch(selectedCharacter){ + case '': + selectedCharacter='\n'; + break; + case '': + selectedCharacter=' '; + break; + default: + break; } } - return true; + return selectedCharacter; } function repeatFn(cm, fn, repeat) { return function() { @@ -2197,7 +2607,19 @@ } return false; } - function cusrorIsBetween(cur1, cur2, cur3) { + function cursorMin(cur1, cur2) { + if (arguments.length > 2) { + cur2 = cursorMin.apply(undefined, Array.prototype.slice.call(arguments, 1)); + } + return cursorIsBefore(cur1, cur2) ? cur1 : cur2; + } + function cursorMax(cur1, cur2) { + if (arguments.length > 2) { + cur2 = cursorMax.apply(undefined, Array.prototype.slice.call(arguments, 1)); + } + return cursorIsBefore(cur1, cur2) ? cur2 : cur1; + } + function cursorIsBetween(cur1, cur2, cur3) { // returns true if cur2 is between cur1 and cur3. var cur1before2 = cursorIsBefore(cur1, cur2); var cur2before3 = cursorIsBefore(cur2, cur3); @@ -2218,25 +2640,254 @@ function escapeRegex(s) { return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1'); } + function extendLineToColumn(cm, lineNum, column) { + var endCh = lineLength(cm, lineNum); + var spaces = new Array(column-endCh+1).join(' '); + cm.setCursor(Pos(lineNum, endCh)); + cm.replaceRange(spaces, cm.getCursor()); + } + // This functions selects a rectangular block + // of text with selectionEnd as any of its corner + // Height of block: + // Difference in selectionEnd.line and first/last selection.line + // Width of the block: + // Distance between selectionEnd.ch and any(first considered here) selection.ch + function selectBlock(cm, selectionEnd) { + var selections = [], ranges = cm.listSelections(); + var head = copyCursor(cm.clipPos(selectionEnd)); + var isClipped = !cursorEqual(selectionEnd, head); + var curHead = cm.getCursor('head'); + var primIndex = getIndex(ranges, curHead); + var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor); + var max = ranges.length - 1; + var index = max - primIndex > primIndex ? max : 0; + var base = ranges[index].anchor; - function exitVisualMode(cm) { - cm.off('mousedown', exitVisualMode); + var firstLine = Math.min(base.line, head.line); + var lastLine = Math.max(base.line, head.line); + var baseCh = base.ch, headCh = head.ch; + + var dir = ranges[index].head.ch - baseCh; + var newDir = headCh - baseCh; + if (dir > 0 && newDir <= 0) { + baseCh++; + if (!isClipped) { headCh--; } + } else if (dir < 0 && newDir >= 0) { + baseCh--; + if (!wasClipped) { headCh++; } + } else if (dir < 0 && newDir == -1) { + baseCh--; + headCh++; + } + for (var line = firstLine; line <= lastLine; line++) { + var range = {anchor: new Pos(line, baseCh), head: new Pos(line, headCh)}; + selections.push(range); + } + primIndex = head.line == lastLine ? selections.length - 1 : 0; + cm.setSelections(selections); + selectionEnd.ch = headCh; + base.ch = baseCh; + return base; + } + function selectForInsert(cm, head, height) { + var sel = []; + for (var i = 0; i < height; i++) { + var lineHead = offsetCursor(head, i, 0); + sel.push({anchor: lineHead, head: lineHead}); + } + cm.setSelections(sel, 0); + } + // getIndex returns the index of the cursor in the selections. + function getIndex(ranges, cursor, end) { + for (var i = 0; i < ranges.length; i++) { + var atAnchor = end != 'head' && cursorEqual(ranges[i].anchor, cursor); + var atHead = end != 'anchor' && cursorEqual(ranges[i].head, cursor); + if (atAnchor || atHead) { + return i; + } + } + return -1; + } + function getSelectedAreaRange(cm, vim) { + var lastSelection = vim.lastSelection; + var getCurrentSelectedAreaRange = function() { + var selections = cm.listSelections(); + var start = selections[0]; + var end = selections[selections.length-1]; + var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head; + var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor; + return [selectionStart, selectionEnd]; + }; + var getLastSelectedAreaRange = function() { + var selectionStart = cm.getCursor(); + var selectionEnd = cm.getCursor(); + var block = lastSelection.visualBlock; + if (block) { + var width = block.width; + var height = block.height; + selectionEnd = Pos(selectionStart.line + height, selectionStart.ch + width); + var selections = []; + // selectBlock creates a 'proper' rectangular block. + // We do not want that in all cases, so we manually set selections. + for (var i = selectionStart.line; i < selectionEnd.line; i++) { + var anchor = Pos(i, selectionStart.ch); + var head = Pos(i, selectionEnd.ch); + var range = {anchor: anchor, head: head}; + selections.push(range); + } + cm.setSelections(selections); + } else { + var start = lastSelection.anchorMark.find(); + var end = lastSelection.headMark.find(); + var line = end.line - start.line; + var ch = end.ch - start.ch; + selectionEnd = {line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch}; + if (lastSelection.visualLine) { + selectionStart = Pos(selectionStart.line, 0); + selectionEnd = Pos(selectionEnd.line, lineLength(cm, selectionEnd.line)); + } + cm.setSelection(selectionStart, selectionEnd); + } + return [selectionStart, selectionEnd]; + }; + if (!vim.visualMode) { + // In case of replaying the action. + return getLastSelectedAreaRange(); + } else { + return getCurrentSelectedAreaRange(); + } + } + // Updates the previous selection with the current selection's values. This + // should only be called in visual mode. + function updateLastSelection(cm, vim) { + var anchor = vim.sel.anchor; + var head = vim.sel.head; + // To accommodate the effect of lastPastedText in the last selection + if (vim.lastPastedText) { + head = cm.posFromIndex(cm.indexFromPos(anchor) + vim.lastPastedText.length); + vim.lastPastedText = null; + } + vim.lastSelection = {'anchorMark': cm.setBookmark(anchor), + 'headMark': cm.setBookmark(head), + 'anchor': copyCursor(anchor), + 'head': copyCursor(head), + 'visualMode': vim.visualMode, + 'visualLine': vim.visualLine, + 'visualBlock': vim.visualBlock}; + } + function expandSelection(cm, start, end) { + var sel = cm.state.vim.sel; + var head = sel.head; + var anchor = sel.anchor; + var tmp; + if (cursorIsBefore(end, start)) { + tmp = end; + end = start; + start = tmp; + } + if (cursorIsBefore(head, anchor)) { + head = cursorMin(start, head); + anchor = cursorMax(anchor, end); + } else { + anchor = cursorMin(start, anchor); + head = cursorMax(head, end); + head = offsetCursor(head, 0, -1); + if (head.ch == -1 && head.line != cm.firstLine()) { + head = Pos(head.line - 1, lineLength(cm, head.line - 1)); + } + } + return [anchor, head]; + } + /** + * Updates the CodeMirror selection to match the provided vim selection. + * If no arguments are given, it uses the current vim selection state. + */ + function updateCmSelection(cm, sel, mode) { var vim = cm.state.vim; - // can't use selection state here because yank has already reset its cursor - vim.lastSelection = {'curStart': vim.marks['<'].find(), - 'curEnd': vim.marks['>'].find(), 'visualMode': vim.visualMode, - 'visualLine': vim.visualLine}; + sel = sel || vim.sel; + var mode = mode || + vim.visualLine ? 'line' : vim.visualBlock ? 'block' : 'char'; + var cmSel = makeCmSelection(cm, sel, mode); + cm.setSelections(cmSel.ranges, cmSel.primary); + updateFakeCursor(cm); + } + function makeCmSelection(cm, sel, mode, exclusive) { + var head = copyCursor(sel.head); + var anchor = copyCursor(sel.anchor); + if (mode == 'char') { + var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; + var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; + head = offsetCursor(sel.head, 0, headOffset); + anchor = offsetCursor(sel.anchor, 0, anchorOffset); + return { + ranges: [{anchor: anchor, head: head}], + primary: 0 + }; + } else if (mode == 'line') { + if (!cursorIsBefore(sel.head, sel.anchor)) { + anchor.ch = 0; + + var lastLine = cm.lastLine(); + if (head.line > lastLine) { + head.line = lastLine; + } + head.ch = lineLength(cm, head.line); + } else { + head.ch = 0; + anchor.ch = lineLength(cm, anchor.line); + } + return { + ranges: [{anchor: anchor, head: head}], + primary: 0 + }; + } else if (mode == 'block') { + var top = Math.min(anchor.line, head.line), + left = Math.min(anchor.ch, head.ch), + bottom = Math.max(anchor.line, head.line), + right = Math.max(anchor.ch, head.ch) + 1; + var height = bottom - top + 1; + var primary = head.line == top ? 0 : height - 1; + var ranges = []; + for (var i = 0; i < height; i++) { + ranges.push({ + anchor: Pos(top + i, left), + head: Pos(top + i, right) + }); + } + return { + ranges: ranges, + primary: primary + }; + } + } + function getHead(cm) { + var cur = cm.getCursor('head'); + if (cm.getSelection().length == 1) { + // Small corner case when only 1 character is selected. The "real" + // head is the left of head and anchor. + cur = cursorMin(cur, cm.getCursor('anchor')); + } + return cur; + } + + /** + * If moveHead is set to false, the CodeMirror selection will not be + * touched. The caller assumes the responsibility of putting the cursor + * in the right place. + */ + function exitVisualMode(cm, moveHead) { + var vim = cm.state.vim; + if (moveHead !== false) { + cm.setCursor(clipCursorToContent(cm, vim.sel.head)); + } + updateLastSelection(cm, vim); vim.visualMode = false; vim.visualLine = false; - var selectionStart = cm.getCursor('anchor'); - var selectionEnd = cm.getCursor('head'); - if (!cursorEqual(selectionStart, selectionEnd)) { - // Clear the selection and set the cursor only if the selection has not - // already been cleared. Otherwise we risk moving the cursor somewhere - // it's not supposed to be. - cm.setCursor(clipCursorToContent(cm, selectionEnd)); - } + vim.visualBlock = false; CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + if (vim.fakeCursor) { + vim.fakeCursor.clear(); + } } // Remove any trailing newlines from the selection. For @@ -2287,7 +2938,7 @@ } function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) { - var cur = cm.getCursor(); + var cur = getHead(cm); var line = cm.getLine(cur.line); var idx = cur.ch; @@ -2562,6 +3213,7 @@ /** * @param {CodeMirror} cm CodeMirror object. + * @param {Pos} cur The position to start from. * @param {int} repeat Number of words to move past. * @param {boolean} forward True to search forward. False to search * backward. @@ -2571,8 +3223,7 @@ * False if only alphabet characters count as part of the word. * @return {Cursor} The position the cursor should move to. */ - function moveToWord(cm, repeat, forward, wordEnd, bigWord) { - var cur = cm.getCursor(); + function moveToWord(cm, cur, repeat, forward, wordEnd, bigWord) { var curStart = copyCursor(cur); var words = []; if (forward && !wordEnd || !forward && wordEnd) { @@ -2670,74 +3321,81 @@ return idx; } - function getContextLevel(ctx) { - return (ctx === 'string' || ctx === 'comment') ? 1 : 0; - } - - function findMatchedSymbol(cm, cur, symb) { - var line = cur.line; - var ch = cur.ch; - symb = symb ? symb : cm.getLine(line).charAt(ch); - - var symbContext = cm.getTokenAt(Pos(line, ch + 1)).type; - var symbCtxLevel = getContextLevel(symbContext); - - var reverseSymb = ({ - '(': ')', ')': '(', - '[': ']', ']': '[', - '{': '}', '}': '{'})[symb]; - - // Couldn't find a matching symbol, abort - if (!reverseSymb) { - return cur; + function findParagraph(cm, head, repeat, dir, inclusive) { + var line = head.line; + var min = cm.firstLine(); + var max = cm.lastLine(); + var start, end, i = line; + function isEmpty(i) { return !cm.getLine(i); } + function isBoundary(i, dir, any) { + if (any) { return isEmpty(i) != isEmpty(i + dir); } + return !isEmpty(i) && isEmpty(i + dir); + } + if (dir) { + while (min <= i && i <= max && repeat > 0) { + if (isBoundary(i, dir)) { repeat--; } + i += dir; + } + return new Pos(i, 0); } - // set our increment to move forward (+1) or backwards (-1) - // depending on which bracket we're matching - var increment = ({'(': 1, '{': 1, '[': 1})[symb] || -1; - var endLine = increment === 1 ? cm.lineCount() : -1; - var depth = 1, nextCh = symb, index = ch, lineText = cm.getLine(line); - // Simple search for closing paren--just count openings and closings till - // we find our match - // TODO: use info from CodeMirror to ignore closing brackets in comments - // and quotes, etc. - while (line !== endLine && depth > 0) { - index += increment; - nextCh = lineText.charAt(index); - if (!nextCh) { - line += increment; - lineText = cm.getLine(line) || ''; - if (increment > 0) { - index = 0; - } else { - var lineLen = lineText.length; - index = (lineLen > 0) ? (lineLen-1) : 0; - } - nextCh = lineText.charAt(index); - } - var revSymbContext = cm.getTokenAt(Pos(line, index + 1)).type; - var revSymbCtxLevel = getContextLevel(revSymbContext); - if (symbCtxLevel >= revSymbCtxLevel) { - if (nextCh === symb) { - depth++; - } else if (nextCh === reverseSymb) { - depth--; + var vim = cm.state.vim; + if (vim.visualLine && isBoundary(line, 1, true)) { + var anchor = vim.sel.anchor; + if (isBoundary(anchor.line, -1, true)) { + if (!inclusive || anchor.line != line) { + line += 1; } } } - - if (nextCh) { - return Pos(line, index); + var startState = isEmpty(line); + for (i = line; i <= max && repeat; i++) { + if (isBoundary(i, 1, true)) { + if (!inclusive || isEmpty(i) != startState) { + repeat--; + } + } } - return cur; + end = new Pos(i, 0); + // select boundary before paragraph for the last one + if (i > max && !startState) { startState = true; } + else { inclusive = false; } + for (i = line; i > min; i--) { + if (!inclusive || isEmpty(i) == startState || i == line) { + if (isBoundary(i, -1, true)) { break; } + } + } + start = new Pos(i, 0); + return { start: start, end: end }; } // TODO: perhaps this finagling of start and end positions belonds // in codmirror/replaceRange? - function selectCompanionObject(cm, revSymb, inclusive) { - var cur = copyCursor(cm.getCursor()); - var end = findMatchedSymbol(cm, cur, revSymb); - var start = findMatchedSymbol(cm, end); + function selectCompanionObject(cm, head, symb, inclusive) { + var cur = head, start, end; + + var bracketRegexp = ({ + '(': /[()]/, ')': /[()]/, + '[': /[[\]]/, ']': /[[\]]/, + '{': /[{}]/, '}': /[{}]/})[symb]; + var openSym = ({ + '(': '(', ')': '(', + '[': '[', ']': '[', + '{': '{', '}': '{'})[symb]; + var curChar = cm.getLine(cur.line).charAt(cur.ch); + // Due to the behavior of scanForBracket, we need to add an offset if the + // cursor is on a matching open bracket. + var offset = curChar === openSym ? 1 : 0; + + start = cm.scanForBracket(Pos(cur.line, cur.ch + offset), -1, null, {'bracketRegex': bracketRegexp}); + end = cm.scanForBracket(Pos(cur.line, cur.ch + offset), 1, null, {'bracketRegex': bracketRegexp}); + + if (!start || !end) { + return { start: cur, end: cur }; + } + + start = start.pos; + end = end.pos; if ((start.line == end.line && start.ch > end.ch) || (start.line > end.line)) { @@ -2758,8 +3416,8 @@ // Takes in a symbol and a cursor and tries to simulate text objects that // have identical opening and closing symbols // TODO support across multiple lines - function findBeginningAndEnd(cm, symb, inclusive) { - var cur = copyCursor(cm.getCursor()); + function findBeginningAndEnd(cm, head, symb, inclusive) { + var cur = copyCursor(head); var line = cm.getLine(cur.line); var chars = line.split(''); var start, end, i, len; @@ -2837,6 +3495,12 @@ }, setReversed: function(reversed) { vimGlobalState.isReversed = reversed; + }, + getScrollbarAnnotate: function() { + return this.annotate; + }, + setScrollbarAnnotate: function(annotate) { + this.annotate = annotate; } }; function getSearchState(cm) { @@ -2852,6 +3516,18 @@ onClose(prompt(shortText, '')); } } + function splitBySlash(argString) { + var slashes = findUnescapedSlashes(argString) || []; + if (!slashes.length) return []; + var tokens = []; + // in case of strings like foo/bar + if (slashes[0] !== 0) return; + for (var i = 0; i < slashes.length; i++) { + if (typeof slashes[i] == 'number') + tokens.push(argString.substring(slashes[i] + 1, slashes[i+1])); + } + return tokens; + } function findUnescapedSlashes(str) { var escapeNextChar = false; @@ -2869,15 +3545,15 @@ // Translates a search string from ex (vim) syntax into javascript form. function translateRegex(str) { // When these match, add a '\' if unescaped or remove one if escaped. - var specials = ['|', '(', ')', '{']; + var specials = '|(){'; // Remove, but never add, a '\' for these. - var unescape = ['}']; + var unescape = '}'; var escapeNextChar = false; var out = []; for (var i = -1; i < str.length; i++) { var c = str.charAt(i) || ''; var n = str.charAt(i+1) || ''; - var specialComesNext = (specials.indexOf(n) != -1); + var specialComesNext = (n && specials.indexOf(n) != -1); if (escapeNextChar) { if (c !== '\\' || !specialComesNext) { out.push(c); @@ -2887,7 +3563,7 @@ if (c === '\\') { escapeNextChar = true; // Treat the unescape list as special for removing, but not adding '\'. - if (unescape.indexOf(n) != -1) { + if (n && unescape.indexOf(n) != -1) { specialComesNext = true; } // Not passing this test means removing a '\'. @@ -2975,6 +3651,9 @@ * through to the Regex object. */ function parseQuery(query, ignoreCase, smartCase) { + // First update the last search register + var lastSearchRegister = vimGlobalState.registerController.getRegister('/'); + lastSearchRegister.setText(query); // Check if the query is already a regex. if (query instanceof RegExp) { return query; } // First try to extract regex + flags from the input. If no flags found, @@ -3100,14 +3779,21 @@ }; } function highlightSearchMatches(cm, query) { - var overlay = getSearchState(cm).getOverlay(); + var searchState = getSearchState(cm); + var overlay = searchState.getOverlay(); if (!overlay || query != overlay.query) { if (overlay) { cm.removeOverlay(overlay); } overlay = searchOverlay(query); cm.addOverlay(overlay); - getSearchState(cm).setOverlay(overlay); + if (cm.showMatchesOnScrollbar) { + if (searchState.getScrollbarAnnotate()) { + searchState.getScrollbarAnnotate().clear(); + } + searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query)); + } + searchState.setOverlay(overlay); } } function findNext(cm, prev, query, repeat) { @@ -3132,8 +3818,13 @@ }); } function clearSearchHighlight(cm) { + var state = getSearchState(cm); cm.removeOverlay(getSearchState(cm).getOverlay()); - getSearchState(cm).setOverlay(null); + state.setOverlay(null); + if (state.getScrollbarAnnotate()) { + state.getScrollbarAnnotate().clear(); + state.setScrollbarAnnotate(null); + } } /** * Check if pos is in the specified range, INCLUSIVE. @@ -3177,6 +3868,7 @@ // shortNames must not match the prefix of the other command. var defaultExCommandMap = [ { name: 'map' }, + { name: 'imap', shortName: 'im' }, { name: 'nmap', shortName: 'nm' }, { name: 'vmap', shortName: 'vm' }, { name: 'unmap' }, @@ -3185,22 +3877,27 @@ { name: 'redo', shortName: 'red' }, { name: 'set', shortName: 'set' }, { name: 'sort', shortName: 'sor' }, - { name: 'substitute', shortName: 's' }, + { name: 'substitute', shortName: 's', possiblyAsync: true }, { name: 'nohlsearch', shortName: 'noh' }, { name: 'delmarks', shortName: 'delm' }, - { name: 'registers', shortName: 'reg' } + { name: 'registers', shortName: 'reg', excludeFromCommandHistory: true }, + { name: 'global', shortName: 'g' } ]; - Vim.ExCommandDispatcher = function() { + var ExCommandDispatcher = function() { this.buildCommandMap_(); }; - Vim.ExCommandDispatcher.prototype = { - processCommand: function(cm, input) { + ExCommandDispatcher.prototype = { + processCommand: function(cm, input, opt_params) { var vim = cm.state.vim; + var commandHistoryRegister = vimGlobalState.registerController.getRegister(':'); + var previousCommand = commandHistoryRegister.toString(); if (vim.visualMode) { exitVisualMode(cm); } var inputStream = new CodeMirror.StringStream(input); - var params = {}; + // update ": with the latest command whether valid or invalid + commandHistoryRegister.setText(input); + var params = opt_params || {}; params.input = input; try { this.parseInput_(cm, inputStream, params); @@ -3208,6 +3905,7 @@ showConfirm(cm, e); throw e; } + var command; var commandName; if (!params.commandName) { // If only a line range is defined, move to the line. @@ -3215,14 +3913,17 @@ commandName = 'move'; } } else { - var command = this.matchCommand_(params.commandName); + command = this.matchCommand_(params.commandName); if (command) { commandName = command.name; + if (command.excludeFromCommandHistory) { + commandHistoryRegister.setText(previousCommand); + } this.parseCommandArgs_(inputStream, params, command); if (command.type == 'exToKey') { // Handle Ex to Key mapping. for (var i = 0; i < command.toKeys.length; i++) { - CodeMirror.Vim.handleKey(cm, command.toKeys[i]); + CodeMirror.Vim.handleKey(cm, command.toKeys[i], 'mapping'); } return; } else if (command.type == 'exToEx') { @@ -3238,6 +3939,12 @@ } try { exCommands[commandName](cm, params); + // Possibly asynchronous commands (e.g. substitute, which might have a + // user confirmation), are responsible for calling the callback when + // done. All others have it taken care of for them here. + if ((!command || !command.possiblyAsync) && params.callback) { + params.callback(); + } } catch(e) { showConfirm(cm, e); throw e; @@ -3340,7 +4047,7 @@ this.commandMap_[commandName] = { name: commandName, type: 'exToKey', - toKeys: parseKeyString(rhs), + toKeys: rhs, user: true }; } @@ -3348,7 +4055,7 @@ if (rhs != ':' && rhs.charAt(0) == ':') { // Key to Ex mapping. var mapping = { - keys: parseKeyString(lhs), + keys: lhs, type: 'keyToEx', exArgs: { input: rhs.substring(1) }, user: true}; @@ -3357,9 +4064,9 @@ } else { // Key to key mapping var mapping = { - keys: parseKeyString(lhs), + keys: lhs, type: 'keyToKey', - toKeys: parseKeyString(rhs), + toKeys: rhs, user: true }; if (ctx) { mapping.context = ctx; } @@ -3368,15 +4075,6 @@ } }, unmap: function(lhs, ctx) { - var arrayEquals = function(a, b) { - if (a === b) return true; - if (a == null || b == null) return true; - if (a.length != b.length) return false; - for (var i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false; - } - return true; - }; if (lhs != ':' && lhs.charAt(0) == ':') { // Ex to Ex or Ex to key mapping if (ctx) { throw Error('Mode not supported for ex mappings'); } @@ -3387,9 +4085,9 @@ } } else { // Key to Ex or key to key mapping - var keys = parseKeyString(lhs); + var keys = lhs; for (var i = 0; i < defaultKeymap.length; i++) { - if (arrayEquals(keys, defaultKeymap[i].keys) + if (keys == defaultKeymap[i].keys && defaultKeymap[i].context === ctx && defaultKeymap[i].user) { defaultKeymap.splice(i, 1); @@ -3401,21 +4099,6 @@ } }; - // Converts a key string sequence of the form abd into Vim's - // keymap representation. - function parseKeyString(str) { - var key, match; - var keys = []; - while (str) { - match = (/<\w+-.+?>|<\w+>|./).exec(str); - if (match === null)break; - key = match[0]; - str = str.substring(match.index + key.length); - keys.push(key); - } - return keys; - } - var exCommands = { map: function(cm, params, ctx) { var mapArgs = params.args; @@ -3427,6 +4110,7 @@ } exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx); }, + imap: function(cm, params) { this.map(cm, params, 'insert'); }, nmap: function(cm, params) { this.map(cm, params, 'normal'); }, vmap: function(cm, params) { this.map(cm, params, 'visual'); }, unmap: function(cm, params, ctx) { @@ -3509,7 +4193,7 @@ continue; } var register = registers[registerName] || new Register(); - regInfo += '"' + registerName + ' ' + register.text + '
'; + regInfo += '"' + registerName + ' ' + register.toString() + '
'; } } showConfirm(cm, regInfo); @@ -3589,44 +4273,112 @@ } cm.replaceRange(text.join('\n'), curStart, curEnd); }, + global: function(cm, params) { + // a global command is of the form + // :[range]g/pattern/[cmd] + // argString holds the string /pattern/[cmd] + var argString = params.argString; + if (!argString) { + showConfirm(cm, 'Regular Expression missing from global'); + return; + } + // range is specified here + var lineStart = (params.line !== undefined) ? params.line : cm.firstLine(); + var lineEnd = params.lineEnd || params.line || cm.lastLine(); + // get the tokens from argString + var tokens = splitBySlash(argString); + var regexPart = argString, cmd; + if (tokens.length) { + regexPart = tokens[0]; + cmd = tokens.slice(1, tokens.length).join('/'); + } + if (regexPart) { + // If regex part is empty, then use the previous query. Otherwise + // use the regex part as the new query. + try { + updateSearchQuery(cm, regexPart, true /** ignoreCase */, + true /** smartCase */); + } catch (e) { + showConfirm(cm, 'Invalid regex: ' + regexPart); + return; + } + } + // now that we have the regexPart, search for regex matches in the + // specified range of lines + var query = getSearchState(cm).getQuery(); + var matchedLines = [], content = ''; + for (var i = lineStart; i <= lineEnd; i++) { + var matched = query.test(cm.getLine(i)); + if (matched) { + matchedLines.push(i+1); + content+= cm.getLine(i) + '
'; + } + } + // if there is no [cmd], just display the list of matched lines + if (!cmd) { + showConfirm(cm, content); + return; + } + var index = 0; + var nextCommand = function() { + if (index < matchedLines.length) { + var command = matchedLines[index] + cmd; + exCommandDispatcher.processCommand(cm, command, { + callback: nextCommand + }); + } + index++; + }; + nextCommand(); + }, substitute: function(cm, params) { if (!cm.getSearchCursor) { throw new Error('Search feature not available. Requires searchcursor.js or ' + 'any other getSearchCursor implementation.'); } var argString = params.argString; - var slashes = findUnescapedSlashes(argString); - if (slashes[0] !== 0) { - showConfirm(cm, 'Substitutions should be of the form ' + - ':s/pattern/replace/'); - return; - } - var regexPart = argString.substring(slashes[0] + 1, slashes[1]); - var replacePart = ''; - var flagsPart; - var count; + var tokens = argString ? splitBySlash(argString) : []; + var regexPart, replacePart = '', trailing, flagsPart, count; var confirm = false; // Whether to confirm each replace. - if (slashes[1]) { - replacePart = argString.substring(slashes[1] + 1, slashes[2]); - if (getOption('pcre')) { - replacePart = unescapeRegexReplace(replacePart); - } else { - replacePart = translateRegexReplace(replacePart); + var global = false; // True to replace all instances on a line, false to replace only 1. + if (tokens.length) { + regexPart = tokens[0]; + replacePart = tokens[1]; + if (replacePart !== undefined) { + if (getOption('pcre')) { + replacePart = unescapeRegexReplace(replacePart); + } else { + replacePart = translateRegexReplace(replacePart); + } + vimGlobalState.lastSubstituteReplacePart = replacePart; + } + trailing = tokens[2] ? tokens[2].split(' ') : []; + } else { + // either the argString is empty or its of the form ' hello/world' + // actually splitBySlash returns a list of tokens + // only if the string starts with a '/' + if (argString && argString.length) { + showConfirm(cm, 'Substitutions should be of the form ' + + ':s/pattern/replace/'); + return; } } - if (slashes[2]) { - // After the 3rd slash, we can have flags followed by a space followed - // by count. - var trailing = argString.substring(slashes[2] + 1).split(' '); + // After the 3rd slash, we can have flags followed by a space followed + // by count. + if (trailing) { flagsPart = trailing[0]; count = parseInt(trailing[1]); - } - if (flagsPart) { - if (flagsPart.indexOf('c') != -1) { - confirm = true; - flagsPart.replace('c', ''); + if (flagsPart) { + if (flagsPart.indexOf('c') != -1) { + confirm = true; + flagsPart.replace('c', ''); + } + if (flagsPart.indexOf('g') != -1) { + global = true; + flagsPart.replace('g', ''); + } + regexPart = regexPart + '/' + flagsPart; } - regexPart = regexPart + '/' + flagsPart; } if (regexPart) { // If regex part is empty, then use the previous query. Otherwise use @@ -3639,6 +4391,11 @@ return; } } + replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart; + if (replacePart === undefined) { + showConfirm(cm, 'No previous substitute regular expression'); + return; + } var state = getSearchState(cm); var query = state.getQuery(); var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line; @@ -3649,7 +4406,7 @@ } var startPos = clipCursorToContent(cm, Pos(lineStart, 0)); var cursor = cm.getSearchCursor(query, startPos); - doReplace(cm, confirm, lineStart, lineEnd, cursor, query, replacePart); + doReplace(cm, confirm, global, lineStart, lineEnd, cursor, query, replacePart, params.callback); }, redo: CodeMirror.commands.redo, undo: CodeMirror.commands.undo, @@ -3728,7 +4485,7 @@ } }; - var exCommandDispatcher = new Vim.ExCommandDispatcher(); + var exCommandDispatcher = new ExCommandDispatcher(); /** * @param {CodeMirror} cm CodeMirror instance we are in. @@ -3738,9 +4495,10 @@ * @param {RegExp} query Query for performing matches with. * @param {string} replaceWith Text to replace matches with. May contain $1, * $2, etc for replacing captured groups using Javascript replace. + * @param {function()} callback A callback for when the replace is done. */ - function doReplace(cm, confirm, lineStart, lineEnd, searchCursor, query, - replaceWith) { + function doReplace(cm, confirm, global, lineStart, lineEnd, searchCursor, query, + replaceWith, callback) { // Set up all the functions. cm.state.vim.exMode = true; var done = false; @@ -3760,17 +4518,21 @@ searchCursor.replace(newText); } function next() { - var found = searchCursor.findNext(); - if (!found) { - done = true; - } else if (isInRange(searchCursor.from(), lineStart, lineEnd)) { + var found; + // The below only loops to skip over multiple occurrences on the same + // line when 'global' is not true. + while(found = searchCursor.findNext() && + isInRange(searchCursor.from(), lineStart, lineEnd)) { + if (!global && lastPos && searchCursor.from().line == lastPos.line) { + continue; + } cm.scrollIntoView(searchCursor.from(), 30); cm.setSelection(searchCursor.from(), searchCursor.to()); lastPos = searchCursor.from(); done = false; - } else { - done = true; + return; } + done = true; } function stop(close) { if (close) { close(); } @@ -3781,6 +4543,7 @@ vim.exMode = false; vim.lastHPos = vim.lastHSPos = lastPos.ch; } + if (callback) { callback(); } } function onPromptKeyDown(e, _value, close) { // Swallow all keys. @@ -3792,7 +4555,13 @@ case 'N': next(); break; case 'A': - cm.operation(replaceAll); break; + // replaceAll contains a call to close of its own. We don't want it + // to fire too early or multiple times. + var savedCallback = callback; + callback = undefined; + cm.operation(replaceAll); + callback = savedCallback; + break; case 'L': replace(); // fall through and exit. @@ -3804,6 +4573,7 @@ break; } if (done) { stop(close); } + return true; } // Actually do replace. @@ -3814,6 +4584,7 @@ } if (!confirm) { replaceAll(); + if (callback) { callback(); }; return; } showPrompt(cm, { @@ -3822,72 +4593,44 @@ }); } - // Register Vim with CodeMirror - function buildVimKeyMap() { - /** - * Handle the raw key event from CodeMirror. Translate the - * Shift + key modifier to the resulting letter, while preserving other - * modifers. - */ - function cmKeyToVimKey(key, modifier) { - var vimKey = key; - if (isUpperCase(vimKey) && modifier == 'Ctrl') { - vimKey = vimKey.toLowerCase(); - } - if (modifier) { - // Vim will parse modifier+key combination as a single key. - vimKey = modifier.charAt(0) + '-' + vimKey; - } - var specialKey = ({Enter:'CR',Backspace:'BS',Delete:'Del'})[vimKey]; - vimKey = specialKey ? specialKey : vimKey; - vimKey = vimKey.length > 1 ? '<'+ vimKey + '>' : vimKey; - return vimKey; - } - - // Closure to bind CodeMirror, key, modifier. - function keyMapper(vimKey) { - return function(cm) { - CodeMirror.Vim.handleKey(cm, vimKey); - }; - } - - var cmToVimKeymap = { - 'nofallthrough': true, - 'style': 'fat-cursor' - }; - function bindKeys(keys, modifier) { - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (!modifier && key.length == 1) { - // Wrap all keys without modifiers with '' to identify them by their - // key characters instead of key identifiers. - key = "'" + key + "'"; - } - var vimKey = cmKeyToVimKey(keys[i], modifier); - var cmKey = modifier ? modifier + '-' + key : key; - cmToVimKeymap[cmKey] = keyMapper(vimKey); - } - } - bindKeys(upperCaseAlphabet); - bindKeys(lowerCaseAlphabet); - bindKeys(upperCaseAlphabet, 'Ctrl'); - bindKeys(specialSymbols); - bindKeys(specialSymbols, 'Ctrl'); - bindKeys(numbers); - bindKeys(numbers, 'Ctrl'); - bindKeys(specialKeys); - bindKeys(specialKeys, 'Ctrl'); - return cmToVimKeymap; - } - CodeMirror.keyMap.vim = buildVimKeyMap(); + CodeMirror.keyMap.vim = { + attach: attachVimMap, + detach: detachVimMap, + call: cmKey + }; function exitInsertMode(cm) { var vim = cm.state.vim; var macroModeState = vimGlobalState.macroModeState; + var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.'); var isPlaying = macroModeState.isPlaying; + var lastChange = macroModeState.lastInsertModeChanges; + // In case of visual block, the insertModeChanges are not saved as a + // single word, so we convert them to a single word + // so as to update the ". register as expected in real vim. + var text = []; if (!isPlaying) { + var selLength = lastChange.inVisualBlock ? vim.lastSelection.visualBlock.height : 1; + var changes = lastChange.changes; + var text = []; + var i = 0; + // In case of multiple selections in blockwise visual, + // the inserted text, for example: 'foo', is stored as + // 'f', 'f', InsertModeKey 'o', 'o', 'o', 'o'. (if you have a block with 2 lines). + // We push the contents of the changes array as per the following: + // 1. In case of InsertModeKey, just increment by 1. + // 2. In case of a character, jump by selLength (2 in the example). + while (i < changes.length) { + // This loop will convert 'ffoooo' to 'foo'. + text.push(changes[i]); + if (changes[i] instanceof InsertModeKey) { + i++; + } else { + i+= selLength; + } + } + lastChange.changes = text; cm.off('change', onChange); - cm.off('cursorActivity', onCursorActivity); CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); } if (!isPlaying && vim.insertModeRepeat > 1) { @@ -3897,23 +4640,39 @@ vim.lastEditInputState.repeatOverride = vim.insertModeRepeat; } delete vim.insertModeRepeat; - cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1); vim.insertMode = false; + cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1); cm.setOption('keyMap', 'vim'); cm.setOption('disableInput', true); cm.toggleOverwrite(false); // exit replace mode if we were in it. + // update the ". register before exiting insert mode + insertModeChangeRegister.setText(lastChange.changes.join('')); CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); if (macroModeState.isRecording) { logInsertModeChange(macroModeState); } } + function _mapCommand(command) { + defaultKeymap.push(command); + } + + function mapCommand(keys, type, name, args, extra) { + var command = {keys: keys, type: type}; + command[type] = name; + command[type + "Args"] = args; + for (var key in extra) + command[key] = extra[key]; + _mapCommand(command); + } + + // The timeout in milliseconds for the two-character ESC keymap should be + // adjusted according to your typing speed to prevent false positives. + defineOption('insertModeEscKeysTimeout', 200, 'number'); + CodeMirror.keyMap['vim-insert'] = { // TODO: override navigation keys so that Esc will cancel automatic // indentation from o, O, i_ - 'Esc': exitInsertMode, - 'Ctrl-[': exitInsertMode, - 'Ctrl-C': exitInsertMode, 'Ctrl-N': 'autocomplete', 'Ctrl-P': 'autocomplete', 'Enter': function(cm) { @@ -3921,12 +4680,18 @@ CodeMirror.commands.newlineAndIndent; fn(cm); }, - fallthrough: ['default'] + fallthrough: ['default'], + attach: attachVimMap, + detach: detachVimMap, + call: cmKey }; CodeMirror.keyMap['vim-replace'] = { 'Backspace': 'goCharLeft', - fallthrough: ['vim-insert'] + fallthrough: ['vim-insert'], + attach: attachVimMap, + detach: detachVimMap, + call: cmKey }; function executeMacroRegister(cm, vim, macroModeState, registerName) { @@ -3934,6 +4699,7 @@ var keyBuffer = register.keyBuffer; var imc = 0; macroModeState.isPlaying = true; + macroModeState.replaySearchQueries = register.searchQueries.slice(0); for (var i = 0; i < keyBuffer.length; i++) { var text = keyBuffer[i]; var match, key; @@ -3943,10 +4709,12 @@ match = (/<\w+-.+?>|<\w+>|./).exec(text); key = match[0]; text = text.substring(match.index + key.length); - CodeMirror.Vim.handleKey(cm, key); + CodeMirror.Vim.handleKey(cm, key, 'macro'); if (vim.insertMode) { - repeatInsertModeChanges( - cm, register.insertModeChanges[imc++].changes, 1); + var changes = register.insertModeChanges[imc++].changes; + vimGlobalState.macroModeState.lastInsertModeChanges.changes = + changes; + repeatInsertModeChanges(cm, changes, 1); exitInsertMode(cm); } } @@ -3972,6 +4740,15 @@ } } + function logSearchQuery(macroModeState, query) { + if (macroModeState.isPlaying) { return; } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register) { + register.pushSearchQuery(query); + } + } + /** * Listens for changes made in insert mode. * Should only be active in insert mode. @@ -3995,18 +4772,63 @@ /** * Listens for any kind of cursor activity on CodeMirror. - * - For tracking cursor activity in insert mode. - * - Should only be active in insert mode. */ - function onCursorActivity() { - var macroModeState = vimGlobalState.macroModeState; - if (macroModeState.isPlaying) { return; } - var lastChange = macroModeState.lastInsertModeChanges; - if (lastChange.expectCursorActivityForChange) { - lastChange.expectCursorActivityForChange = false; - } else { - // Cursor moved outside the context of an edit. Reset the change. - lastChange.changes = []; + function onCursorActivity(cm) { + var vim = cm.state.vim; + if (vim.insertMode) { + // Tracking cursor activity in insert mode (for macro support). + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { return; } + var lastChange = macroModeState.lastInsertModeChanges; + if (lastChange.expectCursorActivityForChange) { + lastChange.expectCursorActivityForChange = false; + } else { + // Cursor moved outside the context of an edit. Reset the change. + lastChange.changes = []; + } + } else if (!cm.curOp.isVimOp) { + handleExternalSelection(cm, vim); + } + if (vim.visualMode) { + updateFakeCursor(cm); + } + } + function updateFakeCursor(cm) { + var vim = cm.state.vim; + var from = copyCursor(vim.sel.head); + var to = offsetCursor(from, 0, 1); + if (vim.fakeCursor) { + vim.fakeCursor.clear(); + } + vim.fakeCursor = cm.markText(from, to, {className: 'cm-animate-fat-cursor'}); + } + function handleExternalSelection(cm, vim) { + var anchor = cm.getCursor('anchor'); + var head = cm.getCursor('head'); + // Enter or exit visual mode to match mouse selection. + if (vim.visualMode && cursorEqual(head, anchor) && lineLength(cm, head.line) > head.ch) { + exitVisualMode(cm, false); + } else if (!vim.visualMode && !vim.insertMode && cm.somethingSelected()) { + vim.visualMode = true; + vim.visualLine = false; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); + } + if (vim.visualMode) { + // Bind CodeMirror selection model to vim selection model. + // Mouse selections are considered visual characterwise. + var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0; + var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0; + head = offsetCursor(head, 0, headOffset); + anchor = offsetCursor(anchor, 0, anchorOffset); + vim.sel = { + anchor: anchor, + head: head + }; + updateMark(cm, vim, '<', cursorMin(head, anchor)); + updateMark(cm, vim, '>', cursorMax(head, anchor)); + } else if (!vim.insertMode) { + // Reset lastHPos if selection was modified by something outside of vim mode e.g. by mouse. + vim.lastHPos = cm.getCursor().ch; } } @@ -4024,12 +4846,13 @@ var macroModeState = vimGlobalState.macroModeState; var lastChange = macroModeState.lastInsertModeChanges; var keyName = CodeMirror.keyName(e); + if (!keyName) { return; } function onKeyFound() { lastChange.changes.push(new InsertModeKey(keyName)); return true; } if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) { - CodeMirror.lookupKey(keyName, ['vim-insert'], onKeyFound); + CodeMirror.lookupKey(keyName, 'vim-insert', onKeyFound); } } @@ -4060,11 +4883,7 @@ // insert mode changes. Will conform to that behavior. repeat = !vim.lastEditActionCommand ? 1 : repeat; var changeObject = macroModeState.lastInsertModeChanges; - // This isn't strictly necessary, but since lastInsertModeChanges is - // supposed to be immutable during replay, this helps catch bugs. - macroModeState.lastInsertModeChanges = {}; repeatInsertModeChanges(cm, changeObject.changes, repeat); - macroModeState.lastInsertModeChanges = changeObject; } } vim.inputState = vim.lastEditInputState; @@ -4102,17 +4921,34 @@ } return true; } + var head = cm.getCursor('head'); + var inVisualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.inVisualBlock; + if (inVisualBlock) { + // Set up block selection again for repeating the changes. + var vim = cm.state.vim; + var lastSel = vim.lastSelection; + var offset = getOffset(lastSel.anchor, lastSel.head); + selectForInsert(cm, head, offset.line + 1); + repeat = cm.listSelections().length; + cm.setCursor(head); + } for (var i = 0; i < repeat; i++) { + if (inVisualBlock) { + cm.setCursor(offsetCursor(head, i, 0)); + } for (var j = 0; j < changes.length; j++) { var change = changes[j]; if (change instanceof InsertModeKey) { - CodeMirror.lookupKey(change.keyName, ['vim-insert'], keyHandler); + CodeMirror.lookupKey(change.keyName, 'vim-insert', keyHandler); } else { var cur = cm.getCursor(); cm.replaceRange(change, cur, cur); } } } + if (inVisualBlock) { + cm.setCursor(offsetCursor(head, 0, 1)); + } } resetVimGlobalState(); diff --git a/applications/admin/static/codemirror/lib/codemirror.css b/applications/admin/static/codemirror/lib/codemirror.css index d263e44b..c56510e9 100644 --- a/applications/admin/static/codemirror/lib/codemirror.css +++ b/applications/admin/static/codemirror/lib/codemirror.css @@ -5,10 +5,6 @@ font-family: monospace; height: 300px; } -.CodeMirror-scroll { - /* Set scrolling behaviour here */ - overflow: auto; -} /* PADDING */ @@ -40,6 +36,9 @@ box-sizing: content-box; } +.CodeMirror-guttermarker { color: black; } +.CodeMirror-guttermarker-subtle { color: #999; } + /* CURSOR */ .CodeMirror div.CodeMirror-cursor { @@ -49,15 +48,42 @@ .CodeMirror div.CodeMirror-secondarycursor { border-left: 1px solid silver; } -.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { +.CodeMirror.cm-fat-cursor div.CodeMirror-cursor { width: auto; border: 0; background: #7e7; } +.CodeMirror.cm-fat-cursor div.CodeMirror-cursors { + z-index: 1; +} + +.cm-animate-fat-cursor { + width: auto; + border: 0; + -webkit-animation: blink 1.06s steps(1) infinite; + -moz-animation: blink 1.06s steps(1) infinite; + animation: blink 1.06s steps(1) infinite; +} +@-moz-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@-webkit-keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} +@keyframes blink { + 0% { background: #7e7; } + 50% { background: none; } + 100% { background: #7e7; } +} + /* Can style cursor different in overwrite (non-insert) mode */ div.CodeMirror-overwrite div.CodeMirror-cursor {} -.cm-tab { display: inline-block; } +.cm-tab { display: inline-block; text-decoration: inherit; } .CodeMirror-ruler { border-left: 1px solid #ccc; @@ -70,11 +96,12 @@ div.CodeMirror-overwrite div.CodeMirror-cursor {} .cm-s-default .cm-atom {color: #219;} .cm-s-default .cm-number {color: #164;} .cm-s-default .cm-def {color: #00f;} -.cm-s-default .cm-variable {color: black;} +.cm-s-default .cm-variable, +.cm-s-default .cm-punctuation, +.cm-s-default .cm-property, +.cm-s-default .cm-operator {} .cm-s-default .cm-variable-2 {color: #05a;} .cm-s-default .cm-variable-3 {color: #085;} -.cm-s-default .cm-property {color: black;} -.cm-s-default .cm-operator {color: black;} .cm-s-default .cm-comment {color: #a50;} .cm-s-default .cm-string {color: #a11;} .cm-s-default .cm-string-2 {color: #f50;} @@ -94,12 +121,16 @@ div.CodeMirror-overwrite div.CodeMirror-cursor {} .cm-header, .cm-strong {font-weight: bold;} .cm-em {font-style: italic;} .cm-link {text-decoration: underline;} +.cm-strikethrough {text-decoration: line-through;} .cm-s-default .cm-error {color: #f00;} .cm-invalidchar {color: #f00;} +/* Default styles for common addons */ + div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } .CodeMirror-activeline-background {background: #e8f2ff;} /* STOP */ @@ -116,6 +147,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} } .CodeMirror-scroll { + overflow: scroll !important; /* Things will break if this is overridden */ /* 30px is the magic margin used to hide the element's real scrollbars */ /* See overflow: hidden in .CodeMirror */ margin-bottom: -30px; margin-right: -30px; @@ -160,7 +192,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} .CodeMirror-gutters { position: absolute; left: 0; top: 0; - padding-bottom: 30px; z-index: 3; } .CodeMirror-gutter { @@ -168,13 +199,17 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} height: 100%; -moz-box-sizing: content-box; box-sizing: content-box; - padding-bottom: 30px; - margin-bottom: -32px; display: inline-block; + margin-bottom: -30px; /* Hack to make IE7 behave */ *zoom:1; *display:inline; } +.CodeMirror-gutter-wrapper { + position: absolute; + z-index: 4; + height: 100%; +} .CodeMirror-gutter-elt { position: absolute; cursor: default; @@ -183,6 +218,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} .CodeMirror-lines { cursor: text; + min-height: 1px; /* prevents collapsing before first draw */ } .CodeMirror pre { /* Reset some styles that the rest of the page might have set */ @@ -220,10 +256,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} .CodeMirror-widget {} -.CodeMirror-wrap .CodeMirror-scroll { - overflow-x: hidden; -} - .CodeMirror-measure { position: absolute; width: 100%; @@ -242,7 +274,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} div.CodeMirror-cursors { visibility: hidden; position: relative; - z-index: 1; + z-index: 3; } .CodeMirror-focused div.CodeMirror-cursors { visibility: visible; @@ -250,6 +282,7 @@ div.CodeMirror-cursors { .CodeMirror-selected { background: #d9d9d9; } .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } +.CodeMirror-crosshair { cursor: crosshair; } .cm-searching { background: #ffa; @@ -268,3 +301,9 @@ div.CodeMirror-cursors { visibility: hidden; } } + +/* See issue #2901 */ +.cm-tab-wrap-hack:after { content: ''; } + +/* Help users use markselection to safely style text background */ +span.CodeMirror-selectedtext { background: none; } diff --git a/applications/admin/static/codemirror/lib/codemirror.js b/applications/admin/static/codemirror/lib/codemirror.js index c3205cc1..0e3d0192 100644 --- a/applications/admin/static/codemirror/lib/codemirror.js +++ b/applications/admin/static/codemirror/lib/codemirror.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + // This is CodeMirror (http://codemirror.net), a code editor // implemented in JavaScript on top of the browser's DOM. // @@ -22,18 +25,15 @@ var gecko = /gecko\/\d/i.test(navigator.userAgent); // ie_uptoN means Internet Explorer version N or lower var ie_upto10 = /MSIE \d/.test(navigator.userAgent); - var ie_upto7 = ie_upto10 && (document.documentMode == null || document.documentMode < 8); - var ie_upto8 = ie_upto10 && (document.documentMode == null || document.documentMode < 9); - var ie_upto9 = ie_upto10 && (document.documentMode == null || document.documentMode < 10); - var ie_11up = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent); + var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent); var ie = ie_upto10 || ie_11up; + var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : ie_11up[1]); var webkit = /WebKit\//.test(navigator.userAgent); var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); var chrome = /Chrome\//.test(navigator.userAgent); var presto = /Opera\//.test(navigator.userAgent); var safari = /Apple Computer/.test(navigator.vendor); var khtml = /KHTML\//.test(navigator.userAgent); - var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent); var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent); var phantom = /PhantomJS/.test(navigator.userAgent); @@ -48,7 +48,7 @@ if (presto_version && presto_version >= 15) { presto = false; webkit = true; } // Some browsers use the wrong event properties to signal cmd/ctrl on OS X var flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)); - var captureRightClick = gecko || (ie && !ie_upto8); + var captureRightClick = gecko || (ie && ie_version >= 9); // Optimize some code when these features are not used. var sawReadOnlySpans = false, sawCollapsedSpans = false; @@ -61,10 +61,9 @@ function CodeMirror(place, options) { if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); - this.options = options = options || {}; + this.options = options = options ? copyObj(options) : {}; // Determine effective options based on given values and defaults. - for (var opt in defaults) if (!options.hasOwnProperty(opt)) - options[opt] = defaults[opt]; + copyObj(defaults, options, false); setGuttersForLineNumbers(options); var doc = options.value; @@ -78,6 +77,7 @@ if (options.lineWrapping) this.display.wrapper.className += " CodeMirror-wrap"; if (options.autofocus && !mobile) focusInput(this); + initScrollbars(this); this.state = { keyMaps: [], // stores maps added by addKeyMap @@ -87,29 +87,37 @@ suppressEdits: false, // used to disable editing during key handlers when in readOnly mode pasteIncoming: false, cutIncoming: false, // help recognize paste/cut edits in readInput draggingText: false, - highlight: new Delayed() // stores highlight worker timeout + highlight: new Delayed(), // stores highlight worker timeout + keySeq: null // Unfinished key sequence }; // Override magic textarea content restore that IE sometimes does // on our hidden textarea on reload - if (ie_upto10) setTimeout(bind(resetInput, this, true), 20); + if (ie && ie_version < 11) setTimeout(bind(resetInput, this, true), 20); registerEventHandlers(this); + ensureGlobalHandlers(); - var cm = this; - runInOp(this, function() { - cm.curOp.forceUpdate = true; - attachDoc(cm, doc); + startOperation(this); + this.curOp.forceUpdate = true; + attachDoc(this, doc); - if ((options.autofocus && !mobile) || activeElt() == display.input) - setTimeout(bind(onFocus, cm), 20); - else - onBlur(cm); + if ((options.autofocus && !mobile) || activeElt() == display.input) + setTimeout(bind(onFocus, this), 20); + else + onBlur(this); - for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) - optionHandlers[opt](cm, options[opt], Init); - for (var i = 0; i < initHooks.length; ++i) initHooks[i](cm); - }); + for (var opt in optionHandlers) if (optionHandlers.hasOwnProperty(opt)) + optionHandlers[opt](this, options[opt], Init); + maybeUpdateLineNumberWidth(this); + if (options.finishInit) options.finishInit(this); + for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); + endOperation(this); + // Suppress optimizelegibility in Webkit, since it breaks text + // measuring on line wrapping boundaries. + if (webkit && options.lineWrapping && + getComputedStyle(display.lineDiv).textRendering == "optimizelegibility") + display.lineDiv.style.textRendering = "auto"; } // DISPLAY CONSTRUCTOR @@ -136,14 +144,13 @@ // Wraps and hides input textarea d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); - // The fake scrollbar elements. - d.scrollbarH = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); - d.scrollbarV = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); // Covers bottom-right square when both scrollbars are present. d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); + d.scrollbarFiller.setAttribute("not-content", "true"); // Covers bottom of gutter when coverGutterNextToScrollbar is on // and h scrollbar is present. d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); + d.gutterFiller.setAttribute("not-content", "true"); // Will contain the actual code, positioned to cover the viewport. d.lineDiv = elt("div", null, "CodeMirror-code"); // Elements are added to these to represent selection and cursors. @@ -160,10 +167,11 @@ d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); // Set to the height of the document, allowing scrolling. d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); + d.sizerWidth = null; // Behavior of elts with overflow: auto and padding is // inconsistent across browsers. This is used to ensure the // scrollable area is big enough. - d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;"); + d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerGap + "px; width: 1px;"); // Will contain the gutters, if any. d.gutters = elt("div", null, "CodeMirror-gutters"); d.lineGutter = null; @@ -171,34 +179,38 @@ d.scroller = elt("div", [d.sizer, d.heightForcer, d.gutters], "CodeMirror-scroll"); d.scroller.setAttribute("tabIndex", "-1"); // The element in which the editor lives. - d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV, - d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); + d.wrapper = elt("div", [d.inputDiv, d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror"); // Work around IE7 z-index bug (not perfect, hence IE7 not really being supported) - if (ie_upto7) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } + if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } // Needed to hide big blue blinking cursor on Mobile Safari if (ios) input.style.width = "0px"; if (!webkit) d.scroller.draggable = true; // Needed to handle Tab key in KHTML if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; } - // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). - if (ie_upto7) d.scrollbarH.style.minHeight = d.scrollbarV.style.minWidth = "18px"; - if (place.appendChild) place.appendChild(d.wrapper); - else place(d.wrapper); + if (place) { + if (place.appendChild) place.appendChild(d.wrapper); + else place(d.wrapper); + } // Current rendered range (may be bigger than the view window). d.viewFrom = d.viewTo = doc.first; + d.reportedViewFrom = d.reportedViewTo = doc.first; // Information about the rendered lines. d.view = []; + d.renderedView = null; // Holds info about a single rendered line when it was rendered // for measurement, while not in view. d.externalMeasured = null; // Empty space (in pixels) above the view d.viewOffset = 0; - d.lastSizeC = 0; + d.lastWrapHeight = d.lastWrapWidth = 0; d.updateLineNumbers = null; + d.nativeBarWidth = d.barHeight = d.barWidth = 0; + d.scrollbarsClipped = false; + // Used to only resize the line number gutter when necessary (when // the amount of lines crosses a boundary that makes its width change) d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null; @@ -232,6 +244,10 @@ // True when shift is held down. d.shift = false; + + // Used to track whether anything happened since the context menu + // was opened. + d.selForContextMenu = null; } // STATE UPDATES @@ -256,10 +272,11 @@ function wrappingChanged(cm) { if (cm.options.lineWrapping) { - cm.display.wrapper.className += " CodeMirror-wrap"; + addClass(cm.display.wrapper, "CodeMirror-wrap"); cm.display.sizer.style.minWidth = ""; + cm.display.sizerWidth = null; } else { - cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", ""); + rmClass(cm.display.wrapper, "CodeMirror-wrap"); findMaxLine(cm); } estimateLineHeights(cm); @@ -297,12 +314,6 @@ }); } - function keyMapChanged(cm) { - var map = keyMap[cm.options.keyMap], style = map.style; - cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + - (style ? " cm-keymap-" + style : ""); - } - function themeChanged(cm) { cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-"); @@ -329,9 +340,12 @@ } } gutters.style.display = i ? "" : "none"; - var width = gutters.offsetWidth; + updateGutterSpace(cm); + } + + function updateGutterSpace(cm) { + var width = cm.display.gutters.offsetWidth; cm.display.sizer.style.marginLeft = width + "px"; - if (i) cm.display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0; } // Compute the character length of a line, taking into account @@ -387,85 +401,187 @@ // Prepare DOM reads needed to update the scrollbars. Done in one // shot to minimize update/measure roundtrips. function measureForScrollbars(cm) { - var scroll = cm.display.scroller; + var d = cm.display, gutterW = d.gutters.offsetWidth; + var docH = Math.round(cm.doc.height + paddingVert(cm.display)); return { - clientHeight: scroll.clientHeight, - barHeight: cm.display.scrollbarV.clientHeight, - scrollWidth: scroll.scrollWidth, clientWidth: scroll.clientWidth, - barWidth: cm.display.scrollbarH.clientWidth, - docHeight: Math.round(cm.doc.height + paddingVert(cm.display)) + clientHeight: d.scroller.clientHeight, + viewHeight: d.wrapper.clientHeight, + scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, + viewWidth: d.wrapper.clientWidth, + barLeft: cm.options.fixedGutter ? gutterW : 0, + docHeight: docH, + scrollHeight: docH + scrollGap(cm) + d.barHeight, + nativeBarWidth: d.nativeBarWidth, + gutterWidth: gutterW }; } + function NativeScrollbars(place, scroll, cm) { + this.cm = cm; + var vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar"); + var horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar"); + place(vert); place(horiz); + + on(vert, "scroll", function() { + if (vert.clientHeight) scroll(vert.scrollTop, "vertical"); + }); + on(horiz, "scroll", function() { + if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal"); + }); + + this.checkedOverlay = false; + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px"; + } + + NativeScrollbars.prototype = copyObj({ + update: function(measure) { + var needsH = measure.scrollWidth > measure.clientWidth + 1; + var needsV = measure.scrollHeight > measure.clientHeight + 1; + var sWidth = measure.nativeBarWidth; + + if (needsV) { + this.vert.style.display = "block"; + this.vert.style.bottom = needsH ? sWidth + "px" : "0"; + var totalHeight = measure.viewHeight - (needsH ? sWidth : 0); + // A bug in IE8 can cause this value to be negative, so guard it. + this.vert.firstChild.style.height = + Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px"; + } else { + this.vert.style.display = ""; + this.vert.firstChild.style.height = "0"; + } + + if (needsH) { + this.horiz.style.display = "block"; + this.horiz.style.right = needsV ? sWidth + "px" : "0"; + this.horiz.style.left = measure.barLeft + "px"; + var totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0); + this.horiz.firstChild.style.width = + (measure.scrollWidth - measure.clientWidth + totalWidth) + "px"; + } else { + this.horiz.style.display = ""; + this.horiz.firstChild.style.width = "0"; + } + + if (!this.checkedOverlay && measure.clientHeight > 0) { + if (sWidth == 0) this.overlayHack(); + this.checkedOverlay = true; + } + + return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0}; + }, + setScrollLeft: function(pos) { + if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos; + }, + setScrollTop: function(pos) { + if (this.vert.scrollTop != pos) this.vert.scrollTop = pos; + }, + overlayHack: function() { + var w = mac && !mac_geMountainLion ? "12px" : "18px"; + this.horiz.style.minHeight = this.vert.style.minWidth = w; + var self = this; + var barMouseDown = function(e) { + if (e_target(e) != self.vert && e_target(e) != self.horiz) + operation(self.cm, onMouseDown)(e); + }; + on(this.vert, "mousedown", barMouseDown); + on(this.horiz, "mousedown", barMouseDown); + }, + clear: function() { + var parent = this.horiz.parentNode; + parent.removeChild(this.horiz); + parent.removeChild(this.vert); + } + }, NativeScrollbars.prototype); + + function NullScrollbars() {} + + NullScrollbars.prototype = copyObj({ + update: function() { return {bottom: 0, right: 0}; }, + setScrollLeft: function() {}, + setScrollTop: function() {}, + clear: function() {} + }, NullScrollbars.prototype); + + CodeMirror.scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars}; + + function initScrollbars(cm) { + if (cm.display.scrollbars) { + cm.display.scrollbars.clear(); + if (cm.display.scrollbars.addClass) + rmClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + cm.display.scrollbars = new CodeMirror.scrollbarModel[cm.options.scrollbarStyle](function(node) { + cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller); + on(node, "mousedown", function() { + if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); + }); + node.setAttribute("not-content", "true"); + }, function(pos, axis) { + if (axis == "horizontal") setScrollLeft(cm, pos); + else setScrollTop(cm, pos); + }, cm); + if (cm.display.scrollbars.addClass) + addClass(cm.display.wrapper, cm.display.scrollbars.addClass); + } + + function updateScrollbars(cm, measure) { + if (!measure) measure = measureForScrollbars(cm); + var startWidth = cm.display.barWidth, startHeight = cm.display.barHeight; + updateScrollbarsInner(cm, measure); + for (var i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { + if (startWidth != cm.display.barWidth && cm.options.lineWrapping) + updateHeightsInViewport(cm); + updateScrollbarsInner(cm, measureForScrollbars(cm)); + startWidth = cm.display.barWidth; startHeight = cm.display.barHeight; + } + } + // Re-synchronize the fake scrollbars with the actual size of the // content. - function updateScrollbars(cm, measure) { - if (!measure) measure = measureForScrollbars(cm); + function updateScrollbarsInner(cm, measure) { var d = cm.display; - var scrollHeight = measure.docHeight + scrollerCutOff; - var needsH = measure.scrollWidth > measure.clientWidth; - var needsV = scrollHeight > measure.clientHeight; - if (needsV) { - d.scrollbarV.style.display = "block"; - d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; - // A bug in IE8 can cause this value to be negative, so guard it. - d.scrollbarV.firstChild.style.height = - Math.max(0, scrollHeight - measure.clientHeight + (measure.barHeight || d.scrollbarV.clientHeight)) + "px"; - } else { - d.scrollbarV.style.display = ""; - d.scrollbarV.firstChild.style.height = "0"; - } - if (needsH) { - d.scrollbarH.style.display = "block"; - d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0"; - d.scrollbarH.firstChild.style.width = - (measure.scrollWidth - measure.clientWidth + (measure.barWidth || d.scrollbarH.clientWidth)) + "px"; - } else { - d.scrollbarH.style.display = ""; - d.scrollbarH.firstChild.style.width = "0"; - } - if (needsH && needsV) { - d.scrollbarFiller.style.display = "block"; - d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px"; - } else d.scrollbarFiller.style.display = ""; - if (needsH && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { - d.gutterFiller.style.display = "block"; - d.gutterFiller.style.height = scrollbarWidth(d.measure) + "px"; - d.gutterFiller.style.width = d.gutters.offsetWidth + "px"; - } else d.gutterFiller.style.display = ""; + var sizes = d.scrollbars.update(measure); - if (mac_geLion && scrollbarWidth(d.measure) === 0) { - d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; - var barMouseDown = function(e) { - if (e_target(e) != d.scrollbarV && e_target(e) != d.scrollbarH) - operation(cm, onMouseDown)(e); - }; - on(d.scrollbarV, "mousedown", barMouseDown); - on(d.scrollbarH, "mousedown", barMouseDown); - } + d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px"; + d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px"; + + if (sizes.right && sizes.bottom) { + d.scrollbarFiller.style.display = "block"; + d.scrollbarFiller.style.height = sizes.bottom + "px"; + d.scrollbarFiller.style.width = sizes.right + "px"; + } else d.scrollbarFiller.style.display = ""; + if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { + d.gutterFiller.style.display = "block"; + d.gutterFiller.style.height = sizes.bottom + "px"; + d.gutterFiller.style.width = measure.gutterWidth + "px"; + } else d.gutterFiller.style.display = ""; } // Compute the lines that are visible in a given viewport (defaults - // the the current scroll position). viewPort may contain top, + // the the current scroll position). viewport may contain top, // height, and ensure (see op.scrollToPos) properties. - function visibleLines(display, doc, viewPort) { - var top = viewPort && viewPort.top != null ? viewPort.top : display.scroller.scrollTop; + function visibleLines(display, doc, viewport) { + var top = viewport && viewport.top != null ? Math.max(0, viewport.top) : display.scroller.scrollTop; top = Math.floor(top - paddingTop(display)); - var bottom = viewPort && viewPort.bottom != null ? viewPort.bottom : top + display.wrapper.clientHeight; + var bottom = viewport && viewport.bottom != null ? viewport.bottom : top + display.wrapper.clientHeight; var from = lineAtHeight(doc, top), to = lineAtHeight(doc, bottom); // Ensure is a {from: {line, ch}, to: {line, ch}} object, and // forces those lines into the viewport (if possible). - if (viewPort && viewPort.ensure) { - var ensureFrom = viewPort.ensure.from.line, ensureTo = viewPort.ensure.to.line; - if (ensureFrom < from) - return {from: ensureFrom, - to: lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight)}; - if (Math.min(ensureTo, doc.lastLine()) >= to) - return {from: lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight), - to: ensureTo}; + if (viewport && viewport.ensure) { + var ensureFrom = viewport.ensure.from.line, ensureTo = viewport.ensure.to.line; + if (ensureFrom < from) { + from = ensureFrom; + to = lineAtHeight(doc, heightAtLine(getLine(doc, ensureFrom)) + display.wrapper.clientHeight); + } else if (Math.min(ensureTo, doc.lastLine()) >= to) { + from = lineAtHeight(doc, heightAtLine(getLine(doc, ensureTo)) - display.wrapper.clientHeight); + to = ensureTo; + } } - return {from: from, to: to}; + return {from: from, to: Math.max(to, from + 1)}; } // LINE NUMBERS @@ -503,9 +619,7 @@ display.lineNumWidth = display.lineNumInnerWidth + padding; display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; display.lineGutter.style.width = display.lineNumWidth + "px"; - var width = display.gutters.offsetWidth; - display.scrollbarH.style.left = cm.options.fixedGutter ? width + "px" : 0; - display.sizer.style.marginLeft = width + "px"; + updateGutterSpace(cm); return true; } return false; @@ -524,76 +638,68 @@ // DISPLAY DRAWING - // Updates the display, selection, and scrollbars, using the - // information in display.view to find out which nodes are no longer - // up-to-date. Tries to bail out early when no changes are needed, - // unless forced is true. - // Returns true if an actual update happened, false otherwise. - function updateDisplay(cm, viewPort, forced) { - var oldFrom = cm.display.viewFrom, oldTo = cm.display.viewTo, updated; - var visible = visibleLines(cm.display, cm.doc, viewPort); - for (var first = true;; first = false) { - var oldWidth = cm.display.scroller.clientWidth; - if (!updateDisplayInner(cm, visible, forced)) break; - updated = true; + function DisplayUpdate(cm, viewport, force) { + var display = cm.display; - // If the max line changed since it was last measured, measure it, - // and ensure the document's width matches it. - if (cm.display.maxLineChanged && !cm.options.lineWrapping) - adjustContentWidth(cm); + this.viewport = viewport; + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport); + this.editorIsHidden = !display.wrapper.offsetWidth; + this.wrapperHeight = display.wrapper.clientHeight; + this.wrapperWidth = display.wrapper.clientWidth; + this.oldDisplayWidth = displayWidth(cm); + this.force = force; + this.dims = getDimensions(cm); + this.events = []; + } - var barMeasure = measureForScrollbars(cm); - updateSelection(cm); - setDocumentHeight(cm, barMeasure); - updateScrollbars(cm, barMeasure); - if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) { - forced = true; - continue; - } - forced = false; + DisplayUpdate.prototype.signal = function(emitter, type) { + if (hasHandler(emitter, type)) + this.events.push(arguments); + }; + DisplayUpdate.prototype.finish = function() { + for (var i = 0; i < this.events.length; i++) + signal.apply(null, this.events[i]); + }; - // Clip forced viewport to actual scrollable area. - if (viewPort && viewPort.top != null) - viewPort = {top: Math.min(barMeasure.docHeight - scrollerCutOff - barMeasure.clientHeight, viewPort.top)}; - // Updated line heights might result in the drawn area not - // actually covering the viewport. Keep looping until it does. - visible = visibleLines(cm.display, cm.doc, viewPort); - if (visible.from >= cm.display.viewFrom && visible.to <= cm.display.viewTo) - break; + function maybeClipScrollbars(cm) { + var display = cm.display; + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth; + display.heightForcer.style.height = scrollGap(cm) + "px"; + display.sizer.style.marginBottom = -display.nativeBarWidth + "px"; + display.sizer.style.borderRightWidth = scrollGap(cm) + "px"; + display.scrollbarsClipped = true; } - - cm.display.updateLineNumbers = null; - if (updated) { - signalLater(cm, "update", cm); - if (cm.display.viewFrom != oldFrom || cm.display.viewTo != oldTo) - signalLater(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); - } - return updated; } // Does the actual updating of the line display. Bails out // (returning false) when there is nothing to be done and forced is // false. - function updateDisplayInner(cm, visible, forced) { + function updateDisplayIfNeeded(cm, update) { var display = cm.display, doc = cm.doc; - if (!display.wrapper.offsetWidth) { + + if (update.editorIsHidden) { resetView(cm); - return; + return false; } // Bail out if the visible area is already rendered and nothing changed. - if (!forced && visible.from >= display.viewFrom && visible.to <= display.viewTo && - countDirtyView(cm) == 0) - return; + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + return false; - if (maybeUpdateLineNumberWidth(cm)) + if (maybeUpdateLineNumberWidth(cm)) { resetView(cm); - var dims = getDimensions(cm); + update.dims = getDimensions(cm); + } // Compute a suitable new viewport (from & to) var end = doc.first + doc.size; - var from = Math.max(visible.from - cm.options.viewportMargin, doc.first); - var to = Math.min(end, visible.to + cm.options.viewportMargin); + var from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first); + var to = Math.min(end, update.visible.to + cm.options.viewportMargin); if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom); if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo); if (sawCollapsedSpans) { @@ -602,7 +708,7 @@ } var different = from != display.viewFrom || to != display.viewTo || - display.lastSizeC != display.wrapper.clientHeight; + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth; adjustView(cm, from, to); display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)); @@ -610,47 +716,87 @@ cm.display.mover.style.top = display.viewOffset + "px"; var toUpdate = countDirtyView(cm); - if (!different && toUpdate == 0 && !forced) return; + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + return false; // For big changes, we hide the enclosing element during the // update, since that speeds up the operations on most browsers. var focused = activeElt(); if (toUpdate > 4) display.lineDiv.style.display = "none"; - patchDisplay(cm, display.updateLineNumbers, dims); + patchDisplay(cm, display.updateLineNumbers, update.dims); if (toUpdate > 4) display.lineDiv.style.display = ""; + display.renderedView = display.view; // There might have been a widget with a focused element that got // hidden or updated, if so re-focus it. if (focused && activeElt() != focused && focused.offsetHeight) focused.focus(); // Prevent selection and cursors from interfering with the scroll - // width. + // width and height. removeChildren(display.cursorDiv); removeChildren(display.selectionDiv); + display.gutters.style.height = 0; if (different) { - display.lastSizeC = display.wrapper.clientHeight; + display.lastWrapHeight = update.wrapperHeight; + display.lastWrapWidth = update.wrapperWidth; startWorker(cm, 400); } - updateHeightsInViewport(cm); + display.updateLineNumbers = null; return true; } - function adjustContentWidth(cm) { - var display = cm.display; - var width = measureChar(cm, display.maxLine, display.maxLine.text.length).left; - display.maxLineChanged = false; - var minWidth = Math.max(0, width + 3); - var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + minWidth + scrollerCutOff - display.scroller.clientWidth); - display.sizer.style.minWidth = minWidth + "px"; - if (maxScrollLeft < cm.doc.scrollLeft) - setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true); + function postUpdateDisplay(cm, update) { + var force = update.force, viewport = update.viewport; + for (var first = true;; first = false) { + if (first && cm.options.lineWrapping && update.oldDisplayWidth != displayWidth(cm)) { + force = true; + } else { + force = false; + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)}; + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport); + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + break; + } + if (!updateDisplayIfNeeded(cm, update)) break; + updateHeightsInViewport(cm); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + } + + update.signal(cm, "update", cm); + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo); + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo; + } + } + + function updateDisplaySimple(cm, viewport) { + var update = new DisplayUpdate(cm, viewport); + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm); + postUpdateDisplay(cm, update); + var barMeasure = measureForScrollbars(cm); + updateSelection(cm); + setDocumentHeight(cm, barMeasure); + updateScrollbars(cm, barMeasure); + update.finish(); + } } function setDocumentHeight(cm, measure) { - cm.display.sizer.style.minHeight = cm.display.heightForcer.style.top = measure.docHeight + "px"; - cm.display.gutters.style.height = Math.max(measure.docHeight, measure.clientHeight - scrollerCutOff) + "px"; + cm.display.sizer.style.minHeight = measure.docHeight + "px"; + var total = measure.docHeight + cm.display.barHeight; + cm.display.heightForcer.style.top = total + "px"; + cm.display.gutters.style.height = Math.max(total + scrollGap(cm), measure.clientHeight) + "px"; } // Read the actual heights of the rendered lines, and update their @@ -661,7 +807,7 @@ for (var i = 0; i < display.view.length; i++) { var cur = display.view[i], height; if (cur.hidden) continue; - if (ie_upto7) { + if (ie && ie_version < 8) { var bot = cur.node.offsetTop + cur.node.offsetHeight; height = bot - prevBottom; prevBottom = bot; @@ -691,9 +837,10 @@ // view, so that we don't interleave reading and writing to the DOM. function getDimensions(cm) { var d = cm.display, left = {}, width = {}; + var gutterLeft = d.gutters.clientLeft; for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) { - left[cm.options.gutters[i]] = n.offsetLeft; - width[cm.options.gutters[i]] = n.offsetWidth; + left[cm.options.gutters[i]] = n.offsetLeft + n.clientLeft + gutterLeft; + width[cm.options.gutters[i]] = n.clientWidth; } return {fixedPos: compensateForHScroll(d), gutterTotalWidth: d.gutters.offsetWidth, @@ -770,7 +917,7 @@ if (lineView.text.parentNode) lineView.text.parentNode.replaceChild(lineView.node, lineView.text); lineView.node.appendChild(lineView.text); - if (ie_upto7) lineView.node.style.zIndex = 2; + if (ie && ie_version < 8) lineView.node.style.zIndex = 2; } return lineView.node; } @@ -836,9 +983,12 @@ if (cm.options.lineNumbers || markers) { var wrap = ensureLineWrapped(lineView); var gutterWrap = lineView.gutter = - wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " + - (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), + wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "left: " + + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + + "px; width: " + dims.gutterTotalWidth + "px"), lineView.text); + if (lineView.line.gutterClass) + gutterWrap.className += " " + lineView.line.gutterClass; if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) lineView.lineNumber = gutterWrap.appendChild( elt("div", lineNumberFor(cm.options, lineN), @@ -890,7 +1040,7 @@ var wrap = ensureLineWrapped(lineView); for (var i = 0, ws = line.widgets; i < ws.length; ++i) { var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget"); - if (!widget.handleMouseEvents) node.ignoreEvents = true; + if (!widget.handleMouseEvents) node.setAttribute("cm-ignore-events", "true"); positionLineWidget(widget, node, lineView, dims); if (allowAbove && widget.above) wrap.insertBefore(node, lineView.gutter || lineView.text); @@ -1126,7 +1276,8 @@ if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) sel = filterSelectionChange(doc, sel); - var bias = cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1; + var bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1); setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)); if (!(options && options.scroll === false) && doc.cm) @@ -1138,9 +1289,10 @@ doc.sel = sel; - if (doc.cm) - doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = - doc.cm.curOp.cursorActivity = true; + if (doc.cm) { + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true; + signalCursorActivity(doc.cm); + } signalLater(doc, "cursorActivity", doc); } @@ -1220,39 +1372,49 @@ // SELECTION DRAWING // Redraw the selection and/or cursor - function updateSelection(cm) { - var display = cm.display, doc = cm.doc; - var curFragment = document.createDocumentFragment(); - var selFragment = document.createDocumentFragment(); + function drawSelection(cm) { + var display = cm.display, doc = cm.doc, result = {}; + var curFragment = result.cursors = document.createDocumentFragment(); + var selFragment = result.selection = document.createDocumentFragment(); for (var i = 0; i < doc.sel.ranges.length; i++) { var range = doc.sel.ranges[i]; var collapsed = range.empty(); if (collapsed || cm.options.showCursorWhenSelecting) - updateSelectionCursor(cm, range, curFragment); + drawSelectionCursor(cm, range, curFragment); if (!collapsed) - updateSelectionRange(cm, range, selFragment); + drawSelectionRange(cm, range, selFragment); } // Move the hidden textarea near the cursor to prevent scrolling artifacts if (cm.options.moveInputWithCursor) { var headPos = cursorCoords(cm, doc.sel.primary().head, "div"); var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect(); - var top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, - headPos.top + lineOff.top - wrapOff.top)); - var left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, - headPos.left + lineOff.left - wrapOff.left)); - display.inputDiv.style.top = top + "px"; - display.inputDiv.style.left = left + "px"; + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)); + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)); } - removeChildrenAndAdd(display.cursorDiv, curFragment); - removeChildrenAndAdd(display.selectionDiv, selFragment); + return result; + } + + function showSelection(cm, drawn) { + removeChildrenAndAdd(cm.display.cursorDiv, drawn.cursors); + removeChildrenAndAdd(cm.display.selectionDiv, drawn.selection); + if (drawn.teTop != null) { + cm.display.inputDiv.style.top = drawn.teTop + "px"; + cm.display.inputDiv.style.left = drawn.teLeft + "px"; + } + } + + function updateSelection(cm) { + showSelection(cm, drawSelection(cm)); } // Draws a cursor for the given range - function updateSelectionCursor(cm, range, output) { - var pos = cursorCoords(cm, range.head, "div"); + function drawSelectionCursor(cm, range, output) { + var pos = cursorCoords(cm, range.head, "div", null, null, !cm.options.singleCursorHeightPerLine); var cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")); cursor.style.left = pos.left + "px"; @@ -1270,13 +1432,16 @@ } // Draws the given range as a highlighted selection - function updateSelectionRange(cm, range, output) { + function drawSelectionRange(cm, range, output) { var display = cm.display, doc = cm.doc; var fragment = document.createDocumentFragment(); - var padding = paddingH(cm.display), leftSide = padding.left, rightSide = display.lineSpace.offsetWidth - padding.right; + var padding = paddingH(cm.display), leftSide = padding.left; + var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right; function add(left, top, width, bottom) { if (top < 0) top = 0; + top = Math.round(top); + bottom = Math.round(bottom); fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + "px; top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px; height: " + (bottom - top) + "px")); @@ -1352,6 +1517,8 @@ display.blinker = setInterval(function() { display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden"; }, cm.options.cursorBlinkRate); + else if (cm.options.cursorBlinkRate < 0) + display.cursorDiv.style.visibility = "hidden"; } // HIGHLIGHT WORKER @@ -1367,15 +1534,20 @@ if (doc.frontier >= cm.display.viewTo) return; var end = +new Date + cm.options.workTime; var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); + var changedLines = []; - runInOp(cm, function() { doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.viewTo + 500), function(line) { if (doc.frontier >= cm.display.viewFrom) { // Visible var oldStyles = line.styles; - line.styles = highlightLine(cm, line, state, true); - var ischange = !oldStyles || oldStyles.length != line.styles.length; + var highlighted = highlightLine(cm, line, state, true); + line.styles = highlighted.styles; + var oldCls = line.styleClasses, newCls = highlighted.classes; + if (newCls) line.styleClasses = newCls; + else if (oldCls) line.styleClasses = null; + var ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass); for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; - if (ischange) regLineChange(cm, doc.frontier, "text"); + if (ischange) changedLines.push(doc.frontier); line.stateAfter = copyState(doc.mode, state); } else { processLine(cm, line.text, state); @@ -1387,6 +1559,9 @@ return true; } }); + if (changedLines.length) runInOp(cm, function() { + for (var i = 0; i < changedLines.length; i++) + regLineChange(cm, changedLines[i], "text"); }); } @@ -1435,8 +1610,17 @@ if (display.cachedPaddingH) return display.cachedPaddingH; var e = removeChildrenAndAdd(display.measure, elt("pre", "x")); var style = window.getComputedStyle ? window.getComputedStyle(e) : e.currentStyle; - return display.cachedPaddingH = {left: parseInt(style.paddingLeft), - right: parseInt(style.paddingRight)}; + var data = {left: parseInt(style.paddingLeft), right: parseInt(style.paddingRight)}; + if (!isNaN(data.left) && !isNaN(data.right)) display.cachedPaddingH = data; + return data; + } + + function scrollGap(cm) { return scrollerGap - cm.display.nativeBarWidth; } + function displayWidth(cm) { + return cm.display.scroller.clientWidth - scrollGap(cm) - cm.display.barWidth; + } + function displayHeight(cm) { + return cm.display.scroller.clientHeight - scrollGap(cm) - cm.display.barHeight; } // Ensure the lineView.wrapping.heights array is populated. This is @@ -1445,7 +1629,7 @@ // height. function ensureLineHeights(cm, lineView, rect) { var wrapping = cm.options.lineWrapping; - var curWidth = wrapping && cm.display.scroller.clientWidth; + var curWidth = wrapping && displayWidth(cm); if (!lineView.measure.heights || wrapping && lineView.measure.width != curWidth) { var heights = lineView.measure.heights = []; if (wrapping) { @@ -1528,7 +1712,7 @@ // Given a prepared measurement object, measures the position of an // actual character (or fetches it from the cache). - function measureCharPrepared(cm, prepared, ch, bias) { + function measureCharPrepared(cm, prepared, ch, bias, varHeight) { if (prepared.before) ch = -1; var key = ch + (bias || ""), found; if (prepared.cache.hasOwnProperty(key)) { @@ -1543,7 +1727,9 @@ found = measureCharInner(cm, prepared, ch, bias); if (!found.bogus) prepared.cache[key] = found; } - return {left: found.left, right: found.right, top: found.top, bottom: found.bottom}; + return {left: found.left, right: found.right, + top: varHeight ? found.rtop : found.top, + bottom: varHeight ? found.rbottom : found.bottom}; } var nullRect = {left: 0, right: 0, top: 0, bottom: 0}; @@ -1587,19 +1773,26 @@ var rect; if (node.nodeType == 3) { // If it is a text node, use a range to retrieve the coordinates. - while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start; - while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end; - if (ie_upto8 && start == 0 && end == mEnd - mStart) { - rect = node.parentNode.getBoundingClientRect(); - } else if (ie && cm.options.lineWrapping) { - var rects = range(node, start, end).getClientRects(); - if (rects.length) - rect = rects[bias == "right" ? rects.length - 1 : 0]; - else - rect = nullRect; - } else { - rect = range(node, start, end).getBoundingClientRect(); + for (var i = 0; i < 4; i++) { // Retry a maximum of 4 times when nonsense rectangles are returned + while (start && isExtendingChar(prepared.line.text.charAt(mStart + start))) --start; + while (mStart + end < mEnd && isExtendingChar(prepared.line.text.charAt(mStart + end))) ++end; + if (ie && ie_version < 9 && start == 0 && end == mEnd - mStart) { + rect = node.parentNode.getBoundingClientRect(); + } else if (ie && cm.options.lineWrapping) { + var rects = range(node, start, end).getClientRects(); + if (rects.length) + rect = rects[bias == "right" ? rects.length - 1 : 0]; + else + rect = nullRect; + } else { + rect = range(node, start, end).getBoundingClientRect() || nullRect; + } + if (rect.left || rect.right || start == 0) break; + end = start; + start = start - 1; + collapse = "right"; } + if (ie && ie_version < 11) rect = maybeUpdateRectForZooming(cm.display.measure, rect); } else { // If it is a widget, simply get the box for the whole widget. if (start > 0) collapse = bias = "right"; var rects; @@ -1608,7 +1801,7 @@ else rect = node.getBoundingClientRect(); } - if (ie_upto8 && !start && (!rect || !rect.left && !rect.right)) { + if (ie && ie_version < 9 && !start && (!rect || !rect.left && !rect.right)) { var rSpan = node.parentNode.getClientRects()[0]; if (rSpan) rect = {left: rSpan.left, right: rSpan.left + charWidth(cm.display), top: rSpan.top, bottom: rSpan.bottom}; @@ -1616,18 +1809,33 @@ rect = nullRect; } - var top, bot = (rect.bottom + rect.top) / 2 - prepared.rect.top; + var rtop = rect.top - prepared.rect.top, rbot = rect.bottom - prepared.rect.top; + var mid = (rtop + rbot) / 2; var heights = prepared.view.measure.heights; for (var i = 0; i < heights.length - 1; i++) - if (bot < heights[i]) break; - top = i ? heights[i - 1] : 0; bot = heights[i]; + if (mid < heights[i]) break; + var top = i ? heights[i - 1] : 0, bot = heights[i]; var result = {left: (collapse == "right" ? rect.right : rect.left) - prepared.rect.left, right: (collapse == "left" ? rect.left : rect.right) - prepared.rect.left, top: top, bottom: bot}; if (!rect.left && !rect.right) result.bogus = true; + if (!cm.options.singleCursorHeightPerLine) { result.rtop = rtop; result.rbottom = rbot; } + return result; } + // Work around problem with bounding client rects on ranges being + // returned incorrectly when zoomed on IE10 and below. + function maybeUpdateRectForZooming(measure, rect) { + if (!window.screen || screen.logicalXDPI == null || + screen.logicalXDPI == screen.deviceXDPI || !hasBadZoomedRects(measure)) + return rect; + var scaleX = screen.logicalXDPI / screen.deviceXDPI; + var scaleY = screen.logicalYDPI / screen.deviceYDPI; + return {left: rect.left * scaleX, right: rect.right * scaleX, + top: rect.top * scaleY, bottom: rect.bottom * scaleY}; + } + function clearLineMeasurementCacheFor(lineView) { if (lineView.measure) { lineView.measure.cache = {}; @@ -1656,7 +1864,8 @@ // Converts a {top, bottom, left, right} box from line-local // coordinates into another coordinate system. Context may be one of - // "line", "div" (display.lineDiv), "local"/null (editor), or "page". + // "line", "div" (display.lineDiv), "local"/null (editor), "window", + // or "page". function intoCoordSystem(cm, lineObj, rect, context) { if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) { var size = widgetHeight(lineObj.widgets[i]); @@ -1704,11 +1913,11 @@ // Returns a box for a given cursor position, which may have an // 'other' property containing the position of the secondary cursor // on a bidi boundary. - function cursorCoords(cm, pos, context, lineObj, preparedMeasure) { + function cursorCoords(cm, pos, context, lineObj, preparedMeasure, varHeight) { lineObj = lineObj || getLine(cm.doc, pos.line); if (!preparedMeasure) preparedMeasure = prepareMeasureForLine(cm, lineObj); function get(ch, right) { - var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left"); + var m = measureCharPrepared(cm, preparedMeasure, ch, right ? "right" : "left", varHeight); if (right) m.left = m.right; else m.right = m.left; return intoCoordSystem(cm, lineObj, m, context); } @@ -1860,66 +2069,171 @@ // error-prone). Instead, display updates are batched and then all // combined and executed at once. + var operationGroup = null; + var nextOpId = 0; // Start a new operation. function startOperation(cm) { cm.curOp = { + cm: cm, viewChanged: false, // Flag that indicates that lines might need to be redrawn startHeight: cm.doc.height, // Used to detect need to update scrollbar forceUpdate: false, // Used to force a redraw updateInput: null, // Whether to reset the input textarea typing: false, // Whether this reset should be careful to leave existing text (for compositing) changeObjs: null, // Accumulated changes, for firing change events - cursorActivity: false, // Whether to fire a cursorActivity event + cursorActivityHandlers: null, // Set of handlers to fire cursorActivity on + cursorActivityCalled: 0, // Tracks which cursorActivity handlers have been called already selectionChanged: false, // Whether the selection needs to be redrawn updateMaxLine: false, // Set when the widest line needs to be determined anew scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet scrollToPos: null, // Used to scroll to a specific position id: ++nextOpId // Unique ID }; - if (!delayedCallbackDepth++) delayedCallbacks = []; + if (operationGroup) { + operationGroup.ops.push(cm.curOp); + } else { + cm.curOp.ownsGroup = operationGroup = { + ops: [cm.curOp], + delayedCallbacks: [] + }; + } + } + + function fireCallbacksForOps(group) { + // Calls delayed callbacks and cursorActivity handlers until no + // new ones appear + var callbacks = group.delayedCallbacks, i = 0; + do { + for (; i < callbacks.length; i++) + callbacks[i](); + for (var j = 0; j < group.ops.length; j++) { + var op = group.ops[j]; + if (op.cursorActivityHandlers) + while (op.cursorActivityCalled < op.cursorActivityHandlers.length) + op.cursorActivityHandlers[op.cursorActivityCalled++](op.cm); + } + } while (i < callbacks.length); } // Finish an operation, updating the display and signalling delayed events function endOperation(cm) { - var op = cm.curOp, doc = cm.doc, display = cm.display; - cm.curOp = null; + var op = cm.curOp, group = op.ownsGroup; + if (!group) return; + try { fireCallbacksForOps(group); } + finally { + operationGroup = null; + for (var i = 0; i < group.ops.length; i++) + group.ops[i].cm.curOp = null; + endOperations(group); + } + } + + // The DOM updates done when an operation finishes are batched so + // that the minimum number of relayouts are required. + function endOperations(group) { + var ops = group.ops; + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R1(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W1(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_R2(ops[i]); + for (var i = 0; i < ops.length; i++) // Write DOM (maybe) + endOperation_W2(ops[i]); + for (var i = 0; i < ops.length; i++) // Read DOM + endOperation_finish(ops[i]); + } + + function endOperation_R1(op) { + var cm = op.cm, display = cm.display; + maybeClipScrollbars(cm); if (op.updateMaxLine) findMaxLine(cm); - // If it looks like an update might be needed, call updateDisplay - if (op.viewChanged || op.forceUpdate || op.scrollTop != null || - op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || - op.scrollToPos.to.line >= display.viewTo) || - display.maxLineChanged && cm.options.lineWrapping) { - var updated = updateDisplay(cm, {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); - if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop; - } - // If no update was run, but the selection changed, redraw that. - if (!updated && op.selectionChanged) updateSelection(cm); - if (!updated && op.startHeight != cm.doc.height) updateScrollbars(cm); + op.mustUpdate = op.viewChanged || op.forceUpdate || op.scrollTop != null || + op.scrollToPos && (op.scrollToPos.from.line < display.viewFrom || + op.scrollToPos.to.line >= display.viewTo) || + display.maxLineChanged && cm.options.lineWrapping; + op.update = op.mustUpdate && + new DisplayUpdate(cm, op.mustUpdate && {top: op.scrollTop, ensure: op.scrollToPos}, op.forceUpdate); + } - // Propagate the scroll position to the actual DOM scroller - if (op.scrollTop != null && display.scroller.scrollTop != op.scrollTop) { - var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)); - display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top; + function endOperation_W1(op) { + op.updatedDisplay = op.mustUpdate && updateDisplayIfNeeded(op.cm, op.update); + } + + function endOperation_R2(op) { + var cm = op.cm, display = cm.display; + if (op.updatedDisplay) updateHeightsInViewport(cm); + + op.barMeasure = measureForScrollbars(cm); + + // If the max line changed since it was last measured, measure it, + // and ensure the document's width matches it. + // updateDisplay_W2 will use these properties to do the actual resizing + if (display.maxLineChanged && !cm.options.lineWrapping) { + op.adjustWidthTo = measureChar(cm, display.maxLine, display.maxLine.text.length).left + 3; + cm.display.sizerWidth = op.adjustWidthTo; + op.barMeasure.scrollWidth = + Math.max(display.scroller.clientWidth, display.sizer.offsetLeft + op.adjustWidthTo + scrollGap(cm) + cm.display.barWidth); + op.maxScrollLeft = Math.max(0, display.sizer.offsetLeft + op.adjustWidthTo - displayWidth(cm)); } - if (op.scrollLeft != null && display.scroller.scrollLeft != op.scrollLeft) { - var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, op.scrollLeft)); - display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left; - alignHorizontally(cm); - } - // If we need to scroll a specific position into view, do so. - if (op.scrollToPos) { - var coords = scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from), - clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin); - if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); + + if (op.updatedDisplay || op.selectionChanged) + op.newSelectionNodes = drawSelection(cm); + } + + function endOperation_W2(op) { + var cm = op.cm; + + if (op.adjustWidthTo != null) { + cm.display.sizer.style.minWidth = op.adjustWidthTo + "px"; + if (op.maxScrollLeft < cm.doc.scrollLeft) + setScrollLeft(cm, Math.min(cm.display.scroller.scrollLeft, op.maxScrollLeft), true); + cm.display.maxLineChanged = false; } + if (op.newSelectionNodes) + showSelection(cm, op.newSelectionNodes); + if (op.updatedDisplay) + setDocumentHeight(cm, op.barMeasure); + if (op.updatedDisplay || op.startHeight != cm.doc.height) + updateScrollbars(cm, op.barMeasure); + if (op.selectionChanged) restartBlink(cm); if (cm.state.focused && op.updateInput) resetInput(cm, op.typing); + } + + function endOperation_finish(op) { + var cm = op.cm, display = cm.display, doc = cm.doc; + + if (op.updatedDisplay) postUpdateDisplay(cm, op.update); + + // Abort mouse wheel delta measurement, when scrolling explicitly + if (display.wheelStartX != null && (op.scrollTop != null || op.scrollLeft != null || op.scrollToPos)) + display.wheelStartX = display.wheelStartY = null; + + // Propagate the scroll position to the actual DOM scroller + if (op.scrollTop != null && (display.scroller.scrollTop != op.scrollTop || op.forceScroll)) { + doc.scrollTop = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, op.scrollTop)); + display.scrollbars.setScrollTop(doc.scrollTop); + display.scroller.scrollTop = doc.scrollTop; + } + if (op.scrollLeft != null && (display.scroller.scrollLeft != op.scrollLeft || op.forceScroll)) { + doc.scrollLeft = Math.max(0, Math.min(display.scroller.scrollWidth - displayWidth(cm), op.scrollLeft)); + display.scrollbars.setScrollLeft(doc.scrollLeft); + display.scroller.scrollLeft = doc.scrollLeft; + alignHorizontally(cm); + } + // If we need to scroll a specific position into view, do so. + if (op.scrollToPos) { + var coords = scrollPosIntoView(cm, clipPos(doc, op.scrollToPos.from), + clipPos(doc, op.scrollToPos.to), op.scrollToPos.margin); + if (op.scrollToPos.isCursor && cm.state.focused) maybeScrollWindow(cm, coords); + } // Fire events for markers that are hidden/unidden by editing or // undoing @@ -1929,19 +2243,14 @@ if (unhidden) for (var i = 0; i < unhidden.length; ++i) if (unhidden[i].lines.length) signal(unhidden[i], "unhide"); - var delayed; - if (!--delayedCallbackDepth) { - delayed = delayedCallbacks; - delayedCallbacks = null; - } + if (display.wrapper.offsetHeight) + doc.scrollTop = cm.display.scroller.scrollTop; + // Fire change events, and delayed event handlers - if (op.changeObjs) { - for (var i = 0; i < op.changeObjs.length; i++) - signal(cm, "change", cm, op.changeObjs[i]); + if (op.changeObjs) signal(cm, "changes", cm, op.changeObjs); - } - if (op.cursorActivity) signal(cm, "cursorActivity", cm); - if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](); + if (op.update) + op.update.finish(); } // Run the given function in an operation @@ -2113,7 +2422,8 @@ function viewCuttingPoint(cm, oldN, newN, dir) { var index = findViewIndex(cm, oldN), diff, view = cm.display.view; - if (!sawCollapsedSpans) return {index: index, lineN: newN}; + if (!sawCollapsedSpans || newN == cm.doc.first + cm.doc.size) + return {index: index, lineN: newN}; for (var i = 0, n = cm.display.viewFrom; i < index; i++) n += view[i].size; if (n != oldN) { @@ -2192,6 +2502,11 @@ cm.display.poll.set(20, p); } + // This will be set to an array of strings when copying, so that, + // when pasting, we know what kind of selections the copied text + // was made out of. + var lastCopied = null; + // Read input from the textarea, and update the document to match. // When something is selected, it is present in the textarea, and // selected (unless it is huge, in which case a placeholder is @@ -2204,12 +2519,21 @@ // possible when it is clear that nothing happened. hasSelection // will be the case when there is a lot of text in the textarea, // in which case reading its value would be expensive. - if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.options.disableInput) return false; + if (!cm.state.focused || (hasSelection(input) && !prevInput) || isReadOnly(cm) || cm.options.disableInput || cm.state.keySeq) + return false; + // See paste handler for more on the fakedLastChar kludge + if (cm.state.pasteIncoming && cm.state.fakedLastChar) { + input.value = input.value.substring(0, input.value.length - 1); + cm.state.fakedLastChar = false; + } var text = input.value; // If nothing changed, bail. if (text == prevInput && !cm.somethingSelected()) return false; - // Work around nonsensical selection resetting in IE9/10 - if (ie && !ie_upto8 && cm.display.inputHasSelection === text) { + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && cm.display.inputHasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { resetInput(cm); return false; } @@ -2218,13 +2542,21 @@ if (withOp) startOperation(cm); cm.display.shift = false; + if (text.charCodeAt(0) == 0x200b && doc.sel == cm.display.selForContextMenu && !prevInput) + prevInput = "\u200b"; // Find the part of the input that is actually new var same = 0, l = Math.min(prevInput.length, text.length); while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; var inserted = text.slice(same), textLines = splitLines(inserted); // When pasing N lines into N selections, insert one line per selection - var multiPaste = cm.state.pasteIncoming && textLines.length > 1 && doc.sel.ranges.length == textLines.length; + var multiPaste = null; + if (cm.state.pasteIncoming && doc.sel.ranges.length > 1) { + if (lastCopied && lastCopied.join("\n") == inserted) + multiPaste = doc.sel.ranges.length % lastCopied.length == 0 && map(lastCopied, splitLines); + else if (textLines.length == doc.sel.ranges.length) + multiPaste = map(textLines, function(l) { return [l]; }); + } // Normal behavior is to insert the new text into every selection for (var i = doc.sel.ranges.length - 1; i >= 0; i--) { @@ -2237,7 +2569,7 @@ else if (cm.state.overwrite && range.empty() && !cm.state.pasteIncoming) to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + lst(textLines).length)); var updateInput = cm.curOp.updateInput; - var changeEvent = {from: from, to: to, text: multiPaste ? [textLines[i]] : textLines, + var changeEvent = {from: from, to: to, text: multiPaste ? multiPaste[i % multiPaste.length] : textLines, origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; makeChange(cm.doc, changeEvent); signalLater(cm, "inputRead", cm, changeEvent); @@ -2245,12 +2577,18 @@ if (inserted && !cm.state.pasteIncoming && cm.options.electricChars && cm.options.smartIndent && range.head.ch < 100 && (!i || doc.sel.ranges[i - 1].head.line != range.head.line)) { - var electric = cm.getModeAt(range.head).electricChars; - if (electric) for (var j = 0; j < electric.length; j++) - if (inserted.indexOf(electric.charAt(j)) > -1) { - indentLine(cm, range.head.line, "smart"); - break; - } + var mode = cm.getModeAt(range.head); + var end = changeEnd(changeEvent); + if (mode.electricChars) { + for (var j = 0; j < mode.electricChars.length; j++) + if (inserted.indexOf(mode.electricChars.charAt(j)) > -1) { + indentLine(cm, end.line, "smart"); + break; + } + } else if (mode.electricInput) { + if (mode.electricInput.test(getLine(doc, end.line).text.slice(0, end.ch))) + indentLine(cm, end.line, "smart"); + } } } ensureCursorVisible(cm); @@ -2268,6 +2606,7 @@ // Reset the input to correspond to the selection (or to be empty, // when not typing and nothing is selected) function resetInput(cm, typing) { + if (cm.display.contextMenuPending) return; var minimal, selected, doc = cm.doc; if (cm.somethingSelected()) { cm.display.prevInput = ""; @@ -2277,17 +2616,19 @@ var content = minimal ? "-" : selected || cm.getSelection(); cm.display.input.value = content; if (cm.state.focused) selectInput(cm.display.input); - if (ie && !ie_upto8) cm.display.inputHasSelection = content; + if (ie && ie_version >= 9) cm.display.inputHasSelection = content; } else if (!typing) { cm.display.prevInput = cm.display.input.value = ""; - if (ie && !ie_upto8) cm.display.inputHasSelection = null; + if (ie && ie_version >= 9) cm.display.inputHasSelection = null; } cm.display.inaccurateSelection = minimal; } function focusInput(cm) { - if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input)) - cm.display.input.focus(); + if (cm.options.readOnly != "nocursor" && (!mobile || activeElt() != cm.display.input)) { + try { cm.display.input.focus(); } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } } function ensureFocus(cm) { @@ -2305,13 +2646,13 @@ var d = cm.display; on(d.scroller, "mousedown", operation(cm, onMouseDown)); // Older IE's will not fire a second mousedown for a double click - if (ie_upto10) + if (ie && ie_version < 11) on(d.scroller, "dblclick", operation(cm, function(e) { if (signalDOMEvent(cm, e)) return; var pos = posFromMouse(cm, e); if (!pos || clickInGutter(cm, e) || eventInWidget(cm.display, e)) return; e_preventDefault(e); - var word = findWordAt(cm.doc, pos); + var word = cm.findWordAt(pos); extendSelection(cm.doc, word.anchor, word.head); })); else @@ -2334,48 +2675,18 @@ signal(cm, "scroll", cm); } }); - on(d.scrollbarV, "scroll", function() { - if (d.scroller.clientHeight) setScrollTop(cm, d.scrollbarV.scrollTop); - }); - on(d.scrollbarH, "scroll", function() { - if (d.scroller.clientHeight) setScrollLeft(cm, d.scrollbarH.scrollLeft); - }); // Listen to wheel events in order to try and update the viewport on time. on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); - // Prevent clicks in the scrollbars from killing focus - function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); } - on(d.scrollbarH, "mousedown", reFocus); - on(d.scrollbarV, "mousedown", reFocus); // Prevent wrapper from ever scrolling on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; }); - // When the window resizes, we need to refresh active editors. - var resizeTimer; - function onResize() { - if (resizeTimer == null) resizeTimer = setTimeout(function() { - resizeTimer = null; - // Might be a text scaling operation, clear size caches. - d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = knownScrollbarWidth = null; - cm.setSize(); - }, 100); - } - on(window, "resize", onResize); - // The above handler holds on to the editor and its data - // structures. Here we poll to unregister it when the editor is no - // longer in the document, so that it can be garbage-collected. - function unregister() { - if (contains(document.body, d.wrapper)) setTimeout(unregister, 5000); - else off(window, "resize", onResize); - } - setTimeout(unregister, 5000); - - on(d.input, "keyup", operation(cm, onKeyUp)); + on(d.input, "keyup", function(e) { onKeyUp.call(cm, e); }); on(d.input, "input", function() { - if (ie && !ie_upto8 && cm.display.inputHasSelection) cm.display.inputHasSelection = null; - fastPoll(cm); + if (ie && ie_version >= 9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null; + readInput(cm); }); on(d.input, "keydown", operation(cm, onKeyDown)); on(d.input, "keypress", operation(cm, onKeyPress)); @@ -2398,21 +2709,54 @@ fastPoll(cm); }); on(d.input, "paste", function() { + // Workaround for webkit bug https://bugs.webkit.org/show_bug.cgi?id=90206 + // Add a char to the end of textarea before paste occur so that + // selection doesn't span to the end of textarea. + if (webkit && !cm.state.fakedLastChar && !(new Date - cm.state.lastMiddleDown < 200)) { + var start = d.input.selectionStart, end = d.input.selectionEnd; + d.input.value += "$"; + // The selection end needs to be set before the start, otherwise there + // can be an intermediate non-empty selection between the two, which + // can override the middle-click paste buffer on linux and cause the + // wrong thing to get pasted. + d.input.selectionEnd = end; + d.input.selectionStart = start; + cm.state.fakedLastChar = true; + } cm.state.pasteIncoming = true; fastPoll(cm); }); - function prepareCopy(e) { - if (d.inaccurateSelection) { - d.prevInput = ""; - d.inaccurateSelection = false; - d.input.value = cm.getSelection(); - selectInput(d.input); + function prepareCopyCut(e) { + if (cm.somethingSelected()) { + lastCopied = cm.getSelections(); + if (d.inaccurateSelection) { + d.prevInput = ""; + d.inaccurateSelection = false; + d.input.value = lastCopied.join("\n"); + selectInput(d.input); + } + } else { + var text = [], ranges = []; + for (var i = 0; i < cm.doc.sel.ranges.length; i++) { + var line = cm.doc.sel.ranges[i].head.line; + var lineRange = {anchor: Pos(line, 0), head: Pos(line + 1, 0)}; + ranges.push(lineRange); + text.push(cm.getRange(lineRange.anchor, lineRange.head)); + } + if (e.type == "cut") { + cm.setSelections(ranges, null, sel_dontScroll); + } else { + d.prevInput = ""; + d.input.value = text.join("\n"); + selectInput(d.input); + } + lastCopied = text; } if (e.type == "cut") cm.state.cutIncoming = true; } - on(d.input, "cut", prepareCopy); - on(d.input, "copy", prepareCopy); + on(d.input, "cut", prepareCopyCut); + on(d.input, "copy", prepareCopyCut); // Needed to handle Tab key in KHTML if (khtml) on(d.sizer, "mouseup", function() { @@ -2421,12 +2765,25 @@ }); } + // Called when the window resizes + function onResize(cm) { + var d = cm.display; + if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) + return; + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null; + d.scrollbarsClipped = false; + cm.setSize(); + } + // MOUSE EVENTS // Return true when the given mouse event happened in a widget function eventInWidget(display, e) { for (var n = e_target(e); n != display.wrapper; n = n.parentNode) { - if (!n || n.ignoreEvents || n.parentNode == display.sizer && n != display.mover) return true; + if (!n || (n.nodeType == 1 && n.getAttribute("cm-ignore-events") == "true") || + (n.parentNode == display.sizer && n != display.mover)) + return true; } } @@ -2437,11 +2794,8 @@ // coordinates beyond the right of the text. function posFromMouse(cm, e, liberal, forRect) { var display = cm.display; - if (!liberal) { - var target = e_target(e); - if (target == display.scrollbarH || target == display.scrollbarV || - target == display.scrollbarFiller || target == display.gutterFiller) return null; - } + if (!liberal && e_target(e).getAttribute("not-content") == "true") return null; + var x, y, space = display.lineSpace.getBoundingClientRect(); // Fails unpredictably on IE[67] when mouse is dragged around quickly. try { x = e.clientX - space.left; y = e.clientY - space.top; } @@ -2449,7 +2803,7 @@ var coords = coordsChar(cm, x, y), line; if (forRect && coords.xRel == 1 && (line = getLine(cm.doc, coords.line).text).length == coords.ch) { var colDiff = countColumn(line, line.length, cm.options.tabSize) - line.length; - coords = Pos(coords.line, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff); + coords = Pos(coords.line, Math.max(0, Math.round((x - paddingH(cm.display).left) / charWidth(cm.display)) - colDiff)); } return coords; } @@ -2511,17 +2865,18 @@ lastClick = {time: now, pos: start}; } - var sel = cm.doc.sel, addNew = mac ? e.metaKey : e.ctrlKey; - if (cm.options.dragDrop && dragAndDrop && !addNew && !isReadOnly(cm) && - type == "single" && sel.contains(start) > -1 && sel.somethingSelected()) - leftButtonStartDrag(cm, e, start); + var sel = cm.doc.sel, modifier = mac ? e.metaKey : e.ctrlKey, contained; + if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && + type == "single" && (contained = sel.contains(start)) > -1 && + !sel.ranges[contained].empty()) + leftButtonStartDrag(cm, e, start, modifier); else - leftButtonSelect(cm, e, start, type, addNew); + leftButtonSelect(cm, e, start, type, modifier); } // Start a text drag. When it ends, see if any dragging actually // happen, and treat as a click if it didn't. - function leftButtonStartDrag(cm, e, start) { + function leftButtonStartDrag(cm, e, start, modifier) { var display = cm.display; var dragEnd = operation(cm, function(e2) { if (webkit) display.scroller.draggable = false; @@ -2530,10 +2885,11 @@ off(display.scroller, "drop", dragEnd); if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { e_preventDefault(e2); - extendSelection(cm.doc, start); + if (!modifier) + extendSelection(cm.doc, start); focusInput(cm); // Work around unexplainable focus problem in IE9 (#2127) - if (ie_upto10 && !ie_upto8) + if (ie && ie_version == 9) setTimeout(function() {document.body.focus(); focusInput(cm);}, 20); } }); @@ -2551,11 +2907,11 @@ var display = cm.display, doc = cm.doc; e_preventDefault(e); - var ourRange, ourIndex, startSel = doc.sel; - if (addNew) { + var ourRange, ourIndex, startSel = doc.sel, ranges = startSel.ranges; + if (addNew && !e.shiftKey) { ourIndex = doc.sel.contains(start); if (ourIndex > -1) - ourRange = doc.sel.ranges[ourIndex]; + ourRange = ranges[ourIndex]; else ourRange = new Range(start, start); } else { @@ -2568,7 +2924,7 @@ start = posFromMouse(cm, e, true, true); ourIndex = -1; } else if (type == "double") { - var word = findWordAt(doc, start); + var word = cm.findWordAt(start); if (cm.display.shift || doc.extend) ourRange = extendRange(doc, ourRange, word.anchor, word.head); else @@ -2586,12 +2942,16 @@ if (!addNew) { ourIndex = 0; setSelection(doc, new Selection([ourRange], 0), sel_mouse); - } else if (ourIndex > -1) { - replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); - } else { - ourIndex = doc.sel.ranges.length; - setSelection(doc, normalizeSelection(doc.sel.ranges.concat([ourRange]), ourIndex), + startSel = doc.sel; + } else if (ourIndex == -1) { + ourIndex = ranges.length; + setSelection(doc, normalizeSelection(ranges.concat([ourRange]), ourIndex), {scroll: false, origin: "*mouse"}); + } else if (ranges.length > 1 && ranges[ourIndex].empty() && type == "single") { + setSelection(doc, normalizeSelection(ranges.slice(0, ourIndex).concat(ranges.slice(ourIndex + 1)), 0)); + startSel = doc.sel; + } else { + replaceOneSelection(doc, ourIndex, ourRange, sel_mouse); } var lastPos = start; @@ -2613,13 +2973,15 @@ ranges.push(new Range(Pos(line, leftPos), Pos(line, findColumn(text, right, tabSize)))); } if (!ranges.length) ranges.push(new Range(start, start)); - setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), sel_mouse); + setSelection(doc, normalizeSelection(startSel.ranges.slice(0, ourIndex).concat(ranges), ourIndex), + {origin: "*mouse", scroll: false}); + cm.scrollIntoView(pos); } else { var oldRange = ourRange; var anchor = oldRange.anchor, head = pos; if (type != "single") { if (type == "double") - var range = findWordAt(doc, pos); + var range = cm.findWordAt(pos); else var range = new Range(Pos(pos.line, 0), clipPos(doc, Pos(pos.line + 1, 0))); if (cmp(range.anchor, anchor) > 0) { @@ -2673,7 +3035,7 @@ } var move = operation(cm, function(e) { - if ((ie && !ie_upto9) ? !e.buttons : !e_button(e)) done(e); + if (!e_button(e)) done(e); else extend(e); }); var up = operation(cm, done); @@ -2719,7 +3081,7 @@ if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; e_preventDefault(e); - if (ie_upto10) lastDrop = +new Date; + if (ie) lastDrop = +new Date; var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; if (!pos || isReadOnly(cm)) return; // Might be a file drop, in which case we simply extract the text @@ -2728,7 +3090,7 @@ var n = files.length, text = Array(n), read = 0; var loadFile = function(file, i) { var reader = new FileReader; - reader.onload = function() { + reader.onload = operation(cm, function() { text[i] = reader.result; if (++read == n) { pos = clipPos(cm.doc, pos); @@ -2736,7 +3098,7 @@ makeChange(cm.doc, change); setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))); } - }; + }); reader.readAsText(file); }; for (var i = 0; i < n; ++i) loadFile(files[i], i); @@ -2751,7 +3113,8 @@ try { var text = e.dataTransfer.getData("Text"); if (text) { - var selected = cm.state.draggingText && cm.listSelections(); + if (cm.state.draggingText && !(mac ? e.metaKey : e.ctrlKey)) + var selected = cm.listSelections(); setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)); if (selected) for (var i = 0; i < selected.length; ++i) replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag"); @@ -2764,7 +3127,7 @@ } function onDragStart(cm, e) { - if (ie_upto10 && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } + if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; e.dataTransfer.setData("Text", cm.getSelection()); @@ -2792,10 +3155,10 @@ function setScrollTop(cm, val) { if (Math.abs(cm.doc.scrollTop - val) < 2) return; cm.doc.scrollTop = val; - if (!gecko) updateDisplay(cm, {top: val}); + if (!gecko) updateDisplaySimple(cm, {top: val}); if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val; - if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val; - if (gecko) updateDisplay(cm); + cm.display.scrollbars.setScrollTop(val); + if (gecko) updateDisplaySimple(cm); startWorker(cm, 100); } // Sync scroller and scrollbar, ensure the gutter elements are @@ -2806,7 +3169,7 @@ cm.doc.scrollLeft = val; alignHorizontally(cm); if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val; - if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val; + cm.display.scrollbars.setScrollLeft(val); } // Since the delta values reported on mouse wheel events are @@ -2830,11 +3193,22 @@ else if (chrome) wheelPixelsPerUnit = -.7; else if (safari) wheelPixelsPerUnit = -1/3; - function onScrollWheel(cm, e) { + var wheelEventDelta = function(e) { var dx = e.wheelDeltaX, dy = e.wheelDeltaY; if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail; if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail; else if (dy == null) dy = e.wheelDelta; + return {x: dx, y: dy}; + }; + CodeMirror.wheelEventPixels = function(e) { + var delta = wheelEventDelta(e); + delta.x *= wheelPixelsPerUnit; + delta.y *= wheelPixelsPerUnit; + return delta; + }; + + function onScrollWheel(cm, e) { + var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y; var display = cm.display, scroll = display.scroller; // Quit if there's nothing to scroll here @@ -2878,7 +3252,7 @@ var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight; if (pixels < 0) top = Math.max(0, top + pixels - 50); else bot = Math.min(cm.doc.height, bot + pixels + 50); - updateDisplay(cm, {top: top, bottom: bot}); + updateDisplaySimple(cm, {top: top, bottom: bot}); } if (wheelSamples < 20) { @@ -2925,62 +3299,70 @@ return done; } - // Collect the currently active keymaps. - function allKeyMaps(cm) { - var maps = cm.state.keyMaps.slice(0); - if (cm.options.extraKeys) maps.push(cm.options.extraKeys); - maps.push(cm.options.keyMap); - return maps; + function lookupKeyForEditor(cm, name, handle) { + for (var i = 0; i < cm.state.keyMaps.length; i++) { + var result = lookupKey(name, cm.state.keyMaps[i], handle, cm); + if (result) return result; + } + return (cm.options.extraKeys && lookupKey(name, cm.options.extraKeys, handle, cm)) + || lookupKey(name, cm.options.keyMap, handle, cm); + } + + var stopSeq = new Delayed; + function dispatchKey(cm, name, e, handle) { + var seq = cm.state.keySeq; + if (seq) { + if (isModifierKey(name)) return "handled"; + stopSeq.set(50, function() { + if (cm.state.keySeq == seq) { + cm.state.keySeq = null; + resetInput(cm); + } + }); + name = seq + " " + name; + } + var result = lookupKeyForEditor(cm, name, handle); + + if (result == "multi") + cm.state.keySeq = name; + if (result == "handled") + signalLater(cm, "keyHandled", cm, name, e); + + if (result == "handled" || result == "multi") { + e_preventDefault(e); + restartBlink(cm); + } + + if (seq && !result && /\'$/.test(name)) { + e_preventDefault(e); + return true; + } + return !!result; } - var maybeTransition; // Handle a key from the keydown event. function handleKeyBinding(cm, e) { - // Handle automatic keymap transitions - var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto; - clearTimeout(maybeTransition); - if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { - if (getKeyMap(cm.options.keyMap) == startMap) { - cm.options.keyMap = (next.call ? next.call(null, cm) : next); - keyMapChanged(cm); - } - }, 50); - - var name = keyName(e, true), handled = false; + var name = keyName(e, true); if (!name) return false; - var keymaps = allKeyMaps(cm); - if (e.shiftKey) { + if (e.shiftKey && !cm.state.keySeq) { // First try to resolve full name (including 'Shift-'). Failing // that, see if there is a cursor-motion command (starting with // 'go') bound to the keyname without 'Shift-'. - handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);}) - || lookupKey(name, keymaps, function(b) { - if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) - return doHandleBinding(cm, b); - }); + return dispatchKey(cm, "Shift-" + name, e, function(b) {return doHandleBinding(cm, b, true);}) + || dispatchKey(cm, name, e, function(b) { + if (typeof b == "string" ? /^go[A-Z]/.test(b) : b.motion) + return doHandleBinding(cm, b); + }); } else { - handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); }); + return dispatchKey(cm, name, e, function(b) { return doHandleBinding(cm, b); }); } - - if (handled) { - e_preventDefault(e); - restartBlink(cm); - signalLater(cm, "keyHandled", cm, name, e); - } - return handled; } // Handle a key from the keypress event function handleCharBinding(cm, e, ch) { - var handled = lookupKey("'" + ch + "'", allKeyMaps(cm), - function(b) { return doHandleBinding(cm, b, true); }); - if (handled) { - e_preventDefault(e); - restartBlink(cm); - signalLater(cm, "keyHandled", cm, "'" + ch + "'", e); - } - return handled; + return dispatchKey(cm, "'" + ch + "'", e, + function(b) { return doHandleBinding(cm, b, true); }); } var lastStoppedKey = null; @@ -2989,7 +3371,7 @@ ensureFocus(cm); if (signalDOMEvent(cm, e)) return; // IE does strange things with escape. - if (ie_upto10 && e.keyCode == 27) e.returnValue = false; + if (ie && ie_version < 11 && e.keyCode == 27) e.returnValue = false; var code = e.keyCode; cm.display.shift = code == 16 || e.shiftKey; var handled = handleKeyBinding(cm, e); @@ -2999,22 +3381,41 @@ if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) cm.replaceSelection("", null, "cut"); } + + // Turn mouse into crosshair when Alt is held on Mac. + if (code == 18 && !/\bCodeMirror-crosshair\b/.test(cm.display.lineDiv.className)) + showCrossHair(cm); + } + + function showCrossHair(cm) { + var lineDiv = cm.display.lineDiv; + addClass(lineDiv, "CodeMirror-crosshair"); + + function up(e) { + if (e.keyCode == 18 || !e.altKey) { + rmClass(lineDiv, "CodeMirror-crosshair"); + off(document, "keyup", up); + off(document, "mouseover", up); + } + } + on(document, "keyup", up); + on(document, "mouseover", up); } function onKeyUp(e) { - if (signalDOMEvent(this, e)) return; if (e.keyCode == 16) this.doc.sel.shift = false; + signalDOMEvent(this, e); } function onKeyPress(e) { var cm = this; - if (signalDOMEvent(cm, e)) return; + if (signalDOMEvent(cm, e) || e.ctrlKey && !e.altKey || mac && e.metaKey) return; var keyCode = e.keyCode, charCode = e.charCode; if (presto && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} if (((presto && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; var ch = String.fromCharCode(charCode == null ? keyCode : charCode); if (handleCharBinding(cm, e, ch)) return; - if (ie && !ie_upto8) cm.display.inputHasSelection = null; + if (ie && ie_version >= 9) cm.display.inputHasSelection = null; fastPoll(cm); } @@ -3025,9 +3426,11 @@ if (!cm.state.focused) { signal(cm, "focus", cm); cm.state.focused = true; - if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1) - cm.display.wrapper.className += " CodeMirror-focused"; - if (!cm.curOp) { + addClass(cm.display.wrapper, "CodeMirror-focused"); + // The prevInput test prevents this from firing when a context + // menu is closed (since the resetInput would kill the + // select-all detection hack) + if (!cm.curOp && cm.display.selForContextMenu != cm.doc.sel) { resetInput(cm); if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730 } @@ -3039,7 +3442,7 @@ if (cm.state.focused) { signal(cm, "blur", cm); cm.state.focused = false; - cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", ""); + rmClass(cm.display.wrapper, "CodeMirror-focused"); } clearInterval(cm.display.blinker); setTimeout(function() {if (!cm.state.focused) cm.display.shift = false;}, 150); @@ -3047,7 +3450,6 @@ // CONTEXT MENU HANDLING - var detectingSelectAll; // To make the context menu work, we need to briefly unhide the // textarea (making it as unobtrusive as possible) to let the // right-click take effect on it. @@ -3071,42 +3473,51 @@ "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + "; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);"; + if (webkit) var oldScrollY = window.scrollY; // Work around Chrome issue (#2712) focusInput(cm); + if (webkit) window.scrollTo(null, oldScrollY); resetInput(cm); // Adds "Select all" to context menu in FF if (!cm.somethingSelected()) display.input.value = display.prevInput = " "; + display.contextMenuPending = true; + display.selForContextMenu = cm.doc.sel; + clearTimeout(display.detectingSelectAll); // Select-all will be greyed out if there's nothing to select, so // this adds a zero-width space so that we can later check whether // it got selected. function prepareSelectAllHack() { if (display.input.selectionStart != null) { - var extval = display.input.value = "\u200b" + (cm.somethingSelected() ? display.input.value : ""); - display.prevInput = "\u200b"; + var selected = cm.somethingSelected(); + var extval = display.input.value = "\u200b" + (selected ? display.input.value : ""); + display.prevInput = selected ? "" : "\u200b"; display.input.selectionStart = 1; display.input.selectionEnd = extval.length; + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel; } } function rehide() { + display.contextMenuPending = false; display.inputDiv.style.position = "relative"; display.input.style.cssText = oldCSS; - if (ie_upto8) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos; + if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos); slowPoll(cm); // Try to detect the user choosing select-all if (display.input.selectionStart != null) { - if (!ie || ie_upto8) prepareSelectAllHack(); - clearTimeout(detectingSelectAll); - var i = 0, poll = function(){ - if (display.prevInput == "\u200b" && display.input.selectionStart == 0) + if (!ie || (ie && ie_version < 9)) prepareSelectAllHack(); + var i = 0, poll = function() { + if (display.selForContextMenu == cm.doc.sel && display.input.selectionStart == 0) operation(cm, commands.selectAll)(cm); - else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); + else if (i++ < 10) display.detectingSelectAll = setTimeout(poll, 500); else resetInput(cm); }; - detectingSelectAll = setTimeout(poll, 200); + display.detectingSelectAll = setTimeout(poll, 200); } } - if (ie && !ie_upto8) prepareSelectAllHack(); + if (ie && ie_version >= 9) prepareSelectAllHack(); if (captureRightClick) { e_stop(e); var mouseup = function() { @@ -3296,9 +3707,9 @@ antiChanges.push(historyChangeFromChange(doc, change)); - var after = i ? computeSelAfterChange(doc, change, null) : lst(source); + var after = i ? computeSelAfterChange(doc, change) : lst(source); makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); - if (doc.cm) ensureCursorVisible(doc.cm); + if (!i && doc.cm) doc.cm.scrollIntoView({from: change.from, to: changeEnd(change)}); var rebased = []; // Propagate to the linked documents @@ -3315,12 +3726,17 @@ // Sub-views need their line numbers shifted when text is added // above or below them in the parent document. function shiftDoc(doc, distance) { + if (distance == 0) return; doc.first += distance; doc.sel = new Selection(map(doc.sel.ranges, function(range) { return new Range(Pos(range.anchor.line + distance, range.anchor.ch), Pos(range.head.line + distance, range.head.ch)); }), doc.sel.primIndex); - if (doc.cm) regChange(doc.cm, doc.first, doc.first - distance, distance); + if (doc.cm) { + regChange(doc.cm, doc.first, doc.first - distance, distance); + for (var d = doc.cm.display, l = d.viewFrom; l < d.viewTo; l++) + regLineChange(doc.cm, l, "gutter"); + } } // More lower-level change function, handling only a single document @@ -3350,7 +3766,7 @@ change.removed = getBetween(doc, change.from, change.to); - if (!selAfter) selAfter = computeSelAfterChange(doc, change, null); + if (!selAfter) selAfter = computeSelAfterChange(doc, change); if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans); else updateDoc(doc, change, spans); setSelectionNoUndo(doc, selAfter, sel_dontScroll); @@ -3373,7 +3789,7 @@ } if (doc.sel.contains(change.from, change.to) > -1) - cm.curOp.cursorActivity = true; + signalCursorActivity(cm); updateDoc(doc, change, spans, estimateHeight(cm)); @@ -3396,18 +3812,25 @@ var lendiff = change.text.length - (to.line - from.line) - 1; // Remember that these lines changed, for updating the display - if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) + if (change.full) + regChange(cm); + else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change)) regLineChange(cm, from.line, "text"); else regChange(cm, from.line, to.line + 1, lendiff); - if (hasHandler(cm, "change") || hasHandler(cm, "changes")) - (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push({ + var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change"); + if (changeHandler || changesHandler) { + var obj = { from: from, to: to, text: change.text, removed: change.removed, origin: change.origin - }); + }; + if (changeHandler) signalLater(cm, "change", cm, obj); + if (changesHandler) (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj); + } + cm.display.selForContextMenu = null; } function replaceRange(doc, code, from, to, origin) { @@ -3422,13 +3845,15 @@ // If an editor sits on the top or bottom of the window, partially // scrolled out of view, this ensures that the cursor is visible. function maybeScrollWindow(cm, coords) { + if (signalDOMEvent(cm, "scrollCursorIntoView")) return; + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null; if (coords.top + box.top < 0) doScroll = true; else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; if (doScroll != null && !phantom) { var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + (coords.top - display.viewOffset - paddingTop(cm.display)) + "px; height: " + - (coords.bottom - coords.top + scrollerCutOff) + "px; left: " + + (coords.bottom - coords.top + scrollGap(cm) + display.barHeight) + "px; left: " + coords.left + "px; width: 2px;"); cm.display.lineSpace.appendChild(scrollNode); scrollNode.scrollIntoView(doScroll); @@ -3441,7 +3866,7 @@ // measured, the position of something may 'drift' during drawing). function scrollPosIntoView(cm, pos, end, margin) { if (margin == null) margin = 0; - for (;;) { + for (var limit = 0; limit < 5; limit++) { var changed = false, coords = cursorCoords(cm, pos); var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), @@ -3457,8 +3882,9 @@ setScrollLeft(cm, scrollPos.scrollLeft); if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; } - if (!changed) return coords; + if (!changed) break; } + return coords; } // Scroll a given set of coordinates into view (immediately). @@ -3476,7 +3902,8 @@ var display = cm.display, snapMargin = textHeight(cm.display); if (y1 < 0) y1 = 0; var screentop = cm.curOp && cm.curOp.scrollTop != null ? cm.curOp.scrollTop : display.scroller.scrollTop; - var screen = display.scroller.clientHeight - scrollerCutOff, result = {}; + var screen = displayHeight(cm), result = {}; + if (y2 - y1 > screen) y2 = y1 + screen; var docBottom = cm.doc.height + paddingVert(display); var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; if (y1 < screentop) { @@ -3487,16 +3914,15 @@ } var screenleft = cm.curOp && cm.curOp.scrollLeft != null ? cm.curOp.scrollLeft : display.scroller.scrollLeft; - var screenw = display.scroller.clientWidth - scrollerCutOff; - x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth; - var gutterw = display.gutters.offsetWidth; - var atLeft = x1 < gutterw + 10; - if (x1 < screenleft + gutterw || atLeft) { - if (atLeft) x1 = 0; - result.scrollLeft = Math.max(0, x1 - 10 - gutterw); - } else if (x2 > screenw + screenleft - 3) { - result.scrollLeft = x2 + 10 - screenw; - } + var screenw = displayWidth(cm) - (cm.options.fixedGutter ? display.gutters.offsetWidth : 0); + var tooWide = x2 - x1 > screenw; + if (tooWide) x2 = x1 + screenw; + if (x1 < 10) + result.scrollLeft = 0; + else if (x1 < screenleft) + result.scrollLeft = Math.max(0, x1 - (tooWide ? 0 : 10)); + else if (x2 > screenw + screenleft - 3) + result.scrollLeft = x2 + (tooWide ? 0 : 10) - screenw; return result; } @@ -3552,7 +3978,7 @@ if (how == "smart") { // Fall back to "prev" when the mode doesn't have an indentation // method. - if (!cm.doc.mode.indent) how = "prev"; + if (!doc.mode.indent) how = "prev"; else state = getStateBefore(cm, n); } @@ -3564,8 +3990,8 @@ indentation = 0; how = "not"; } else if (how == "smart") { - indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); - if (indentation == Pass) { + indentation = doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass || indentation > 150) { if (!aggressive) return; how = "prev"; } @@ -3588,7 +4014,7 @@ if (pos < indentation) indentString += spaceStr(indentation - pos); if (indentString != curSpaceString) { - replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + replaceRange(doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); } else { // Ensure that, if the cursor was in the whitespace at the start // of the line, it is moved to the end of that space. @@ -3607,13 +4033,12 @@ // Utility for applying a change to a line by handle or number, // returning the number and optionally registering the line as // changed. - function changeLine(cm, handle, changeType, op) { - var no = handle, line = handle, doc = cm.doc; + function changeLine(doc, handle, changeType, op) { + var no = handle, line = handle; if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); else no = lineNo(handle); if (no == null) return null; - if (op(line, no)) regLineChange(cm, no, changeType); - else return null; + if (op(line, no) && doc.cm) regLineChange(doc.cm, no, changeType); return line; } @@ -3676,10 +4101,11 @@ else if (unit == "column") moveOnce(true); else if (unit == "word" || unit == "group") { var sawType = null, group = unit == "group"; + var helper = doc.cm && doc.cm.getHelper(pos, "wordChars"); for (var first = true;; first = false) { if (dir < 0 && !moveOnce(!first)) break; var cur = lineObj.text.charAt(ch) || "\n"; - var type = isWordChar(cur) ? "w" + var type = isWordChar(cur, helper) ? "w" : group && cur == "\n" ? "n" : !group || /\s/.test(cur) ? null : "p"; @@ -3718,22 +4144,6 @@ return target; } - // Find the word at the given position (as returned by coordsChar). - function findWordAt(doc, pos) { - var line = getLine(doc, pos.line).text; - var start = pos.ch, end = pos.ch; - if (line) { - if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; - var startChar = line.charAt(start); - var check = isWordChar(startChar) ? isWordChar - : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} - : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; - while (start > 0 && check(line.charAt(start - 1))) --start; - while (end < line.length && check(line.charAt(end))) ++end; - } - return new Range(Pos(pos.line, start), Pos(pos.line, end)); - } - // EDITOR METHODS // The publicly visible API. Note that methodOp(f) means @@ -3760,12 +4170,12 @@ getDoc: function() {return this.doc;}, addKeyMap: function(map, bottom) { - this.state.keyMaps[bottom ? "push" : "unshift"](map); + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)); }, removeKeyMap: function(map) { var maps = this.state.keyMaps; for (var i = 0; i < maps.length; ++i) - if (maps[i] == map || (typeof maps[i] != "string" && maps[i].name == map)) { + if (maps[i] == map || maps[i].name == map) { maps.splice(i, 1); return true; } @@ -3803,11 +4213,14 @@ for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; if (!range.empty()) { - var start = Math.max(end, range.from().line); - var to = range.to(); + var from = range.from(), to = range.to(); + var start = Math.max(end, from.line); end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1; for (var j = start; j < end; ++j) indentLine(this, j, how); + var newRanges = this.doc.sel.ranges; + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll); } else if (range.head.line > end) { indentLine(this, range.head.line, how, true); end = range.head.line; @@ -3819,33 +4232,27 @@ // Fetch the parser token for a given character. Useful for hacks // that want to inspect the mode state (say, for completion). getTokenAt: function(pos, precise) { - var doc = this.doc; - pos = clipPos(doc, pos); - var state = getStateBefore(this, pos.line, precise), mode = this.doc.mode; - var line = getLine(doc, pos.line); - var stream = new StringStream(line.text, this.options.tabSize); - while (stream.pos < pos.ch && !stream.eol()) { - stream.start = stream.pos; - var style = mode.token(stream, state); - } - return {start: stream.start, - end: stream.pos, - string: stream.current(), - type: style || null, - state: state}; + return takeToken(this, pos, precise); + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true); }, getTokenTypeAt: function(pos) { pos = clipPos(this.doc, pos); var styles = getLineStyles(this, getLine(this.doc, pos.line)); var before = 0, after = (styles.length - 1) / 2, ch = pos.ch; - if (ch == 0) return styles[2]; - for (;;) { + var type; + if (ch == 0) type = styles[2]; + else for (;;) { var mid = (before + after) >> 1; if ((mid ? styles[mid * 2 - 1] : 0) >= ch) after = mid; else if (styles[mid * 2 + 1] < ch) before = mid + 1; - else return styles[mid * 2 + 2]; + else { type = styles[mid * 2 + 2]; break; } } + var cut = type ? type.indexOf("cm-overlay ") : -1; + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1); }, getModeAt: function(pos) { @@ -3922,7 +4329,7 @@ defaultCharWidth: function() { return charWidth(this.display); }, setGutterMarker: methodOp(function(line, gutterID, value) { - return changeLine(this, line, "gutter", function(line) { + return changeLine(this.doc, line, "gutter", function(line) { var markers = line.gutterMarkers || (line.gutterMarkers = {}); markers[gutterID] = value; if (!value && isEmpty(markers)) line.gutterMarkers = null; @@ -3942,32 +4349,6 @@ }); }), - addLineClass: methodOp(function(handle, where, cls) { - return changeLine(this, handle, "class", function(line) { - var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; - if (!line[prop]) line[prop] = cls; - else if (new RegExp("(?:^|\\s)" + cls + "(?:$|\\s)").test(line[prop])) return false; - else line[prop] += " " + cls; - return true; - }); - }), - - removeLineClass: methodOp(function(handle, where, cls) { - return changeLine(this, handle, "class", function(line) { - var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass"; - var cur = line[prop]; - if (!cur) return false; - else if (cls == null) line[prop] = null; - else { - var found = cur.match(new RegExp("(?:^|\\s+)" + cls + "(?:$|\\s+)")); - if (!found) return false; - var end = found.index + found[0].length; - line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; - } - return true; - }); - }), - addLineWidget: methodOp(function(handle, node, options) { return addLineWidget(this, handle, node, options); }), @@ -3996,6 +4377,7 @@ pos = cursorCoords(this, clipPos(this.doc, pos)); var top = pos.bottom, left = pos.left; node.style.position = "absolute"; + node.setAttribute("cm-ignore-events", "true"); display.sizer.appendChild(node); if (vert == "over") { top = pos.top; @@ -4026,7 +4408,7 @@ triggerOnKeyDown: methodOp(onKeyDown), triggerOnKeyPress: methodOp(onKeyPress), - triggerOnKeyUp: methodOp(onKeyUp), + triggerOnKeyUp: onKeyUp, execCommand: function(cmd) { if (commands.hasOwnProperty(cmd)) @@ -4095,12 +4477,30 @@ doc.sel.ranges[i].goalColumn = goals[i]; }), + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + var doc = this.doc, line = getLine(doc, pos.line).text; + var start = pos.ch, end = pos.ch; + if (line) { + var helper = this.getHelper(pos, "wordChars"); + if ((pos.xRel < 0 || end == line.length) && start) --start; else ++end; + var startChar = line.charAt(start); + var check = isWordChar(startChar, helper) + ? function(ch) { return isWordChar(ch, helper); } + : /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} + : function(ch) {return !/\s/.test(ch) && !isWordChar(ch);}; + while (start > 0 && check(line.charAt(start - 1))) --start; + while (end < line.length && check(line.charAt(end))) ++end; + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)); + }, + toggleOverwrite: function(value) { if (value != null && value == this.state.overwrite) return; if (this.state.overwrite = !this.state.overwrite) - this.display.cursorDiv.className += " CodeMirror-overwrite"; + addClass(this.display.cursorDiv, "CodeMirror-overwrite"); else - this.display.cursorDiv.className = this.display.cursorDiv.className.replace(" CodeMirror-overwrite", ""); + rmClass(this.display.cursorDiv, "CodeMirror-overwrite"); signal(this, "overwriteToggle", this, this.state.overwrite); }, @@ -4112,10 +4512,11 @@ if (y != null) this.curOp.scrollTop = y; }), getScrollInfo: function() { - var scroller = this.display.scroller, co = scrollerCutOff; + var scroller = this.display.scroller; return {left: scroller.scrollLeft, top: scroller.scrollTop, - height: scroller.scrollHeight - co, width: scroller.scrollWidth - co, - clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)}; }, scrollIntoView: methodOp(function(range, margin) { @@ -4143,14 +4544,21 @@ }), setSize: methodOp(function(width, height) { + var cm = this; function interpret(val) { return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val; } - if (width != null) this.display.wrapper.style.width = interpret(width); - if (height != null) this.display.wrapper.style.height = interpret(height); - if (this.options.lineWrapping) clearLineMeasurementCache(this); - this.curOp.forceUpdate = true; - signal(this, "refresh", this); + if (width != null) cm.display.wrapper.style.width = interpret(width); + if (height != null) cm.display.wrapper.style.height = interpret(height); + if (cm.options.lineWrapping) clearLineMeasurementCache(this); + var lineNo = cm.display.viewFrom; + cm.doc.iter(lineNo, cm.display.viewTo, function(line) { + if (line.widgets) for (var i = 0; i < line.widgets.length; i++) + if (line.widgets[i].noHScroll) { regLineChange(cm, lineNo, "widget"); break; } + ++lineNo; + }); + cm.curOp.forceUpdate = true; + signal(cm, "refresh", this); }), operation: function(f){return runInOp(this, f);}, @@ -4158,8 +4566,10 @@ refresh: methodOp(function() { var oldHeight = this.display.cachedTextHeight; regChange(this); + this.curOp.forceUpdate = true; clearCaches(this); this.scrollTo(this.doc.scrollLeft, this.doc.scrollTop); + updateGutterSpace(this); if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) estimateLineHeights(this); signal(this, "refresh", this); @@ -4172,6 +4582,7 @@ clearCaches(this); resetInput(this); this.scrollTo(doc.scrollLeft, doc.scrollTop); + this.curOp.forceScroll = true; signalLater(this, "swapDoc", this, old); return old; }), @@ -4217,7 +4628,7 @@ clearCaches(cm); regChange(cm); }, true); - option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm, val) { + option("specialChars", /[\t\u0000-\u0019\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g, function(cm, val) { cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); cm.refresh(); }, true); @@ -4230,7 +4641,12 @@ themeChanged(cm); guttersChanged(cm); }, true); - option("keyMap", "default", keyMapChanged); + option("keyMap", "default", function(cm, val, old) { + var next = getKeyMap(val); + var prev = old != CodeMirror.Init && getKeyMap(old); + if (prev && prev.detach) prev.detach(cm, next); + if (next.attach) next.attach(cm, prev || null); + }); option("extraKeys", null); option("lineWrapping", false, wrappingChanged, true); @@ -4242,7 +4658,13 @@ cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; cm.refresh(); }, true); - option("coverGutterNextToScrollbar", false, updateScrollbars, true); + option("coverGutterNextToScrollbar", false, function(cm) {updateScrollbars(cm);}, true); + option("scrollbarStyle", "native", function(cm) { + initScrollbars(cm); + updateScrollbars(cm); + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop); + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft); + }, true); option("lineNumbers", false, function(cm) { setGuttersForLineNumbers(cm.options); guttersChanged(cm); @@ -4268,7 +4690,8 @@ option("cursorBlinkRate", 530); option("cursorScrollMargin", 0); - option("cursorHeight", 1); + option("cursorHeight", 1, updateSelection, true); + option("singleCursorHeightPerLine", true, updateSelection, true); option("workTime", 100); option("workDelay", 100); option("flattenSpans", true, resetModeState, true); @@ -4297,10 +4720,8 @@ // load a mode. (Preferred mechanism is the require/define calls.) CodeMirror.defineMode = function(name, mode) { if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; - if (arguments.length > 2) { - mode.dependencies = []; - for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); - } + if (arguments.length > 2) + mode.dependencies = Array.prototype.slice.call(arguments, 2); modes[name] = mode; }; @@ -4451,6 +4872,20 @@ return {from: Pos(range.from().line, 0), to: range.from()}; }); }, + delWrappedLineLeft: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var leftPos = cm.coordsChar({left: 0, top: top}, "div"); + return {from: leftPos, to: range.from()}; + }); + }, + delWrappedLineRight: function(cm) { + deleteNearSelection(cm, function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var rightPos = cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div"); + return {from: range.from(), to: rightPos }; + }); + }, undo: function(cm) {cm.undo();}, redo: function(cm) {cm.redo();}, undoSelection: function(cm) {cm.undoSelection();}, @@ -4458,23 +4893,17 @@ goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, goLineStart: function(cm) { - cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, sel_move); + cm.extendSelectionsBy(function(range) { return lineStart(cm, range.head.line); }, + {origin: "+move", bias: 1}); }, goLineStartSmart: function(cm) { cm.extendSelectionsBy(function(range) { - var start = lineStart(cm, range.head.line); - var line = cm.getLineHandle(start.line); - var order = getOrder(line); - if (!order || order[0].level == 0) { - var firstNonWS = Math.max(0, line.text.search(/\S/)); - var inWS = range.head.line == start.line && range.head.ch <= firstNonWS && range.head.ch; - return Pos(start.line, inWS ? 0 : firstNonWS); - } - return start; - }, sel_move); + return lineStartSmart(cm, range.head); + }, {origin: "+move", bias: 1}); }, goLineEnd: function(cm) { - cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, sel_move); + cm.extendSelectionsBy(function(range) { return lineEnd(cm, range.head.line); }, + {origin: "+move", bias: -1}); }, goLineRight: function(cm) { cm.extendSelectionsBy(function(range) { @@ -4488,6 +4917,14 @@ return cm.coordsChar({left: 0, top: top}, "div"); }, sel_move); }, + goLineLeftSmart: function(cm) { + cm.extendSelectionsBy(function(range) { + var top = cm.charCoords(range.head, "div").top + 5; + var pos = cm.coordsChar({left: 0, top: top}, "div"); + if (pos.ch < cm.getLine(pos.line).search(/\S/)) return lineStartSmart(cm, range.head); + return pos; + }, sel_move); + }, goLineUp: function(cm) {cm.moveV(-1, "line");}, goLineDown: function(cm) {cm.moveV(1, "line");}, goPageUp: function(cm) {cm.moveV(-1, "page");}, @@ -4510,19 +4947,40 @@ indentMore: function(cm) {cm.indentSelection("add");}, indentLess: function(cm) {cm.indentSelection("subtract");}, insertTab: function(cm) {cm.replaceSelection("\t");}, + insertSoftTab: function(cm) { + var spaces = [], ranges = cm.listSelections(), tabSize = cm.options.tabSize; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].from(); + var col = countColumn(cm.getLine(pos.line), pos.ch, tabSize); + spaces.push(new Array(tabSize - col % tabSize + 1).join(" ")); + } + cm.replaceSelections(spaces); + }, defaultTab: function(cm) { if (cm.somethingSelected()) cm.indentSelection("add"); else cm.execCommand("insertTab"); }, transposeChars: function(cm) { runInOp(cm, function() { - var ranges = cm.listSelections(); + var ranges = cm.listSelections(), newSel = []; for (var i = 0; i < ranges.length; i++) { var cur = ranges[i].head, line = getLine(cm.doc, cur.line).text; - if (cur.ch > 0 && cur.ch < line.length - 1) - cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), - Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1)); + if (line) { + if (cur.ch == line.length) cur = new Pos(cur.line, cur.ch - 1); + if (cur.ch > 0) { + cur = new Pos(cur.line, cur.ch + 1); + cm.replaceRange(line.charAt(cur.ch - 1) + line.charAt(cur.ch - 2), + Pos(cur.line, cur.ch - 2), cur, "+transpose"); + } else if (cur.line > cm.doc.first) { + var prev = getLine(cm.doc, cur.line - 1).text; + if (prev) + cm.replaceRange(line.charAt(0) + "\n" + prev.charAt(prev.length - 1), + Pos(cur.line - 1, prev.length - 1), Pos(cur.line, 1), "+transpose"); + } + } + newSel.push(new Range(cur, cur)); } + cm.setSelections(newSel); }); }, newlineAndIndent: function(cm) { @@ -4539,9 +4997,11 @@ toggleOverwrite: function(cm) {cm.toggleOverwrite();} }; + // STANDARD KEYMAPS var keyMap = CodeMirror.keyMap = {}; + keyMap.basic = { "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", @@ -4555,7 +5015,7 @@ // are simply ignored. keyMap.pcDefault = { "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", - "Ctrl-Home": "goDocStart", "Ctrl-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", + "Ctrl-Home": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", @@ -4563,16 +5023,6 @@ "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", fallthrough: "basic" }; - keyMap.macDefault = { - "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", - "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", - "Alt-Right": "goGroupRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delGroupBefore", - "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", - "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", - "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delLineLeft", - "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", - fallthrough: ["basic", "emacsy"] - }; // Very basic readline/emacs-style bindings, which are standard on Mac. keyMap.emacsy = { "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", @@ -4580,67 +5030,114 @@ "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + fallthrough: ["basic", "emacsy"] + }; keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; // KEYMAP DISPATCH - function getKeyMap(val) { - if (typeof val == "string") return keyMap[val]; - else return val; + function normalizeKeyName(name) { + var parts = name.split(/-(?!$)/), name = parts[parts.length - 1]; + var alt, ctrl, shift, cmd; + for (var i = 0; i < parts.length - 1; i++) { + var mod = parts[i]; + if (/^(cmd|meta|m)$/i.test(mod)) cmd = true; + else if (/^a(lt)?$/i.test(mod)) alt = true; + else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true; + else if (/^s(hift)$/i.test(mod)) shift = true; + else throw new Error("Unrecognized modifier name: " + mod); + } + if (alt) name = "Alt-" + name; + if (ctrl) name = "Ctrl-" + name; + if (cmd) name = "Cmd-" + name; + if (shift) name = "Shift-" + name; + return name; } - // Given an array of keymaps and a key name, call handle on any - // bindings found, until that returns a truthy value, at which point - // we consider the key handled. Implements things like binding a key - // to false stopping further handling and keymap fallthrough. - var lookupKey = CodeMirror.lookupKey = function(name, maps, handle) { - function lookup(map) { - map = getKeyMap(map); - var found = map[name]; - if (found === false) return "stop"; - if (found != null && handle(found)) return true; - if (map.nofallthrough) return "stop"; + // This is a kludge to keep keymaps mostly working as raw objects + // (backwards compatibility) while at the same time support features + // like normalization and multi-stroke key bindings. It compiles a + // new normalized keymap, and then updates the old object to reflect + // this. + CodeMirror.normalizeKeyMap = function(keymap) { + var copy = {}; + for (var keyname in keymap) if (keymap.hasOwnProperty(keyname)) { + var value = keymap[keyname]; + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue; + if (value == "...") { delete keymap[keyname]; continue; } - var fallthrough = map.fallthrough; - if (fallthrough == null) return false; - if (Object.prototype.toString.call(fallthrough) != "[object Array]") - return lookup(fallthrough); - for (var i = 0; i < fallthrough.length; ++i) { - var done = lookup(fallthrough[i]); - if (done) return done; + var keys = map(keyname.split(" "), normalizeKeyName); + for (var i = 0; i < keys.length; i++) { + var val, name; + if (i == keys.length - 1) { + name = keyname; + val = value; + } else { + name = keys.slice(0, i + 1).join(" "); + val = "..."; + } + var prev = copy[name]; + if (!prev) copy[name] = val; + else if (prev != val) throw new Error("Inconsistent bindings for " + name); } - return false; + delete keymap[keyname]; } + for (var prop in copy) keymap[prop] = copy[prop]; + return keymap; + }; - for (var i = 0; i < maps.length; ++i) { - var done = lookup(maps[i]); - if (done) return done != "stop"; + var lookupKey = CodeMirror.lookupKey = function(key, map, handle, context) { + map = getKeyMap(map); + var found = map.call ? map.call(key, context) : map[key]; + if (found === false) return "nothing"; + if (found === "...") return "multi"; + if (found != null && handle(found)) return "handled"; + + if (map.fallthrough) { + if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") + return lookupKey(key, map.fallthrough, handle, context); + for (var i = 0; i < map.fallthrough.length; i++) { + var result = lookupKey(key, map.fallthrough[i], handle, context); + if (result) return result; + } } }; // Modifier key presses don't count as 'real' key presses for the // purpose of keymap fallthrough. - var isModifierKey = CodeMirror.isModifierKey = function(event) { - var name = keyNames[event.keyCode]; + var isModifierKey = CodeMirror.isModifierKey = function(value) { + var name = typeof value == "string" ? value : keyNames[value.keyCode]; return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; }; // Look up the name of a key as indicated by an event object. var keyName = CodeMirror.keyName = function(event, noShift) { if (presto && event.keyCode == 34 && event["char"]) return false; - var name = keyNames[event.keyCode]; + var base = keyNames[event.keyCode], name = base; if (name == null || event.altGraphKey) return false; - if (event.altKey) name = "Alt-" + name; - if (flipCtrlCmd ? event.metaKey : event.ctrlKey) name = "Ctrl-" + name; - if (flipCtrlCmd ? event.ctrlKey : event.metaKey) name = "Cmd-" + name; - if (!noShift && event.shiftKey) name = "Shift-" + name; + if (event.altKey && base != "Alt") name = "Alt-" + name; + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name; + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name; + if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name; return name; }; + function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val; + } + // FROMTEXTAREA CodeMirror.fromTextArea = function(textarea, options) { - if (!options) options = {}; + options = options ? copyObj(options) : {}; options.value = textarea.value; if (!options.tabindex && textarea.tabindex) options.tabindex = textarea.tabindex; @@ -4671,22 +5168,26 @@ } } + options.finishInit = function(cm) { + cm.save = save; + cm.getTextArea = function() { return textarea; }; + cm.toTextArea = function() { + cm.toTextArea = isNaN; // Prevent this from being ran twice + save(); + textarea.parentNode.removeChild(cm.getWrapperElement()); + textarea.style.display = ""; + if (textarea.form) { + off(textarea.form, "submit", save); + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit; + } + }; + }; + textarea.style.display = "none"; var cm = CodeMirror(function(node) { textarea.parentNode.insertBefore(node, textarea.nextSibling); }, options); - cm.save = save; - cm.getTextArea = function() { return textarea; }; - cm.toTextArea = function() { - save(); - textarea.parentNode.removeChild(cm.getWrapperElement()); - textarea.style.display = ""; - if (textarea.form) { - off(textarea.form, "submit", save); - if (typeof textarea.form.submit == "function") - textarea.form.submit = realSubmit; - } - }; return cm; }; @@ -4826,6 +5327,7 @@ } if (cm) signalLater(cm, "markerCleared", cm, this); if (withOp) endOperation(cm); + if (this.parent) this.parent.clear(); }; // Find the position of the marker in the document. Returns a {from, @@ -4905,7 +5407,7 @@ if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); var marker = new TextMarker(doc, type), diff = cmp(from, to); - if (options) copyObj(options, marker); + if (options) copyObj(options, marker, false); // Don't connect empty markers unless clearWhenEmpty is false if (diff > 0 || diff == 0 && marker.clearWhenEmpty !== false) return marker; @@ -4913,7 +5415,7 @@ // Showing up as a widget implies collapsed (widget replaces text) marker.collapsed = true; marker.widgetNode = elt("span", [marker.replacedWith], "CodeMirror-widget"); - if (!options.handleMouseEvents) marker.widgetNode.ignoreEvents = true; + if (!options.handleMouseEvents) marker.widgetNode.setAttribute("cm-ignore-events", "true"); if (options.insertLeft) marker.widgetNode.insertLeft = true; } if (marker.collapsed) { @@ -4957,7 +5459,7 @@ if (updateMaxLine) cm.curOp.updateMaxLine = true; if (marker.collapsed) regChange(cm, from.line, to.line + 1); - else if (marker.className || marker.title || marker.startStyle || marker.endStyle) + else if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.css) for (var i = from.line; i <= to.line; i++) regLineChange(cm, i, "text"); if (marker.atomic) reCheckSelection(cm.doc); signalLater(cm, "markerAdded", cm, marker); @@ -4973,10 +5475,8 @@ var SharedTextMarker = CodeMirror.SharedTextMarker = function(markers, primary) { this.markers = markers; this.primary = primary; - for (var i = 0, me = this; i < markers.length; ++i) { + for (var i = 0; i < markers.length; ++i) markers[i].parent = this; - on(markers[i], "clear", function(){me.clear();}); - } }; eventMixin(SharedTextMarker); @@ -5006,6 +5506,37 @@ return new SharedTextMarker(markers, primary); } + function findSharedMarkers(doc) { + return doc.findMarks(Pos(doc.first, 0), doc.clipPos(Pos(doc.lastLine())), + function(m) { return m.parent; }); + } + + function copySharedMarkers(doc, markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], pos = marker.find(); + var mFrom = doc.clipPos(pos.from), mTo = doc.clipPos(pos.to); + if (cmp(mFrom, mTo)) { + var subMark = markText(doc, mFrom, mTo, marker.primary, marker.primary.type); + marker.markers.push(subMark); + subMark.parent = marker; + } + } + } + + function detachSharedMarkers(markers) { + for (var i = 0; i < markers.length; i++) { + var marker = markers[i], linked = [marker.primary.doc];; + linkedDocs(marker.primary.doc, function(d) { linked.push(d); }); + for (var j = 0; j < marker.markers.length; j++) { + var subMarker = marker.markers[j]; + if (indexOf(linked, subMarker.doc) == -1) { + subMarker.parent = null; + marker.markers.splice(j--, 1); + } + } + } + } + // TEXTMARKER SPANS function MarkedSpan(marker, from, to) { @@ -5068,6 +5599,7 @@ // spans partially within the change. Returns an array of span // arrays with one element for each line in (after) the change. function stretchSpansOverChange(doc, change) { + if (change.full) return null; var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans; var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans; if (!oldFirst && !oldLast) return null; @@ -5255,8 +5787,8 @@ var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; - if (fromCmp <= 0 && (cmp(found.to, from) || extraRight(sp.marker) - extraLeft(marker)) > 0 || - fromCmp >= 0 && (cmp(found.from, to) || extraLeft(sp.marker) - extraRight(marker)) < 0) + if (fromCmp <= 0 && (cmp(found.to, from) > 0 || (sp.marker.inclusiveRight && marker.inclusiveLeft)) || + fromCmp >= 0 && (cmp(found.from, to) < 0 || (sp.marker.inclusiveLeft && marker.inclusiveRight))) return true; } } @@ -5374,15 +5906,21 @@ function widgetHeight(widget) { if (widget.height != null) return widget.height; - if (!contains(document.body, widget.node)) - removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative")); + if (!contains(document.body, widget.node)) { + var parentStyle = "position: relative;"; + if (widget.coverGutter) + parentStyle += "margin-left: -" + widget.cm.display.gutters.offsetWidth + "px;"; + if (widget.noHScroll) + parentStyle += "width: " + widget.cm.display.wrapper.clientWidth + "px;"; + removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, parentStyle)); + } return widget.height = widget.node.offsetHeight; } function addLineWidget(cm, handle, node, options) { var widget = new LineWidget(cm, node, options); if (widget.noHScroll) cm.display.alignWidgets = true; - changeLine(cm, handle, "widget", function(line) { + changeLine(cm.doc, handle, "widget", function(line) { var widgets = line.widgets || (line.widgets = []); if (widget.insertAt == null) widgets.push(widget); else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget); @@ -5430,13 +5968,66 @@ detachMarkedSpans(line); } + function extractLineClasses(type, output) { + if (type) for (;;) { + var lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/); + if (!lineClass) break; + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (output[prop] == null) + output[prop] = lineClass[2]; + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + output[prop] += " " + lineClass[2]; + } + return type; + } + + function callBlankLine(mode, state) { + if (mode.blankLine) return mode.blankLine(state); + if (!mode.innerMode) return; + var inner = CodeMirror.innerMode(mode, state); + if (inner.mode.blankLine) return inner.mode.blankLine(inner.state); + } + + function readToken(mode, stream, state, inner) { + for (var i = 0; i < 10; i++) { + if (inner) inner[0] = CodeMirror.innerMode(mode, state).mode; + var style = mode.token(stream, state); + if (stream.pos > stream.start) return style; + } + throw new Error("Mode " + mode.name + " failed to advance stream."); + } + + // Utility for getTokenAt and getLineTokens + function takeToken(cm, pos, precise, asArray) { + function getObj(copy) { + return {start: stream.start, end: stream.pos, + string: stream.current(), + type: style || null, + state: copy ? copyState(doc.mode, state) : state}; + } + + var doc = cm.doc, mode = doc.mode, style; + pos = clipPos(doc, pos); + var line = getLine(doc, pos.line), state = getStateBefore(cm, pos.line, precise); + var stream = new StringStream(line.text, cm.options.tabSize), tokens; + if (asArray) tokens = []; + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos; + style = readToken(mode, stream, state); + if (asArray) tokens.push(getObj(true)); + } + return asArray ? tokens : getObj(); + } + // Run the given mode's parser over a line, calling f for each token. - function runMode(cm, text, mode, state, f, forceToEnd) { + function runMode(cm, text, mode, state, f, lineClasses, forceToEnd) { var flattenSpans = mode.flattenSpans; if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; var curStart = 0, curStyle = null; var stream = new StringStream(text, cm.options.tabSize), style; - if (text == "" && mode.blankLine) mode.blankLine(state); + var inner = cm.options.addModeClass && [null]; + if (text == "") extractLineClasses(callBlankLine(mode, state), lineClasses); while (!stream.eol()) { if (stream.pos > cm.options.maxHighlightLength) { flattenSpans = false; @@ -5444,15 +6035,18 @@ stream.pos = text.length; style = null; } else { - style = mode.token(stream, state); + style = extractLineClasses(readToken(mode, stream, state, inner), lineClasses); } - if (cm.options.addModeClass) { - var mName = CodeMirror.innerMode(mode, state).mode.name; + if (inner) { + var mName = inner[0].name; if (mName) style = "m-" + (style ? mName + " " + style : mName); } if (!flattenSpans || curStyle != style) { - if (curStart < stream.start) f(stream.start, curStyle); - curStart = stream.start; curStyle = style; + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 50000); + f(curStart, curStyle); + } + curStyle = style; } stream.start = stream.pos; } @@ -5471,11 +6065,11 @@ function highlightLine(cm, line, state, forceToEnd) { // A styles array always starts with a number identifying the // mode/overlays that it is based on (for easy invalidation). - var st = [cm.state.modeGen]; + var st = [cm.state.modeGen], lineClasses = {}; // Compute the base array of styles runMode(cm, line.text, cm.doc.mode, state, function(end, style) { st.push(end, style); - }, forceToEnd); + }, lineClasses, forceToEnd); // Run overlays, adjust style array. for (var o = 0; o < cm.state.overlays.length; ++o) { @@ -5492,23 +6086,28 @@ } if (!style) return; if (overlay.opaque) { - st.splice(start, i - start, end, style); + st.splice(start, i - start, end, "cm-overlay " + style); i = start + 2; } else { for (; start < i; start += 2) { var cur = st[start+1]; - st[start+1] = cur ? cur + " " + style : style; + st[start+1] = (cur ? cur + " " : "") + "cm-overlay " + style; } } - }); + }, lineClasses); } - return st; + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}; } - function getLineStyles(cm, line) { - if (!line.styles || line.styles[0] != cm.state.modeGen) - line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); + function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + var result = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); + line.styles = result.styles; + if (result.classes) line.styleClasses = result.classes; + else if (line.styleClasses) line.styleClasses = null; + if (updateFrontier === cm.doc.frontier) cm.doc.frontier++; + } return line.styles; } @@ -5519,9 +6118,9 @@ var mode = cm.doc.mode; var stream = new StringStream(text, cm.options.tabSize); stream.start = stream.pos = startAt || 0; - if (text == "" && mode.blankLine) mode.blankLine(state); + if (text == "") callBlankLine(mode, state); while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { - mode.token(stream, state); + readToken(mode, stream, state); stream.start = stream.pos; } } @@ -5530,20 +6129,9 @@ // containing one or more styles) to a CSS style. This is cached, // and also looks for line-wide styles. var styleToClassCache = {}, styleToClassCacheWithMode = {}; - function interpretTokenStyle(style, builder) { - if (!style) return null; - for (;;) { - var lineClass = style.match(/(?:^|\s+)line-(background-)?(\S+)/); - if (!lineClass) break; - style = style.slice(0, lineClass.index) + style.slice(lineClass.index + lineClass[0].length); - var prop = lineClass[1] ? "bgClass" : "textClass"; - if (builder[prop] == null) - builder[prop] = lineClass[2]; - else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(builder[prop])) - builder[prop] += " " + lineClass[2]; - } - if (/^\s*$/.test(style)) return null; - var cache = builder.cm.options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) return null; + var cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; return cache[style] || (cache[style] = style.replace(/\S+/g, "cm-$&")); } @@ -5573,7 +6161,14 @@ if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line))) builder.addToken = buildTokenBadBidi(builder.addToken, order); builder.map = []; - insertLineContent(line, builder, getLineStyles(cm, line)); + var allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line); + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)); + if (line.styleClasses) { + if (line.styleClasses.bgClass) + builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || ""); + if (line.styleClasses.textClass) + builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || ""); + } // Ensure at least a single node is present, for measuring. if (builder.map.length == 0) @@ -5589,26 +6184,34 @@ } } + // See issue #2901 + if (webkit && /\bcm-tab\b/.test(builder.content.lastChild.className)) + builder.content.className = "cm-tab-wrap-hack"; + signal(cm, "renderLine", cm, lineView.line, builder.pre); + if (builder.pre.className) + builder.textClass = joinClasses(builder.pre.className, builder.textClass || ""); + return builder; } function defaultSpecialCharPlaceholder(ch) { var token = elt("span", "\u2022", "cm-invalidchar"); token.title = "\\u" + ch.charCodeAt(0).toString(16); + token.setAttribute("aria-label", token.title); return token; } // Build up the DOM representation for a single token, and add it to // the line map. Takes care to render special characters separately. - function buildToken(builder, text, style, startStyle, endStyle, title) { + function buildToken(builder, text, style, startStyle, endStyle, title, css) { if (!text) return; var special = builder.cm.options.specialChars, mustWrap = false; if (!special.test(text)) { builder.col += text.length; var content = document.createTextNode(text); builder.map.push(builder.pos, builder.pos + text.length, content); - if (ie_upto8) mustWrap = true; + if (ie && ie_version < 9) mustWrap = true; builder.pos += text.length; } else { var content = document.createDocumentFragment(), pos = 0; @@ -5618,7 +6221,7 @@ var skipped = m ? m.index - pos : text.length - pos; if (skipped) { var txt = document.createTextNode(text.slice(pos, pos + skipped)); - if (ie_upto8) content.appendChild(elt("span", [txt])); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); else content.appendChild(txt); builder.map.push(builder.pos, builder.pos + skipped, txt); builder.col += skipped; @@ -5629,10 +6232,11 @@ if (m[0] == "\t") { var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; var txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); + txt.setAttribute("role", "presentation"); builder.col += tabWidth; } else { var txt = builder.cm.options.specialCharPlaceholder(m[0]); - if (ie_upto8) content.appendChild(elt("span", [txt])); + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])); else content.appendChild(txt); builder.col += 1; } @@ -5640,11 +6244,11 @@ builder.pos++; } } - if (style || startStyle || endStyle || mustWrap) { + if (style || startStyle || endStyle || mustWrap || css) { var fullStyle = style || ""; if (startStyle) fullStyle += startStyle; if (endStyle) fullStyle += endStyle; - var token = elt("span", [content], fullStyle); + var token = elt("span", [content], fullStyle, css); if (title) token.title = title; return builder.content.appendChild(token); } @@ -5699,15 +6303,15 @@ var spans = line.markedSpans, allText = line.text, at = 0; if (!spans) { for (var i = 1; i < styles.length; i+=2) - builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder)); + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)); return; } - var len = allText.length, pos = 0, i = 1, text = "", style; + var len = allText.length, pos = 0, i = 1, text = "", style, css; var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; for (;;) { if (nextChange == pos) { // Update current marker set - spanStyle = spanEndStyle = spanStartStyle = title = ""; + spanStyle = spanEndStyle = spanStartStyle = title = css = ""; collapsed = null; nextChange = Infinity; var foundBookmarks = []; for (var j = 0; j < spans.length; ++j) { @@ -5715,6 +6319,7 @@ if (sp.from <= pos && (sp.to == null || sp.to > pos)) { if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; } if (m.className) spanStyle += " " + m.className; + if (m.css) css = m.css; if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; if (m.title && !title) title = m.title; @@ -5742,14 +6347,14 @@ if (!collapsed) { var tokenText = end > upto ? text.slice(0, upto - pos) : text; builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, - spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title); + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css); } if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;} pos = end; spanStartStyle = ""; } text = allText.slice(at, at = styles[i++]); - style = interpretTokenStyle(styles[i++], builder); + style = interpretTokenStyle(styles[i++], builder.cm.options); } } } @@ -5771,17 +6376,24 @@ updateLine(line, text, spans, estimateHeight); signalLater(line, "change", line, change); } + function linesFor(start, end) { + for (var i = start, result = []; i < end; ++i) + result.push(new Line(text[i], spansFor(i), estimateHeight)); + return result; + } var from = change.from, to = change.to, text = change.text; var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line); var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; // Adjust the line structure - if (isWholeLineUpdate(doc, change)) { + if (change.full) { + doc.insert(0, linesFor(0, text.length)); + doc.remove(text.length, doc.size - text.length); + } else if (isWholeLineUpdate(doc, change)) { // This is a whole-line replace. Treated specially to make // sure line objects move the way they are supposed to. - for (var i = 0, added = []; i < text.length - 1; ++i) - added.push(new Line(text[i], spansFor(i), estimateHeight)); + var added = linesFor(0, text.length - 1); update(lastLine, lastLine.text, lastSpans); if (nlines) doc.remove(from.line, nlines); if (added.length) doc.insert(from.line, added); @@ -5789,8 +6401,7 @@ if (text.length == 1) { update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans); } else { - for (var added = [], i = 1; i < text.length - 1; ++i) - added.push(new Line(text[i], spansFor(i), estimateHeight)); + var added = linesFor(1, text.length - 1); added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)); update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); doc.insert(from.line + 1, added); @@ -5801,8 +6412,7 @@ } else { update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)); update(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans); - for (var i = 1, added = []; i < text.length - 1; ++i) - added.push(new Line(text[i], spansFor(i), estimateHeight)); + var added = linesFor(1, text.length - 1); if (nlines > 1) doc.remove(from.line + 1, nlines - 1); doc.insert(from.line + 1, added); } @@ -6013,7 +6623,7 @@ setValue: docMethodOp(function(code) { var top = Pos(this.first, 0), last = this.first + this.size - 1; makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), - text: splitLines(code), origin: "setValue"}, true); + text: splitLines(code), origin: "setValue", full: true}, true); setSelection(this, simpleSelection(top)); }), replaceRange: function(code, from, to, origin) { @@ -6101,13 +6711,13 @@ } return parts; }, - replaceSelection: docMethodOp(function(code, collapse, origin) { + replaceSelection: function(code, collapse, origin) { var dup = []; for (var i = 0; i < this.sel.ranges.length; i++) dup[i] = code; this.replaceSelections(dup, collapse, origin || "+input"); - }), - replaceSelections: function(code, collapse, origin) { + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { var changes = [], sel = this.sel; for (var i = 0; i < sel.ranges.length; i++) { var range = sel.ranges[i]; @@ -6118,7 +6728,7 @@ makeChange(this, changes[i]); if (newSel) setSelectionReplaceHistory(this, newSel); else if (this.cm) ensureCursorVisible(this.cm); - }, + }), undo: docMethodOp(function() {makeChangeFromHistory(this, "undo");}), redo: docMethodOp(function() {makeChangeFromHistory(this, "redo");}), undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true);}), @@ -6140,7 +6750,7 @@ }, changeGeneration: function(forceSplit) { if (forceSplit) - this.history.lastOp = this.history.lastOrigin = null; + this.history.lastOp = this.history.lastSelOp = this.history.lastOrigin = null; return this.history.generation; }, isClean: function (gen) { @@ -6157,6 +6767,35 @@ hist.undone = copyHistoryArray(histData.undone.slice(0), null, true); }, + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + if (!line[prop]) line[prop] = cls; + else if (classTest(cls).test(line[prop])) return false; + else line[prop] += " " + cls; + return true; + }); + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", function(line) { + var prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass"; + var cur = line[prop]; + if (!cur) return false; + else if (cls == null) line[prop] = null; + else { + var found = cur.match(classTest(cls)); + if (!found) return false; + var end = found.index + found[0].length; + line[prop] = cur.slice(0, found.index) + (!found.index || end == cur.length ? "" : " ") + cur.slice(end) || null; + } + return true; + }); + }), + markText: function(from, to, options) { return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); }, @@ -6178,7 +6817,7 @@ } return markers; }, - findMarks: function(from, to) { + findMarks: function(from, to, filter) { from = clipPos(this, from); to = clipPos(this, to); var found = [], lineNo = from.line; this.iter(from.line, to.line + 1, function(line) { @@ -6187,7 +6826,8 @@ var span = spans[i]; if (!(lineNo == from.line && from.ch > span.to || span.from == null && lineNo != from.line|| - lineNo == to.line && span.from > to.ch)) + lineNo == to.line && span.from > to.ch) && + (!filter || filter(span.marker))) found.push(span.marker.parent || span.marker); } ++lineNo; @@ -6245,6 +6885,7 @@ if (options.sharedHist) copy.history = this.history; (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}); copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}]; + copySharedMarkers(copy, findSharedMarkers(this)); return copy; }, unlinkDoc: function(other) { @@ -6254,6 +6895,7 @@ if (link.doc != other) continue; this.linked.splice(i, 1); other.unlinkDoc(this); + detachSharedMarkers(findSharedMarkers(this)); break; } // If the histories were shared, split them again @@ -6429,7 +7071,7 @@ // Used to track when changes can be merged into a single undo // event this.lastModTime = this.lastSelTime = 0; - this.lastOp = null; + this.lastOp = this.lastSelOp = null; this.lastOrigin = this.lastSelOrigin = null; // Used by the isClean() method this.generation = this.maxGeneration = startGen || 1; @@ -6507,7 +7149,7 @@ hist.done.push(selAfter); hist.generation = ++hist.maxGeneration; hist.lastModTime = hist.lastSelTime = time; - hist.lastOp = opId; + hist.lastOp = hist.lastSelOp = opId; hist.lastOrigin = hist.lastSelOrigin = change.origin; if (!last) signal(doc, "historyAdded"); @@ -6533,7 +7175,7 @@ // the current, or the origins don't allow matching. Origins // starting with * are always merged, those starting with + are // merged when similar and close together in time. - if (opId == hist.lastOp || + if (opId == hist.lastSelOp || (origin && hist.lastSelOrigin == origin && (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) @@ -6543,7 +7185,7 @@ hist.lastSelTime = +new Date; hist.lastSelOrigin = origin; - hist.lastOp = opId; + hist.lastSelOp = opId; if (options && options.clearRedo !== false) clearSelectionEvents(hist.undone); } @@ -6728,6 +7370,8 @@ for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); }; + var orphanDelayedCallbacks = null; + // Often, we want to signal events at a point where we are in the // middle of some work, but don't want the handler to start calling // other methods on the editor, which might be in an inconsistent @@ -6735,25 +7379,26 @@ // signalLater looks whether there are any handlers, and schedules // them to be executed when the last operation ends, or, if no // operation is active, when a timeout fires. - var delayedCallbacks, delayedCallbackDepth = 0; function signalLater(emitter, type /*, values...*/) { var arr = emitter._handlers && emitter._handlers[type]; if (!arr) return; - var args = Array.prototype.slice.call(arguments, 2); - if (!delayedCallbacks) { - ++delayedCallbackDepth; - delayedCallbacks = []; - setTimeout(fireDelayed, 0); + var args = Array.prototype.slice.call(arguments, 2), list; + if (operationGroup) { + list = operationGroup.delayedCallbacks; + } else if (orphanDelayedCallbacks) { + list = orphanDelayedCallbacks; + } else { + list = orphanDelayedCallbacks = []; + setTimeout(fireOrphanDelayed, 0); } function bnd(f) {return function(){f.apply(null, args);};}; for (var i = 0; i < arr.length; ++i) - delayedCallbacks.push(bnd(arr[i])); + list.push(bnd(arr[i])); } - function fireDelayed() { - --delayedCallbackDepth; - var delayed = delayedCallbacks; - delayedCallbacks = null; + function fireOrphanDelayed() { + var delayed = orphanDelayedCallbacks; + orphanDelayedCallbacks = null; for (var i = 0; i < delayed.length; ++i) delayed[i](); } @@ -6761,10 +7406,20 @@ // registering a (non-DOM) handler on the editor for the event name, // and preventDefault-ing the event in that handler. function signalDOMEvent(cm, e, override) { + if (typeof e == "string") + e = {type: e, preventDefault: function() { this.defaultPrevented = true; }}; signal(cm, override || e.type, cm, e); return e_defaultPrevented(e) || e.codemirrorIgnore; } + function signalCursorActivity(cm) { + var arr = cm._handlers && cm._handlers.cursorActivity; + if (!arr) return; + var set = cm.curOp.cursorActivityHandlers || (cm.curOp.cursorActivityHandlers = []); + for (var i = 0; i < arr.length; ++i) if (indexOf(set, arr[i]) == -1) + set.push(arr[i]); + } + function hasHandler(emitter, type) { var arr = emitter._handlers && emitter._handlers[type]; return arr && arr.length > 0; @@ -6780,7 +7435,7 @@ // MISC UTILITIES // Number of pixels added to scroller and sizer to hide scrollbar - var scrollerCutOff = 30; + var scrollerGap = 30; // Returned or thrown by various protocols to signal 'I'm not // handling this'. @@ -6848,13 +7503,11 @@ if (array[i] == elt) return i; return -1; } - if ([].indexOf) indexOf = function(array, elt) { return array.indexOf(elt); }; function map(array, f) { var out = []; for (var i = 0; i < array.length; i++) out[i] = f(array[i], i); return out; } - if ([].map) map = function(array, f) { return array.map(f); }; function createObj(base, props) { var inst; @@ -6869,9 +7522,11 @@ return inst; }; - function copyObj(obj, target) { + function copyObj(obj, target, overwrite) { if (!target) target = {}; - for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; + for (var prop in obj) + if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + target[prop] = obj[prop]; return target; } @@ -6880,11 +7535,16 @@ return function(){return f.apply(null, args);}; } - var nonASCIISingleCaseWordChar = /[\u00df\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; - var isWordChar = CodeMirror.isWordChar = function(ch) { + var nonASCIISingleCaseWordChar = /[\u00df\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; + var isWordCharBasic = CodeMirror.isWordChar = function(ch) { return /\w/.test(ch) || ch > "\x80" && (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); }; + function isWordChar(ch, helper) { + if (!helper) return isWordCharBasic(ch); + if (helper.source.indexOf("\\w") > -1 && isWordCharBasic(ch)) return true; + return helper.test(ch); + } function isEmpty(obj) { for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; @@ -6919,7 +7579,8 @@ }; else range = function(node, start, end) { var r = document.body.createTextRange(); - r.moveToElementText(node.parentNode); + try { r.moveToElementText(node.parentNode); } + catch(e) { return r; } r.collapse(true); r.moveEnd("character", end); r.moveStart("character", start); @@ -6936,49 +7597,97 @@ return removeChildren(parent).appendChild(e); } - function contains(parent, child) { + var contains = CodeMirror.contains = function(parent, child) { if (parent.contains) return parent.contains(child); - while (child = child.parentNode) + while (child = child.parentNode) { + if (child.nodeType == 11) child = child.host; if (child == parent) return true; - } + } + }; function activeElt() { return document.activeElement; } // Older versions of IE throws unspecified error when touching // document.activeElement in some cases (during loading, in iframe) - if (ie_upto10) activeElt = function() { + if (ie && ie_version < 11) activeElt = function() { try { return document.activeElement; } catch(e) { return document.body; } }; + function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*"); } + var rmClass = CodeMirror.rmClass = function(node, cls) { + var current = node.className; + var match = classTest(cls).exec(current); + if (match) { + var after = current.slice(match.index + match[0].length); + node.className = current.slice(0, match.index) + (after ? match[1] + after : ""); + } + }; + var addClass = CodeMirror.addClass = function(node, cls) { + var current = node.className; + if (!classTest(cls).test(current)) node.className += (current ? " " : "") + cls; + }; + function joinClasses(a, b) { + var as = a.split(" "); + for (var i = 0; i < as.length; i++) + if (as[i] && !classTest(as[i]).test(b)) b += " " + as[i]; + return b; + } + + // WINDOW-WIDE EVENTS + + // These must be handled carefully, because naively registering a + // handler for each editor will cause the editors to never be + // garbage collected. + + function forEachCodeMirror(f) { + if (!document.body.getElementsByClassName) return; + var byClass = document.body.getElementsByClassName("CodeMirror"); + for (var i = 0; i < byClass.length; i++) { + var cm = byClass[i].CodeMirror; + if (cm) f(cm); + } + } + + var globalsRegistered = false; + function ensureGlobalHandlers() { + if (globalsRegistered) return; + registerGlobalHandlers(); + globalsRegistered = true; + } + function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + var resizeTimer; + on(window, "resize", function() { + if (resizeTimer == null) resizeTimer = setTimeout(function() { + resizeTimer = null; + forEachCodeMirror(onResize); + }, 100); + }); + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", function() { + forEachCodeMirror(onBlur); + }); + } + // FEATURE DETECTION // Detect drag-and-drop var dragAndDrop = function() { // There is *some* kind of drag-and-drop support in IE6-8, but I // couldn't get it to work yet. - if (ie_upto8) return false; + if (ie && ie_version < 9) return false; var div = elt('div'); return "draggable" in div || "dragDrop" in div; }(); - var knownScrollbarWidth; - function scrollbarWidth(measure) { - if (knownScrollbarWidth != null) return knownScrollbarWidth; - var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll"); - removeChildrenAndAdd(measure, test); - if (test.offsetWidth) - knownScrollbarWidth = test.offsetHeight - test.clientHeight; - return knownScrollbarWidth || 0; - } - var zwspSupported; function zeroWidthElement(measure) { if (zwspSupported == null) { var test = elt("span", "\u200b"); removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")])); if (measure.firstChild.offsetHeight != 0) - zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_upto7; + zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !(ie && ie_version < 8); } if (zwspSupported) return elt("span", "\u200b"); else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); @@ -6990,7 +7699,7 @@ if (badBidiRects != null) return badBidiRects; var txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")); var r0 = range(txt, 0, 1).getBoundingClientRect(); - if (r0.left == r0.right) return false; + if (!r0 || r0.left == r0.right) return false; // Safari returns null in some cases (#2780) var r1 = range(txt, 1, 2).getBoundingClientRect(); return badBidiRects = (r1.right - r0.right < 3); } @@ -7032,6 +7741,15 @@ return typeof e.oncopy == "function"; })(); + var badZoomedRects = null; + function hasBadZoomedRects(measure) { + if (badZoomedRects != null) return badZoomedRects; + var node = removeChildrenAndAdd(measure, elt("span", "x")); + var normal = node.getBoundingClientRect(); + var fromRange = range(node, 0, 1).getBoundingClientRect(); + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1; + } + // KEY NAMES var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", @@ -7094,6 +7812,17 @@ var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); return Pos(lineN == null ? lineNo(line) : lineN, ch); } + function lineStartSmart(cm, pos) { + var start = lineStart(cm, pos.line); + var line = getLine(cm.doc, start.line); + var order = getOrder(line); + if (!order || order[0].level == 0) { + var firstNonWS = Math.max(0, line.text.search(/\S/)); + var inWS = pos.line == start.line && pos.ch <= firstNonWS && pos.ch; + return Pos(start.line, inWS ? 0 : firstNonWS); + } + return start; + } function compareBidiLevel(order, a, b) { var linedir = order[0].level; @@ -7333,7 +8062,7 @@ // THE END - CodeMirror.version = "4.0.3"; + CodeMirror.version = "4.13.0"; return CodeMirror; }); diff --git a/applications/admin/static/codemirror/mode/clike/clike.js b/applications/admin/static/codemirror/mode/clike/clike.js deleted file mode 100644 index f6626cd0..00000000 --- a/applications/admin/static/codemirror/mode/clike/clike.js +++ /dev/null @@ -1,362 +0,0 @@ -CodeMirror.defineMode("clike", function(config, parserConfig) { - var indentUnit = config.indentUnit, - statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, - dontAlignCalls = parserConfig.dontAlignCalls, - keywords = parserConfig.keywords || {}, - builtin = parserConfig.builtin || {}, - blockKeywords = parserConfig.blockKeywords || {}, - atoms = parserConfig.atoms || {}, - hooks = parserConfig.hooks || {}, - multiLineStrings = parserConfig.multiLineStrings; - var isOperatorChar = /[+\-*&%=<>!?|\/]/; - - var curPunc; - - function tokenBase(stream, state) { - var ch = stream.next(); - if (hooks[ch]) { - var result = hooks[ch](stream, state); - if (result !== false) return result; - } - if (ch == '"' || ch == "'") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } - if (/[\[\]{}\(\),;\:\.]/.test(ch)) { - curPunc = ch; - return null; - } - if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return "number"; - } - if (ch == "/") { - if (stream.eat("*")) { - state.tokenize = tokenComment; - return tokenComment(stream, state); - } - if (stream.eat("/")) { - stream.skipToEnd(); - return "comment"; - } - } - if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return "operator"; - } - stream.eatWhile(/[\w\$_]/); - var cur = stream.current(); - if (keywords.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "keyword"; - } - if (builtin.propertyIsEnumerable(cur)) { - if (blockKeywords.propertyIsEnumerable(cur)) curPunc = "newstatement"; - return "builtin"; - } - if (atoms.propertyIsEnumerable(cur)) return "atom"; - return "variable"; - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) {end = true; break;} - escaped = !escaped && next == "\\"; - } - if (end || !(escaped || multiLineStrings)) - state.tokenize = null; - return "string"; - }; - } - - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "/" && maybeEnd) { - state.tokenize = null; - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - - function Context(indented, column, type, align, prev) { - this.indented = indented; - this.column = column; - this.type = type; - this.align = align; - this.prev = prev; - } - function pushContext(state, col, type) { - var indent = state.indented; - if (state.context && state.context.type == "statement") - indent = state.context.indented; - return state.context = new Context(indent, col, type, null, state.context); - } - function popContext(state) { - var t = state.context.type; - if (t == ")" || t == "]" || t == "}") - state.indented = state.context.indented; - return state.context = state.context.prev; - } - - // Interface - - return { - startState: function(basecolumn) { - return { - tokenize: null, - context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), - indented: 0, - startOfLine: true - }; - }, - - token: function(stream, state) { - var ctx = state.context; - if (stream.sol()) { - if (ctx.align == null) ctx.align = false; - state.indented = stream.indentation(); - state.startOfLine = true; - } - if (stream.eatSpace()) return null; - curPunc = null; - var style = (state.tokenize || tokenBase)(stream, state); - if (style == "comment" || style == "meta") return style; - if (ctx.align == null) ctx.align = true; - - if ((curPunc == ";" || curPunc == ":" || curPunc == ",") && ctx.type == "statement") popContext(state); - else if (curPunc == "{") pushContext(state, stream.column(), "}"); - else if (curPunc == "[") pushContext(state, stream.column(), "]"); - else if (curPunc == "(") pushContext(state, stream.column(), ")"); - else if (curPunc == "}") { - while (ctx.type == "statement") ctx = popContext(state); - if (ctx.type == "}") ctx = popContext(state); - while (ctx.type == "statement") ctx = popContext(state); - } - else if (curPunc == ctx.type) popContext(state); - else if (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || (ctx.type == "statement" && curPunc == "newstatement")) - pushContext(state, stream.column(), "statement"); - state.startOfLine = false; - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; - var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); - if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; - var closing = firstChar == ctx.type; - if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); - else if (ctx.align && (!dontAlignCalls || ctx.type != ")")) return ctx.column + (closing ? 0 : 1); - else if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit; - else return ctx.indented + (closing ? 0 : indentUnit); - }, - - electricChars: "{}", - blockCommentStart: "/*", - blockCommentEnd: "*/", - lineComment: "//", - fold: "brace" - }; -}); - -(function() { - function words(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - var cKeywords = "auto if break int case long char register continue return default short do sizeof " + - "double static else struct entry switch extern typedef float union for unsigned " + - "goto while enum void const signed volatile"; - - function cppHook(stream, state) { - if (!state.startOfLine) return false; - for (;;) { - if (stream.skipTo("\\")) { - stream.next(); - if (stream.eol()) { - state.tokenize = cppHook; - break; - } - } else { - stream.skipToEnd(); - state.tokenize = null; - break; - } - } - return "meta"; - } - - // C#-style strings where "" escapes a quote. - function tokenAtString(stream, state) { - var next; - while ((next = stream.next()) != null) { - if (next == '"' && !stream.eat('"')) { - state.tokenize = null; - break; - } - } - return "string"; - } - - function mimes(ms, mode) { - for (var i = 0; i < ms.length; ++i) CodeMirror.defineMIME(ms[i], mode); - } - - mimes(["text/x-csrc", "text/x-c", "text/x-chdr"], { - name: "clike", - keywords: words(cKeywords), - blockKeywords: words("case do else for if switch while struct"), - atoms: words("null"), - hooks: {"#": cppHook} - }); - mimes(["text/x-c++src", "text/x-c++hdr"], { - name: "clike", - keywords: words(cKeywords + " asm dynamic_cast namespace reinterpret_cast try bool explicit new " + - "static_cast typeid catch operator template typename class friend private " + - "this using const_cast inline public throw virtual delete mutable protected " + - "wchar_t"), - blockKeywords: words("catch class do else finally for if struct switch try while"), - atoms: words("true false null"), - hooks: {"#": cppHook} - }); - CodeMirror.defineMIME("text/x-java", { - name: "clike", - keywords: words("abstract assert boolean break byte case catch char class const continue default " + - "do double else enum extends final finally float for goto if implements import " + - "instanceof int interface long native new package private protected public " + - "return short static strictfp super switch synchronized this throw throws transient " + - "try void volatile while"), - blockKeywords: words("catch class do else finally for if switch try while"), - atoms: words("true false null"), - hooks: { - "@": function(stream) { - stream.eatWhile(/[\w\$_]/); - return "meta"; - } - } - }); - CodeMirror.defineMIME("text/x-csharp", { - name: "clike", - keywords: words("abstract as base break case catch checked class const continue" + - " default delegate do else enum event explicit extern finally fixed for" + - " foreach goto if implicit in interface internal is lock namespace new" + - " operator out override params private protected public readonly ref return sealed" + - " sizeof stackalloc static struct switch this throw try typeof unchecked" + - " unsafe using virtual void volatile while add alias ascending descending dynamic from get" + - " global group into join let orderby partial remove select set value var yield"), - blockKeywords: words("catch class do else finally for foreach if struct switch try while"), - builtin: words("Boolean Byte Char DateTime DateTimeOffset Decimal Double" + - " Guid Int16 Int32 Int64 Object SByte Single String TimeSpan UInt16 UInt32" + - " UInt64 bool byte char decimal double short int long object" + - " sbyte float string ushort uint ulong"), - atoms: words("true false null"), - hooks: { - "@": function(stream, state) { - if (stream.eat('"')) { - state.tokenize = tokenAtString; - return tokenAtString(stream, state); - } - stream.eatWhile(/[\w\$_]/); - return "meta"; - } - } - }); - CodeMirror.defineMIME("text/x-scala", { - name: "clike", - keywords: words( - - /* scala */ - "abstract case catch class def do else extends false final finally for forSome if " + - "implicit import lazy match new null object override package private protected return " + - "sealed super this throw trait try trye type val var while with yield _ : = => <- <: " + - "<% >: # @ " + - - /* package scala */ - "assert assume require print println printf readLine readBoolean readByte readShort " + - "readChar readInt readLong readFloat readDouble " + - - "AnyVal App Application Array BufferedIterator BigDecimal BigInt Char Console Either " + - "Enumeration Equiv Error Exception Fractional Function IndexedSeq Integral Iterable " + - "Iterator List Map Numeric Nil NotNull Option Ordered Ordering PartialFunction PartialOrdering " + - "Product Proxy Range Responder Seq Serializable Set Specializable Stream StringBuilder " + - "StringContext Symbol Throwable Traversable TraversableOnce Tuple Unit Vector :: #:: " + - - /* package java.lang */ - "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable " + - "Compiler Double Exception Float Integer Long Math Number Object Package Pair Process " + - "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " + - "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void" - - - ), - blockKeywords: words("catch class do else finally for forSome if match switch try while"), - atoms: words("true false null"), - hooks: { - "@": function(stream) { - stream.eatWhile(/[\w\$_]/); - return "meta"; - } - } - }); - mimes(["x-shader/x-vertex", "x-shader/x-fragment"], { - name: "clike", - keywords: words("float int bool void " + - "vec2 vec3 vec4 ivec2 ivec3 ivec4 bvec2 bvec3 bvec4 " + - "mat2 mat3 mat4 " + - "sampler1D sampler2D sampler3D samplerCube " + - "sampler1DShadow sampler2DShadow" + - "const attribute uniform varying " + - "break continue discard return " + - "for while do if else struct " + - "in out inout"), - blockKeywords: words("for while do if else struct"), - builtin: words("radians degrees sin cos tan asin acos atan " + - "pow exp log exp2 sqrt inversesqrt " + - "abs sign floor ceil fract mod min max clamp mix step smootstep " + - "length distance dot cross normalize ftransform faceforward " + - "reflect refract matrixCompMult " + - "lessThan lessThanEqual greaterThan greaterThanEqual " + - "equal notEqual any all not " + - "texture1D texture1DProj texture1DLod texture1DProjLod " + - "texture2D texture2DProj texture2DLod texture2DProjLod " + - "texture3D texture3DProj texture3DLod texture3DProjLod " + - "textureCube textureCubeLod " + - "shadow1D shadow2D shadow1DProj shadow2DProj " + - "shadow1DLod shadow2DLod shadow1DProjLod shadow2DProjLod " + - "dFdx dFdy fwidth " + - "noise1 noise2 noise3 noise4"), - atoms: words("true false " + - "gl_FragColor gl_SecondaryColor gl_Normal gl_Vertex " + - "gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 " + - "gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 " + - "gl_FogCoord " + - "gl_Position gl_PointSize gl_ClipVertex " + - "gl_FrontColor gl_BackColor gl_FrontSecondaryColor gl_BackSecondaryColor " + - "gl_TexCoord gl_FogFragCoord " + - "gl_FragCoord gl_FrontFacing " + - "gl_FragColor gl_FragData gl_FragDepth " + - "gl_ModelViewMatrix gl_ProjectionMatrix gl_ModelViewProjectionMatrix " + - "gl_TextureMatrix gl_NormalMatrix gl_ModelViewMatrixInverse " + - "gl_ProjectionMatrixInverse gl_ModelViewProjectionMatrixInverse " + - "gl_TexureMatrixTranspose gl_ModelViewMatrixInverseTranspose " + - "gl_ProjectionMatrixInverseTranspose " + - "gl_ModelViewProjectionMatrixInverseTranspose " + - "gl_TextureMatrixInverseTranspose " + - "gl_NormalScale gl_DepthRange gl_ClipPlane " + - "gl_Point gl_FrontMaterial gl_BackMaterial gl_LightSource gl_LightModel " + - "gl_FrontLightModelProduct gl_BackLightModelProduct " + - "gl_TextureColor gl_EyePlaneS gl_EyePlaneT gl_EyePlaneR gl_EyePlaneQ " + - "gl_FogParameters " + - "gl_MaxLights gl_MaxClipPlanes gl_MaxTextureUnits gl_MaxTextureCoords " + - "gl_MaxVertexAttribs gl_MaxVertexUniformComponents gl_MaxVaryingFloats " + - "gl_MaxVertexTextureImageUnits gl_MaxTextureImageUnits " + - "gl_MaxFragmentUniformComponents gl_MaxCombineTextureImageUnits " + - "gl_MaxDrawBuffers"), - hooks: {"#": cppHook} - }); -}()); diff --git a/applications/admin/static/codemirror/mode/clike/index.html b/applications/admin/static/codemirror/mode/clike/index.html deleted file mode 100644 index 45add491..00000000 --- a/applications/admin/static/codemirror/mode/clike/index.html +++ /dev/null @@ -1,195 +0,0 @@ - - -CodeMirror: C-like mode - - - - - - - - - - -
-

C-like mode

- -
- -

C++ example

- -
- -

Java example

- -
- - - -

Simple mode that tries to handle C-like languages as well as it - can. Takes two configuration parameters: keywords, an - object whose property names are the keywords in the language, - and useCPP, which determines whether C preprocessor - directives are recognized.

- -

MIME types defined: text/x-csrc - (C code), text/x-c++src (C++ - code), text/x-java (Java - code), text/x-csharp (C#).

-
diff --git a/applications/admin/static/codemirror/mode/clike/scala.html b/applications/admin/static/codemirror/mode/clike/scala.html deleted file mode 100644 index e9acc049..00000000 --- a/applications/admin/static/codemirror/mode/clike/scala.html +++ /dev/null @@ -1,767 +0,0 @@ - - -CodeMirror: Scala mode - - - - - - - - - - -
-

Scala mode

-
- -
- - -
diff --git a/applications/admin/static/codemirror/mode/clojure/clojure.js b/applications/admin/static/codemirror/mode/clojure/clojure.js deleted file mode 100644 index ee22a12f..00000000 --- a/applications/admin/static/codemirror/mode/clojure/clojure.js +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Author: Hans Engel - * Branched from CodeMirror's Scheme mode (by Koh Zi Han, based on implementation by Koh Zi Chun) - */ -CodeMirror.defineMode("clojure", function () { - var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", CHARACTER = "string-2", - ATOM = "atom", NUMBER = "number", BRACKET = "bracket", KEYWORD = "keyword"; - var INDENT_WORD_SKIP = 2; - - function makeKeywords(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - var atoms = makeKeywords("true false nil"); - - var keywords = makeKeywords( - "defn defn- def def- defonce defmulti defmethod defmacro defstruct deftype defprotocol defrecord defproject deftest slice defalias defhinted defmacro- defn-memo defnk defnk defonce- defunbound defunbound- defvar defvar- let letfn do case cond condp for loop recur when when-not when-let when-first if if-let if-not . .. -> ->> doto and or dosync doseq dotimes dorun doall load import unimport ns in-ns refer try catch finally throw with-open with-local-vars binding gen-class gen-and-load-class gen-and-save-class handler-case handle"); - - var builtins = makeKeywords( - "* *' *1 *2 *3 *agent* *allow-unresolved-vars* *assert* *clojure-version* *command-line-args* *compile-files* *compile-path* *compiler-options* *data-readers* *e *err* *file* *flush-on-newline* *fn-loader* *in* *math-context* *ns* *out* *print-dup* *print-length* *print-level* *print-meta* *print-readably* *read-eval* *source-path* *unchecked-math* *use-context-classloader* *verbose-defrecords* *warn-on-reflection* + +' - -' -> ->> ->ArrayChunk ->Vec ->VecNode ->VecSeq -cache-protocol-fn -reset-methods .. / < <= = == > >= EMPTY-NODE accessor aclone add-classpath add-watch agent agent-error agent-errors aget alength alias all-ns alter alter-meta! alter-var-root amap ancestors and apply areduce array-map aset aset-boolean aset-byte aset-char aset-double aset-float aset-int aset-long aset-short assert assoc assoc! assoc-in associative? atom await await-for await1 bases bean bigdec bigint biginteger binding bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set bit-shift-left bit-shift-right bit-test bit-xor boolean boolean-array booleans bound-fn bound-fn* bound? butlast byte byte-array bytes case cast char char-array char-escape-string char-name-string char? chars chunk chunk-append chunk-buffer chunk-cons chunk-first chunk-next chunk-rest chunked-seq? class class? clear-agent-errors clojure-version coll? comment commute comp comparator compare compare-and-set! compile complement concat cond condp conj conj! cons constantly construct-proxy contains? count counted? create-ns create-struct cycle dec dec' decimal? declare default-data-readers definline definterface defmacro defmethod defmulti defn defn- defonce defprotocol defrecord defstruct deftype delay delay? deliver denominator deref derive descendants destructure disj disj! dissoc dissoc! distinct distinct? doall dorun doseq dosync dotimes doto double double-array doubles drop drop-last drop-while empty empty? ensure enumeration-seq error-handler error-mode eval even? every-pred every? ex-data ex-info extend extend-protocol extend-type extenders extends? false? ffirst file-seq filter filterv find find-keyword find-ns find-protocol-impl find-protocol-method find-var first flatten float float-array float? floats flush fn fn? fnext fnil for force format frequencies future future-call future-cancel future-cancelled? future-done? future? gen-class gen-interface gensym get get-in get-method get-proxy-class get-thread-bindings get-validator group-by hash hash-combine hash-map hash-set identical? identity if-let if-not ifn? import in-ns inc inc' init-proxy instance? int int-array integer? interleave intern interpose into into-array ints io! isa? iterate iterator-seq juxt keep keep-indexed key keys keyword keyword? last lazy-cat lazy-seq let letfn line-seq list list* list? load load-file load-reader load-string loaded-libs locking long long-array longs loop macroexpand macroexpand-1 make-array make-hierarchy map map-indexed map? mapcat mapv max max-key memfn memoize merge merge-with meta method-sig methods min min-key mod munge name namespace namespace-munge neg? newline next nfirst nil? nnext not not-any? not-empty not-every? not= ns ns-aliases ns-imports ns-interns ns-map ns-name ns-publics ns-refers ns-resolve ns-unalias ns-unmap nth nthnext nthrest num number? numerator object-array odd? or parents partial partition partition-all partition-by pcalls peek persistent! pmap pop pop! pop-thread-bindings pos? pr pr-str prefer-method prefers primitives-classnames print print-ctor print-dup print-method print-simple print-str printf println println-str prn prn-str promise proxy proxy-call-with-super proxy-mappings proxy-name proxy-super push-thread-bindings pvalues quot rand rand-int rand-nth range ratio? rational? rationalize re-find re-groups re-matcher re-matches re-pattern re-seq read read-line read-string realized? reduce reduce-kv reductions ref ref-history-count ref-max-history ref-min-history ref-set refer refer-clojure reify release-pending-sends rem remove remove-all-methods remove-method remove-ns remove-watch repeat repeatedly replace replicate require reset! reset-meta! resolve rest restart-agent resultset-seq reverse reversible? rseq rsubseq satisfies? second select-keys send send-off seq seq? seque sequence sequential? set set-error-handler! set-error-mode! set-validator! set? short short-array shorts shuffle shutdown-agents slurp some some-fn sort sort-by sorted-map sorted-map-by sorted-set sorted-set-by sorted? special-symbol? spit split-at split-with str string? struct struct-map subs subseq subvec supers swap! symbol symbol? sync take take-last take-nth take-while test the-ns thread-bound? time to-array to-array-2d trampoline transient tree-seq true? type unchecked-add unchecked-add-int unchecked-byte unchecked-char unchecked-dec unchecked-dec-int unchecked-divide-int unchecked-double unchecked-float unchecked-inc unchecked-inc-int unchecked-int unchecked-long unchecked-multiply unchecked-multiply-int unchecked-negate unchecked-negate-int unchecked-remainder-int unchecked-short unchecked-subtract unchecked-subtract-int underive unquote unquote-splicing update-in update-proxy use val vals var-get var-set var? vary-meta vec vector vector-of vector? when when-first when-let when-not while with-bindings with-bindings* with-in-str with-loading-context with-local-vars with-meta with-open with-out-str with-precision with-redefs with-redefs-fn xml-seq zero? zipmap *default-data-reader-fn* as-> cond-> cond->> reduced reduced? send-via set-agent-send-executor! set-agent-send-off-executor! some-> some->>"); - - var indentKeys = makeKeywords( - // Built-ins - "ns fn def defn defmethod bound-fn if if-not case condp when while when-not when-first do future comment doto locking proxy with-open with-precision reify deftype defrecord defprotocol extend extend-protocol extend-type try catch " + - - // Binding forms - "let letfn binding loop for doseq dotimes when-let if-let " + - - // Data structures - "defstruct struct-map assoc " + - - // clojure.test - "testing deftest " + - - // contrib - "handler-case handle dotrace deftrace"); - - var tests = { - digit: /\d/, - digit_or_colon: /[\d:]/, - hex: /[0-9a-f]/i, - sign: /[+-]/, - exponent: /e/i, - keyword_char: /[^\s\(\[\;\)\]]/, - symbol: /[\w*+!\-\._?:\/]/ - }; - - function stateStack(indent, type, prev) { // represents a state stack object - this.indent = indent; - this.type = type; - this.prev = prev; - } - - function pushStack(state, indent, type) { - state.indentStack = new stateStack(indent, type, state.indentStack); - } - - function popStack(state) { - state.indentStack = state.indentStack.prev; - } - - function isNumber(ch, stream){ - // hex - if ( ch === '0' && stream.eat(/x/i) ) { - stream.eatWhile(tests.hex); - return true; - } - - // leading sign - if ( ( ch == '+' || ch == '-' ) && ( tests.digit.test(stream.peek()) ) ) { - stream.eat(tests.sign); - ch = stream.next(); - } - - if ( tests.digit.test(ch) ) { - stream.eat(ch); - stream.eatWhile(tests.digit); - - if ( '.' == stream.peek() ) { - stream.eat('.'); - stream.eatWhile(tests.digit); - } - - if ( stream.eat(tests.exponent) ) { - stream.eat(tests.sign); - stream.eatWhile(tests.digit); - } - - return true; - } - - return false; - } - - // Eat character that starts after backslash \ - function eatCharacter(stream) { - var first = stream.next(); - // Read special literals: backspace, newline, space, return. - // Just read all lowercase letters. - if (first.match(/[a-z]/) && stream.match(/[a-z]+/, true)) { - return; - } - // Read unicode character: \u1000 \uA0a1 - if (first === "u") { - stream.match(/[0-9a-z]{4}/i, true); - } - } - - return { - startState: function () { - return { - indentStack: null, - indentation: 0, - mode: false - }; - }, - - token: function (stream, state) { - if (state.indentStack == null && stream.sol()) { - // update indentation, but only if indentStack is empty - state.indentation = stream.indentation(); - } - - // skip spaces - if (stream.eatSpace()) { - return null; - } - var returnType = null; - - switch(state.mode){ - case "string": // multi-line string parsing mode - var next, escaped = false; - while ((next = stream.next()) != null) { - if (next == "\"" && !escaped) { - - state.mode = false; - break; - } - escaped = !escaped && next == "\\"; - } - returnType = STRING; // continue on in string mode - break; - default: // default parsing mode - var ch = stream.next(); - - if (ch == "\"") { - state.mode = "string"; - returnType = STRING; - } else if (ch == "\\") { - eatCharacter(stream); - returnType = CHARACTER; - } else if (ch == "'" && !( tests.digit_or_colon.test(stream.peek()) )) { - returnType = ATOM; - } else if (ch == ";") { // comment - stream.skipToEnd(); // rest of the line is a comment - returnType = COMMENT; - } else if (isNumber(ch,stream)){ - returnType = NUMBER; - } else if (ch == "(" || ch == "[" || ch == "{" ) { - var keyWord = '', indentTemp = stream.column(), letter; - /** - Either - (indent-word .. - (non-indent-word .. - (;something else, bracket, etc. - */ - - if (ch == "(") while ((letter = stream.eat(tests.keyword_char)) != null) { - keyWord += letter; - } - - if (keyWord.length > 0 && (indentKeys.propertyIsEnumerable(keyWord) || - /^(?:def|with)/.test(keyWord))) { // indent-word - pushStack(state, indentTemp + INDENT_WORD_SKIP, ch); - } else { // non-indent word - // we continue eating the spaces - stream.eatSpace(); - if (stream.eol() || stream.peek() == ";") { - // nothing significant after - // we restart indentation 1 space after - pushStack(state, indentTemp + 1, ch); - } else { - pushStack(state, indentTemp + stream.current().length, ch); // else we match - } - } - stream.backUp(stream.current().length - 1); // undo all the eating - - returnType = BRACKET; - } else if (ch == ")" || ch == "]" || ch == "}") { - returnType = BRACKET; - if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : (ch == "]" ? "[" :"{"))) { - popStack(state); - } - } else if ( ch == ":" ) { - stream.eatWhile(tests.symbol); - return ATOM; - } else { - stream.eatWhile(tests.symbol); - - if (keywords && keywords.propertyIsEnumerable(stream.current())) { - returnType = KEYWORD; - } else if (builtins && builtins.propertyIsEnumerable(stream.current())) { - returnType = BUILTIN; - } else if (atoms && atoms.propertyIsEnumerable(stream.current())) { - returnType = ATOM; - } else returnType = null; - } - } - - return returnType; - }, - - indent: function (state) { - if (state.indentStack == null) return state.indentation; - return state.indentStack.indent; - }, - - lineComment: ";;" - }; -}); - -CodeMirror.defineMIME("text/x-clojure", "clojure"); diff --git a/applications/admin/static/codemirror/mode/clojure/index.html b/applications/admin/static/codemirror/mode/clojure/index.html deleted file mode 100644 index 5a50c566..00000000 --- a/applications/admin/static/codemirror/mode/clojure/index.html +++ /dev/null @@ -1,88 +0,0 @@ - - -CodeMirror: Clojure mode - - - - - - - - - -
-

Clojure mode

-
- - -

MIME types defined: text/x-clojure.

- -
diff --git a/applications/admin/static/codemirror/mode/coffeescript/LICENSE b/applications/admin/static/codemirror/mode/coffeescript/LICENSE deleted file mode 100644 index 977e284e..00000000 --- a/applications/admin/static/codemirror/mode/coffeescript/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License - -Copyright (c) 2011 Jeff Pickhardt -Modified from the Python CodeMirror mode, Copyright (c) 2010 Timothy Farrell - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/applications/admin/static/codemirror/mode/coffeescript/coffeescript.js b/applications/admin/static/codemirror/mode/coffeescript/coffeescript.js deleted file mode 100644 index b7203f12..00000000 --- a/applications/admin/static/codemirror/mode/coffeescript/coffeescript.js +++ /dev/null @@ -1,348 +0,0 @@ -/** - * Link to the project's GitHub page: - * https://github.com/pickhardt/coffeescript-codemirror-mode - */ -CodeMirror.defineMode('coffeescript', function(conf) { - var ERRORCLASS = 'error'; - - function wordRegexp(words) { - return new RegExp("^((" + words.join(")|(") + "))\\b"); - } - - var singleOperators = new RegExp("^[\\+\\-\\*/%&|\\^~<>!\?]"); - var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\},:`=;\\.]'); - var doubleOperators = new RegExp("^((\->)|(\=>)|(\\+\\+)|(\\+\\=)|(\\-\\-)|(\\-\\=)|(\\*\\*)|(\\*\\=)|(\\/\\/)|(\\/\\=)|(==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//))"); - var doubleDelimiters = new RegExp("^((\\.\\.)|(\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); - var tripleDelimiters = new RegExp("^((\\.\\.\\.)|(//=)|(>>=)|(<<=)|(\\*\\*=))"); - var identifiers = new RegExp("^[_A-Za-z$][_A-Za-z$0-9]*"); - var properties = new RegExp("^(@|this\.)[_A-Za-z$][_A-Za-z$0-9]*"); - - var wordOperators = wordRegexp(['and', 'or', 'not', - 'is', 'isnt', 'in', - 'instanceof', 'typeof']); - var indentKeywords = ['for', 'while', 'loop', 'if', 'unless', 'else', - 'switch', 'try', 'catch', 'finally', 'class']; - var commonKeywords = ['break', 'by', 'continue', 'debugger', 'delete', - 'do', 'in', 'of', 'new', 'return', 'then', - 'this', 'throw', 'when', 'until']; - - var keywords = wordRegexp(indentKeywords.concat(commonKeywords)); - - indentKeywords = wordRegexp(indentKeywords); - - - var stringPrefixes = new RegExp("^('{3}|\"{3}|['\"])"); - var regexPrefixes = new RegExp("^(/{3}|/)"); - var commonConstants = ['Infinity', 'NaN', 'undefined', 'null', 'true', 'false', 'on', 'off', 'yes', 'no']; - var constants = wordRegexp(commonConstants); - - // Tokenizers - function tokenBase(stream, state) { - // Handle scope changes - if (stream.sol()) { - var scopeOffset = state.scopes[0].offset; - if (stream.eatSpace()) { - var lineOffset = stream.indentation(); - if (lineOffset > scopeOffset) { - return 'indent'; - } else if (lineOffset < scopeOffset) { - return 'dedent'; - } - return null; - } else { - if (scopeOffset > 0) { - dedent(stream, state); - } - } - } - if (stream.eatSpace()) { - return null; - } - - var ch = stream.peek(); - - // Handle docco title comment (single line) - if (stream.match("####")) { - stream.skipToEnd(); - return 'comment'; - } - - // Handle multi line comments - if (stream.match("###")) { - state.tokenize = longComment; - return state.tokenize(stream, state); - } - - // Single line comment - if (ch === '#') { - stream.skipToEnd(); - return 'comment'; - } - - // Handle number literals - if (stream.match(/^-?[0-9\.]/, false)) { - var floatLiteral = false; - // Floats - if (stream.match(/^-?\d*\.\d+(e[\+\-]?\d+)?/i)) { - floatLiteral = true; - } - if (stream.match(/^-?\d+\.\d*/)) { - floatLiteral = true; - } - if (stream.match(/^-?\.\d+/)) { - floatLiteral = true; - } - - if (floatLiteral) { - // prevent from getting extra . on 1.. - if (stream.peek() == "."){ - stream.backUp(1); - } - return 'number'; - } - // Integers - var intLiteral = false; - // Hex - if (stream.match(/^-?0x[0-9a-f]+/i)) { - intLiteral = true; - } - // Decimal - if (stream.match(/^-?[1-9]\d*(e[\+\-]?\d+)?/)) { - intLiteral = true; - } - // Zero by itself with no other piece of number. - if (stream.match(/^-?0(?![\dx])/i)) { - intLiteral = true; - } - if (intLiteral) { - return 'number'; - } - } - - // Handle strings - if (stream.match(stringPrefixes)) { - state.tokenize = tokenFactory(stream.current(), 'string'); - return state.tokenize(stream, state); - } - // Handle regex literals - if (stream.match(regexPrefixes)) { - if (stream.current() != '/' || stream.match(/^.*\//, false)) { // prevent highlight of division - state.tokenize = tokenFactory(stream.current(), 'string-2'); - return state.tokenize(stream, state); - } else { - stream.backUp(1); - } - } - - // Handle operators and delimiters - if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { - return 'punctuation'; - } - if (stream.match(doubleOperators) - || stream.match(singleOperators) - || stream.match(wordOperators)) { - return 'operator'; - } - if (stream.match(singleDelimiters)) { - return 'punctuation'; - } - - if (stream.match(constants)) { - return 'atom'; - } - - if (stream.match(keywords)) { - return 'keyword'; - } - - if (stream.match(identifiers)) { - return 'variable'; - } - - if (stream.match(properties)) { - return 'property'; - } - - // Handle non-detected items - stream.next(); - return ERRORCLASS; - } - - function tokenFactory(delimiter, outclass) { - var singleline = delimiter.length == 1; - return function(stream, state) { - while (!stream.eol()) { - stream.eatWhile(/[^'"\/\\]/); - if (stream.eat('\\')) { - stream.next(); - if (singleline && stream.eol()) { - return outclass; - } - } else if (stream.match(delimiter)) { - state.tokenize = tokenBase; - return outclass; - } else { - stream.eat(/['"\/]/); - } - } - if (singleline) { - if (conf.mode.singleLineStringErrors) { - outclass = ERRORCLASS; - } else { - state.tokenize = tokenBase; - } - } - return outclass; - }; - } - - function longComment(stream, state) { - while (!stream.eol()) { - stream.eatWhile(/[^#]/); - if (stream.match("###")) { - state.tokenize = tokenBase; - break; - } - stream.eatWhile("#"); - } - return "comment"; - } - - function indent(stream, state, type) { - type = type || 'coffee'; - var indentUnit = 0; - if (type === 'coffee') { - for (var i = 0; i < state.scopes.length; i++) { - if (state.scopes[i].type === 'coffee') { - indentUnit = state.scopes[i].offset + conf.indentUnit; - break; - } - } - } else { - indentUnit = stream.column() + stream.current().length; - } - state.scopes.unshift({ - offset: indentUnit, - type: type - }); - } - - function dedent(stream, state) { - if (state.scopes.length == 1) return; - if (state.scopes[0].type === 'coffee') { - var _indent = stream.indentation(); - var _indent_index = -1; - for (var i = 0; i < state.scopes.length; ++i) { - if (_indent === state.scopes[i].offset) { - _indent_index = i; - break; - } - } - if (_indent_index === -1) { - return true; - } - while (state.scopes[0].offset !== _indent) { - state.scopes.shift(); - } - return false; - } else { - state.scopes.shift(); - return false; - } - } - - function tokenLexer(stream, state) { - var style = state.tokenize(stream, state); - var current = stream.current(); - - // Handle '.' connected identifiers - if (current === '.') { - style = state.tokenize(stream, state); - current = stream.current(); - if (/^\.[\w$]+$/.test(current)) { - return 'variable'; - } else { - return ERRORCLASS; - } - } - - // Handle scope changes. - if (current === 'return') { - state.dedent += 1; - } - if (((current === '->' || current === '=>') && - !state.lambda && - state.scopes[0].type == 'coffee' && - stream.peek() === '') - || style === 'indent') { - indent(stream, state); - } - var delimiter_index = '[({'.indexOf(current); - if (delimiter_index !== -1) { - indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1)); - } - if (indentKeywords.exec(current)){ - indent(stream, state); - } - if (current == 'then'){ - dedent(stream, state); - } - - - if (style === 'dedent') { - if (dedent(stream, state)) { - return ERRORCLASS; - } - } - delimiter_index = '])}'.indexOf(current); - if (delimiter_index !== -1) { - if (dedent(stream, state)) { - return ERRORCLASS; - } - } - if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'coffee') { - if (state.scopes.length > 1) state.scopes.shift(); - state.dedent -= 1; - } - - return style; - } - - var external = { - startState: function(basecolumn) { - return { - tokenize: tokenBase, - scopes: [{offset:basecolumn || 0, type:'coffee'}], - lastToken: null, - lambda: false, - dedent: 0 - }; - }, - - token: function(stream, state) { - var style = tokenLexer(stream, state); - - state.lastToken = {style:style, content: stream.current()}; - - if (stream.eol() && stream.lambda) { - state.lambda = false; - } - - return style; - }, - - indent: function(state) { - if (state.tokenize != tokenBase) { - return 0; - } - - return state.scopes[0].offset; - }, - - lineComment: "#", - fold: "indent" - }; - return external; -}); - -CodeMirror.defineMIME('text/x-coffeescript', 'coffeescript'); diff --git a/applications/admin/static/codemirror/mode/coffeescript/index.html b/applications/admin/static/codemirror/mode/coffeescript/index.html deleted file mode 100644 index 6e6fde52..00000000 --- a/applications/admin/static/codemirror/mode/coffeescript/index.html +++ /dev/null @@ -1,740 +0,0 @@ - - -CodeMirror: CoffeeScript mode - - - - - - - - - -
-

CoffeeScript mode

-
- - -

MIME types defined: text/x-coffeescript.

- -

The CoffeeScript mode was written by Jeff Pickhardt (license).

- -
diff --git a/applications/admin/static/codemirror/mode/css/css.js b/applications/admin/static/codemirror/mode/css/css.js index f8fc5cea..34355aaa 100644 --- a/applications/admin/static/codemirror/mode/css/css.js +++ b/applications/admin/static/codemirror/mode/css/css.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -13,12 +16,15 @@ CodeMirror.defineMode("css", function(config, parserConfig) { var indentUnit = config.indentUnit, tokenHooks = parserConfig.tokenHooks, + documentTypes = parserConfig.documentTypes || {}, mediaTypes = parserConfig.mediaTypes || {}, mediaFeatures = parserConfig.mediaFeatures || {}, propertyKeywords = parserConfig.propertyKeywords || {}, + nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {}, + fontProperties = parserConfig.fontProperties || {}, + counterDescriptors = parserConfig.counterDescriptors || {}, colorKeywords = parserConfig.colorKeywords || {}, valueKeywords = parserConfig.valueKeywords || {}, - fontProperties = parserConfig.fontProperties || {}, allowNested = parserConfig.allowNested; var type, override; @@ -53,7 +59,12 @@ CodeMirror.defineMode("css", function(config, parserConfig) { if (/[\d.]/.test(stream.peek())) { stream.eatWhile(/[\w.%]/); return ret("number", "unit"); - } else if (stream.match(/^[^-]+-/)) { + } else if (stream.match(/^-[\w\\\-]+/)) { + stream.eatWhile(/[\w\\\-]/); + if (stream.match(/^\s*:/, false)) + return ret("variable-2", "variable-definition"); + return ret("variable-2", "variable"); + } else if (stream.match(/^\w+-/)) { return ret("meta", "meta"); } } else if (/[,+>*\/]/.test(ch)) { @@ -62,7 +73,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return ret("qualifier", "qualifier"); } else if (/[:;{}\[\]\(\)]/.test(ch)) { return ret(null, ch); - } else if (ch == "u" && stream.match("rl(")) { + } else if ((ch == "u" && stream.match(/rl(-prefix)?\(/)) || + (ch == "d" && stream.match("omain(")) || + (ch == "r" && stream.match("egexp("))) { stream.backUp(1); state.tokenize = tokenParenthesized; return ret("property", "word"); @@ -91,7 +104,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { function tokenParenthesized(stream, state) { stream.next(); // Must be '(' - if (!stream.match(/\s*[\"\']/, false)) + if (!stream.match(/\s*[\"\')]/, false)) state.tokenize = tokenString(")"); else state.tokenize = null; @@ -144,10 +157,11 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return pushContext(state, stream, "block"); } else if (type == "}" && state.context.prev) { return popContext(state); - } else if (type == "@media") { - return pushContext(state, stream, "media"); - } else if (type == "@font-face") { - return "font_face_before"; + } else if (/@(media|supports|(-moz-)?document)/.test(type)) { + return pushContext(state, stream, "atBlock"); + } else if (/@(font-face|counter-style)/.test(type)) { + state.stateArg = type; + return "restricted_atBlock_before"; } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) { return "keyframes"; } else if (type && type.charAt(0) == "@") { @@ -163,18 +177,22 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } else if (type == ":") { return "pseudo"; } else if (allowNested && type == "(") { - return pushContext(state, stream, "params"); + return pushContext(state, stream, "parens"); } return state.context.type; }; states.block = function(type, stream, state) { if (type == "word") { - if (propertyKeywords.hasOwnProperty(stream.current().toLowerCase())) { + var word = stream.current().toLowerCase(); + if (propertyKeywords.hasOwnProperty(word)) { override = "property"; return "maybeprop"; + } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) { + override = "string-2"; + return "maybeprop"; } else if (allowNested) { - override = stream.match(/^\s*:/, false) ? "property" : "tag"; + override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag"; return "block"; } else { override += " error"; @@ -220,6 +238,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) { states.parens = function(type, stream, state) { if (type == "{" || type == "}") return popAndPass(type, stream, state); if (type == ")") return popContext(state); + if (type == "(") return pushContext(state, stream, "parens"); + if (type == "word") wordAsValue(stream); return "parens"; }; @@ -231,47 +251,63 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return pass(type, stream, state); }; - states.media = function(type, stream, state) { - if (type == "(") return pushContext(state, stream, "media_parens"); + states.atBlock = function(type, stream, state) { + if (type == "(") return pushContext(state, stream, "atBlock_parens"); if (type == "}") return popAndPass(type, stream, state); if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top"); if (type == "word") { var word = stream.current().toLowerCase(); - if (word == "only" || word == "not" || word == "and") + if (word == "only" || word == "not" || word == "and" || word == "or") override = "keyword"; + else if (documentTypes.hasOwnProperty(word)) + override = "tag"; else if (mediaTypes.hasOwnProperty(word)) override = "attribute"; else if (mediaFeatures.hasOwnProperty(word)) override = "property"; + else if (propertyKeywords.hasOwnProperty(word)) + override = "property"; + else if (nonStandardPropertyKeywords.hasOwnProperty(word)) + override = "string-2"; + else if (valueKeywords.hasOwnProperty(word)) + override = "atom"; else override = "error"; } return state.context.type; }; - states.media_parens = function(type, stream, state) { + states.atBlock_parens = function(type, stream, state) { if (type == ")") return popContext(state); if (type == "{" || type == "}") return popAndPass(type, stream, state, 2); - return states.media(type, stream, state); + return states.atBlock(type, stream, state); }; - states.font_face_before = function(type, stream, state) { + states.restricted_atBlock_before = function(type, stream, state) { if (type == "{") - return pushContext(state, stream, "font_face"); + return pushContext(state, stream, "restricted_atBlock"); + if (type == "word" && state.stateArg == "@counter-style") { + override = "variable"; + return "restricted_atBlock_before"; + } return pass(type, stream, state); }; - states.font_face = function(type, stream, state) { - if (type == "}") return popContext(state); + states.restricted_atBlock = function(type, stream, state) { + if (type == "}") { + state.stateArg = null; + return popContext(state); + } if (type == "word") { - if (!fontProperties.hasOwnProperty(stream.current().toLowerCase())) + if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) || + (state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase()))) override = "error"; else override = "property"; return "maybeprop"; } - return "font_face"; + return "restricted_atBlock"; }; states.keyframes = function(type, stream, state) { @@ -295,17 +331,11 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return "interpolation"; }; - states.params = function(type, stream, state) { - if (type == ")") return popContext(state); - if (type == "{" || type == "}") return popAndPass(type, stream, state); - if (type == "word") wordAsValue(stream); - return "params"; - }; - return { startState: function(base) { return {tokenize: null, state: "top", + stateArg: null, context: new Context("top", base || 0, null)}; }, @@ -324,11 +354,11 @@ CodeMirror.defineMode("css", function(config, parserConfig) { indent: function(state, textAfter) { var cx = state.context, ch = textAfter && textAfter.charAt(0); var indent = cx.indent; - if (cx.type == "prop" && ch == "}") cx = cx.prev; + if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev; if (cx.prev && - (ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation" || cx.type == "font_face") || - ch == ")" && (cx.type == "parens" || cx.type == "params" || cx.type == "media_parens") || - ch == "{" && (cx.type == "at" || cx.type == "media"))) { + (ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation" || cx.type == "restricted_atBlock") || + ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") || + ch == "{" && (cx.type == "at" || cx.type == "atBlock"))) { indent = cx.indent - indentUnit; cx = cx.prev; } @@ -350,6 +380,10 @@ CodeMirror.defineMode("css", function(config, parserConfig) { return keys; } + var documentTypes_ = [ + "domain", "regexp", "url", "url-prefix" + ], documentTypes = keySet(documentTypes_); + var mediaTypes_ = [ "all", "aural", "braille", "handheld", "print", "projection", "screen", "tty", "tv", "embossed" @@ -417,7 +451,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "marker-offset", "marks", "marquee-direction", "marquee-loop", "marquee-play-count", "marquee-speed", "marquee-style", "max-height", "max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index", - "nav-left", "nav-right", "nav-up", "opacity", "order", "orphans", "outline", + "nav-left", "nav-right", "nav-up", "object-fit", "object-position", + "opacity", "order", "orphans", "outline", "outline-color", "outline-offset", "outline-style", "outline-width", "overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y", "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", @@ -428,8 +463,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "region-break-before", "region-break-inside", "region-fragment", "rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness", "right", "rotation", "rotation-point", "ruby-align", "ruby-overhang", - "ruby-position", "ruby-span", "shape-inside", "shape-outside", "size", - "speak", "speak-as", "speak-header", + "ruby-position", "ruby-span", "shape-image-threshold", "shape-inside", "shape-margin", + "shape-outside", "size", "speak", "speak-as", "speak-header", "speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set", "tab-size", "table-layout", "target", "target-name", "target-new", "target-position", "text-align", "text-align-last", "text-decoration", @@ -444,19 +479,37 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "vertical-align", "visibility", "voice-balance", "voice-duration", "voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress", "voice-volume", "volume", "white-space", "widows", "width", "word-break", - "word-spacing", "word-wrap", "z-index", "zoom", + "word-spacing", "word-wrap", "z-index", // SVG-specific "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color", "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events", - "color-interpolation", "color-interpolation-filters", "color-profile", + "color-interpolation", "color-interpolation-filters", "color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering", "marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering", "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal", - "glyph-orientation-vertical", "kerning", "text-anchor", "writing-mode" + "glyph-orientation-vertical", "text-anchor", "writing-mode" ], propertyKeywords = keySet(propertyKeywords_); + var nonStandardPropertyKeywords_ = [ + "scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color", + "scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color", + "scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside", + "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button", + "searchfield-results-decoration", "zoom" + ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_); + + var fontProperties_ = [ + "font-family", "src", "unicode-range", "font-variant", "font-feature-settings", + "font-stretch", "font-weight", "font-style" + ], fontProperties = keySet(fontProperties_); + + var counterDescriptors_ = [ + "additive-symbols", "fallback", "negative", "pad", "prefix", "range", + "speak-as", "suffix", "symbols", "system" + ], counterDescriptors = keySet(counterDescriptors_); + var colorKeywords_ = [ "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", @@ -479,54 +532,57 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", - "purple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", - "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", + "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", + "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen" ], colorKeywords = keySet(colorKeywords_); var valueKeywords_ = [ - "above", "absolute", "activeborder", "activecaption", "afar", - "after-white-space", "ahead", "alias", "all", "all-scroll", "alternate", + "above", "absolute", "activeborder", "additive", "activecaption", "afar", + "after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate", "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", - "arabic-indic", "armenian", "asterisks", "auto", "avoid", "avoid-column", "avoid-page", + "arabic-indic", "armenian", "asterisks", "attr", "auto", "avoid", "avoid-column", "avoid-page", "avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary", "bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box", - "both", "bottom", "break", "break-all", "break-word", "button", "button-bevel", - "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", + "both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel", + "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian", "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", - "cell", "center", "checkbox", "circle", "cjk-earthly-branch", + "cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch", "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", "col-resize", "collapse", "column", "compact", "condensed", "contain", "content", - "content-box", "context-menu", "continuous", "copy", "cover", "crop", - "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", + "content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop", + "cross", "crosshair", "currentcolor", "cursive", "cyclic", "dashed", "decimal", "decimal-leading-zero", "default", "default-button", "destination-atop", "destination-in", "destination-out", "destination-over", "devanagari", - "disc", "discard", "document", "dot-dash", "dot-dot-dash", "dotted", - "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", + "disc", "discard", "disclosure-closed", "disclosure-open", "document", + "dot-dash", "dot-dot-dash", + "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", "element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", "ethiopic-halehame-aa-et", "ethiopic-halehame-am-et", "ethiopic-halehame-gez", "ethiopic-halehame-om-et", "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", - "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", - "ethiopic-halehame-tig", "ew-resize", "expanded", "extra-condensed", - "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes", + "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig", + "ethiopic-numeric", "ew-resize", "expanded", "extends", "extra-condensed", + "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "footnotes", "forwards", "from", "geometricPrecision", "georgian", "graytext", "groove", "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", "help", "hidden", "hide", "higher", "highlight", "highlighttext", "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", - "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", - "italic", "justify", "kannada", "katakana", "katakana-iroha", "keep-all", "khmer", + "inline-block", "inline-flex", "inline-table", "inset", "inside", "intrinsic", "invert", + "italic", "japanese-formal", "japanese-informal", "justify", "kannada", + "katakana", "katakana-iroha", "keep-all", "khmer", + "korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal", "landscape", "lao", "large", "larger", "left", "level", "lighter", - "line-through", "linear", "lines", "list-item", "listbox", "listitem", + "line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian", - "lower-roman", "lowercase", "ltr", "malayalam", "match", + "lower-roman", "lowercase", "ltr", "malayalam", "match", "matrix", "matrix3d", "media-controls-background", "media-current-time-display", "media-fullscreen-button", "media-mute-button", "media-play-button", "media-return-to-realtime-button", "media-rewind-button", @@ -538,45 +594,49 @@ CodeMirror.defineMode("css", function(config, parserConfig) { "mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize", "narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", - "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", + "ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset", "outside", "outside-shape", "overlay", "overline", "padding", "padding-box", - "painted", "page", "paused", "persian", "plus-darker", "plus-lighter", "pointer", - "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", "push-button", - "radio", "read-only", "read-write", "read-write-plaintext-only", "rectangle", "region", - "relative", "repeat", "repeat-x", "repeat-y", "reset", "reverse", "rgb", "rgba", - "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", - "s-resize", "sans-serif", "scroll", "scrollbar", "se-resize", "searchfield", + "painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter", + "pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", + "progress", "push-button", "radial-gradient", "radio", "read-only", + "read-write", "read-write-plaintext-only", "rectangle", "region", + "relative", "repeat", "repeating-linear-gradient", + "repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse", + "rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY", + "rotateZ", "round", "row-resize", "rtl", "run-in", "running", + "s-resize", "sans-serif", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", + "scroll", "scrollbar", "se-resize", "searchfield", "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button", "searchfield-results-decoration", "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", - "single", "skip-white-space", "slide", "slider-horizontal", + "simp-chinese-formal", "simp-chinese-informal", "single", + "skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal", "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", "small", "small-caps", "small-caption", "smaller", "solid", "somali", - "source-atop", "source-in", "source-out", "source-over", "space", "square", - "square-button", "start", "static", "status-bar", "stretch", "stroke", - "sub", "subpixel-antialiased", "super", "sw-resize", "table", + "source-atop", "source-in", "source-out", "source-over", "space", "spell-out", "square", + "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub", + "subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", "table-row", "table-row-group", + "tamil", "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight", "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top", + "trad-chinese-formal", "trad-chinese-informal", + "translate", "translate3d", "translateX", "translateY", "translateZ", "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal", "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", - "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted", + "var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted", "visibleStroke", "visual", "w-resize", "wait", "wave", "wider", - "window", "windowframe", "windowtext", "x-large", "x-small", "xor", + "window", "windowframe", "windowtext", "words", "x-large", "x-small", "xor", "xx-large", "xx-small" ], valueKeywords = keySet(valueKeywords_); - var fontProperties_ = [ - "font-family", "src", "unicode-range", "font-variant", "font-feature-settings", - "font-stretch", "font-weight", "font-style" - ], fontProperties = keySet(fontProperties_); - - var allWords = mediaTypes_.concat(mediaFeatures_).concat(propertyKeywords_).concat(colorKeywords_).concat(valueKeywords_); + var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(propertyKeywords_) + .concat(nonStandardPropertyKeywords_).concat(colorKeywords_).concat(valueKeywords_); CodeMirror.registerHelper("hintWords", "css", allWords); function tokenCComment(stream, state) { @@ -602,12 +662,15 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } CodeMirror.defineMIME("text/css", { + documentTypes: documentTypes, mediaTypes: mediaTypes, mediaFeatures: mediaFeatures, propertyKeywords: propertyKeywords, + nonStandardPropertyKeywords: nonStandardPropertyKeywords, + fontProperties: fontProperties, + counterDescriptors: counterDescriptors, colorKeywords: colorKeywords, valueKeywords: valueKeywords, - fontProperties: fontProperties, tokenHooks: { "<": function(stream, state) { if (!stream.match("!--")) return false; @@ -627,6 +690,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { mediaTypes: mediaTypes, mediaFeatures: mediaFeatures, propertyKeywords: propertyKeywords, + nonStandardPropertyKeywords: nonStandardPropertyKeywords, colorKeywords: colorKeywords, valueKeywords: valueKeywords, fontProperties: fontProperties, @@ -644,7 +708,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { } }, ":": function(stream) { - if (stream.match(/\s*{/)) + if (stream.match(/\s*\{/)) return [null, "{"]; return false; }, @@ -667,6 +731,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) { mediaTypes: mediaTypes, mediaFeatures: mediaFeatures, propertyKeywords: propertyKeywords, + nonStandardPropertyKeywords: nonStandardPropertyKeywords, colorKeywords: colorKeywords, valueKeywords: valueKeywords, fontProperties: fontProperties, diff --git a/applications/admin/static/codemirror/mode/css/less_test.js b/applications/admin/static/codemirror/mode/css/less_test.js new file mode 100644 index 00000000..2ba69984 --- /dev/null +++ b/applications/admin/static/codemirror/mode/css/less_test.js @@ -0,0 +1,51 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + "use strict"; + + var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-less"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "less"); } + + MT("variable", + "[variable-2 @base]: [atom #f04615];", + "[qualifier .class] {", + " [property width]: [variable percentage]([number 0.5]); [comment // returns `50%`]", + " [property color]: [variable saturate]([variable-2 @base], [number 5%]);", + "}"); + + MT("amp", + "[qualifier .child], [qualifier .sibling] {", + " [qualifier .parent] [atom &] {", + " [property color]: [keyword black];", + " }", + " [atom &] + [atom &] {", + " [property color]: [keyword red];", + " }", + "}"); + + MT("mixin", + "[qualifier .mixin] ([variable dark]; [variable-2 @color]) {", + " [property color]: [variable darken]([variable-2 @color], [number 10%]);", + "}", + "[qualifier .mixin] ([variable light]; [variable-2 @color]) {", + " [property color]: [variable lighten]([variable-2 @color], [number 10%]);", + "}", + "[qualifier .mixin] ([variable-2 @_]; [variable-2 @color]) {", + " [property display]: [atom block];", + "}", + "[variable-2 @switch]: [variable light];", + "[qualifier .class] {", + " [qualifier .mixin]([variable-2 @switch]; [atom #888]);", + "}"); + + MT("nest", + "[qualifier .one] {", + " [def @media] ([property width]: [number 400px]) {", + " [property font-size]: [number 1.2em];", + " [def @media] [attribute print] [keyword and] [property color] {", + " [property color]: [keyword blue];", + " }", + " }", + "}"); +})(); diff --git a/applications/admin/static/codemirror/mode/css/scss.html b/applications/admin/static/codemirror/mode/css/scss.html deleted file mode 100644 index 0677d08f..00000000 --- a/applications/admin/static/codemirror/mode/css/scss.html +++ /dev/null @@ -1,157 +0,0 @@ - - -CodeMirror: SCSS mode - - - - - - - - - -
-

SCSS mode

-
- - -

The SCSS mode is a sub-mode of the CSS mode (defined in css.js.

- -

Parsing/Highlighting Tests: normal, verbose.

- -
diff --git a/applications/admin/static/codemirror/mode/css/scss_test.js b/applications/admin/static/codemirror/mode/css/scss_test.js index c51cb42b..8dcea9e8 100644 --- a/applications/admin/static/codemirror/mode/css/scss_test.js +++ b/applications/admin/static/codemirror/mode/css/scss_test.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-scss"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); } diff --git a/applications/admin/static/codemirror/mode/css/test.js b/applications/admin/static/codemirror/mode/css/test.js index b3f47767..bef71561 100644 --- a/applications/admin/static/codemirror/mode/css/test.js +++ b/applications/admin/static/codemirror/mode/css/test.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function() { var mode = CodeMirror.getMode({indentUnit: 2}, "css"); function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } @@ -116,4 +119,77 @@ " [property src]: [atom url]([string http://blah]),", " [atom url]([string http://foo]);", "}"); + + MT("empty_url", + "[def @import] [tag url]() [tag screen];"); + + MT("parens", + "[qualifier .foo] {", + " [property background-image]: [variable fade]([atom #000], [number 20%]);", + " [property border-image]: [atom linear-gradient](", + " [atom to] [atom bottom],", + " [variable fade]([atom #000], [number 20%]) [number 0%],", + " [variable fade]([atom #000], [number 20%]) [number 100%]", + " );", + "}"); + + MT("css_variable", + ":[variable-3 root] {", + " [variable-2 --main-color]: [atom #06c];", + "}", + "[tag h1][builtin #foo] {", + " [property color]: [atom var]([variable-2 --main-color]);", + "}"); + + MT("supports", + "[def @supports] ([keyword not] (([property text-align-last]: [atom justify]) [keyword or] ([meta -moz-][property text-align-last]: [atom justify])) {", + " [property text-align-last]: [atom justify];", + "}"); + + MT("document", + "[def @document] [tag url]([string http://blah]),", + " [tag url-prefix]([string https://]),", + " [tag domain]([string blah.com]),", + " [tag regexp]([string \".*blah.+\"]) {", + " [builtin #id] {", + " [property background-color]: [keyword white];", + " }", + " [tag foo] {", + " [property font-family]: [variable Verdana], [atom sans-serif];", + " }", + " }"); + + MT("document_url", + "[def @document] [tag url]([string http://blah]) { [qualifier .class] { } }"); + + MT("document_urlPrefix", + "[def @document] [tag url-prefix]([string https://]) { [builtin #id] { } }"); + + MT("document_domain", + "[def @document] [tag domain]([string blah.com]) { [tag foo] { } }"); + + MT("document_regexp", + "[def @document] [tag regexp]([string \".*blah.+\"]) { [builtin #id] { } }"); + + MT("counter-style", + "[def @counter-style] [variable binary] {", + " [property system]: [atom numeric];", + " [property symbols]: [number 0] [number 1];", + " [property suffix]: [string \".\"];", + " [property range]: [atom infinite];", + " [property speak-as]: [atom numeric];", + "}"); + + MT("counter-style-additive-symbols", + "[def @counter-style] [variable simple-roman] {", + " [property system]: [atom additive];", + " [property additive-symbols]: [number 10] [variable X], [number 5] [variable V], [number 1] [variable I];", + " [property range]: [number 1] [number 49];", + "}"); + + MT("counter-style-use", + "[tag ol][qualifier .roman] { [property list-style]: [variable simple-roman]; }"); + + MT("counter-style-symbols", + "[tag ol] { [property list-style]: [atom symbols]([atom cyclic] [string \"*\"] [string \"\\2020\"] [string \"\\2021\"] [string \"\\A7\"]); }"); })(); diff --git a/applications/admin/static/codemirror/mode/haml/haml.js b/applications/admin/static/codemirror/mode/haml/haml.js deleted file mode 100644 index 793308f6..00000000 --- a/applications/admin/static/codemirror/mode/haml/haml.js +++ /dev/null @@ -1,153 +0,0 @@ -(function() { - "use strict"; - - // full haml mode. This handled embeded ruby and html fragments too - CodeMirror.defineMode("haml", function(config) { - var htmlMode = CodeMirror.getMode(config, {name: "htmlmixed"}); - var rubyMode = CodeMirror.getMode(config, "ruby"); - - function rubyInQuote(endQuote) { - return function(stream, state) { - var ch = stream.peek(); - if (ch == endQuote && state.rubyState.tokenize.length == 1) { - // step out of ruby context as it seems to complete processing all the braces - stream.next(); - state.tokenize = html; - return "closeAttributeTag"; - } else { - return ruby(stream, state); - } - }; - } - - function ruby(stream, state) { - if (stream.match("-#")) { - stream.skipToEnd(); - return "comment"; - } - return rubyMode.token(stream, state.rubyState); - } - - function html(stream, state) { - var ch = stream.peek(); - - // handle haml declarations. All declarations that cant be handled here - // will be passed to html mode - if (state.previousToken.style == "comment" ) { - if (state.indented > state.previousToken.indented) { - stream.skipToEnd(); - return "commentLine"; - } - } - - if (state.startOfLine) { - if (ch == "!" && stream.match("!!")) { - stream.skipToEnd(); - return "tag"; - } else if (stream.match(/^%[\w:#\.]+=/)) { - state.tokenize = ruby; - return "hamlTag"; - } else if (stream.match(/^%[\w:]+/)) { - return "hamlTag"; - } else if (ch == "/" ) { - stream.skipToEnd(); - return "comment"; - } - } - - if (state.startOfLine || state.previousToken.style == "hamlTag") { - if ( ch == "#" || ch == ".") { - stream.match(/[\w-#\.]*/); - return "hamlAttribute"; - } - } - - // donot handle --> as valid ruby, make it HTML close comment instead - if (state.startOfLine && !stream.match("-->", false) && (ch == "=" || ch == "-" )) { - state.tokenize = ruby; - return null; - } - - if (state.previousToken.style == "hamlTag" || - state.previousToken.style == "closeAttributeTag" || - state.previousToken.style == "hamlAttribute") { - if (ch == "(") { - state.tokenize = rubyInQuote(")"); - return null; - } else if (ch == "{") { - state.tokenize = rubyInQuote("}"); - return null; - } - } - - return htmlMode.token(stream, state.htmlState); - } - - return { - // default to html mode - startState: function() { - var htmlState = htmlMode.startState(); - var rubyState = rubyMode.startState(); - return { - htmlState: htmlState, - rubyState: rubyState, - indented: 0, - previousToken: { style: null, indented: 0}, - tokenize: html - }; - }, - - copyState: function(state) { - return { - htmlState : CodeMirror.copyState(htmlMode, state.htmlState), - rubyState: CodeMirror.copyState(rubyMode, state.rubyState), - indented: state.indented, - previousToken: state.previousToken, - tokenize: state.tokenize - }; - }, - - token: function(stream, state) { - if (stream.sol()) { - state.indented = stream.indentation(); - state.startOfLine = true; - } - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - state.startOfLine = false; - // dont record comment line as we only want to measure comment line with - // the opening comment block - if (style && style != "commentLine") { - state.previousToken = { style: style, indented: state.indented }; - } - // if current state is ruby and the previous token is not `,` reset the - // tokenize to html - if (stream.eol() && state.tokenize == ruby) { - stream.backUp(1); - var ch = stream.peek(); - stream.next(); - if (ch && ch != ",") { - state.tokenize = html; - } - } - // reprocess some of the specific style tag when finish setting previousToken - if (style == "hamlTag") { - style = "tag"; - } else if (style == "commentLine") { - style = "comment"; - } else if (style == "hamlAttribute") { - style = "attribute"; - } else if (style == "closeAttributeTag") { - style = null; - } - return style; - }, - - indent: function(state) { - return state.indented; - } - }; - }, "htmlmixed", "ruby"); - - CodeMirror.defineMIME("text/x-haml", "haml"); -})(); diff --git a/applications/admin/static/codemirror/mode/haml/index.html b/applications/admin/static/codemirror/mode/haml/index.html deleted file mode 100644 index a7378755..00000000 --- a/applications/admin/static/codemirror/mode/haml/index.html +++ /dev/null @@ -1,79 +0,0 @@ - - -CodeMirror: HAML mode - - - - - - - - - - - - - -
-

HAML mode

-
- - -

MIME types defined: text/x-haml.

- -

Parsing/Highlighting Tests: normal, verbose.

- -
diff --git a/applications/admin/static/codemirror/mode/haml/test.js b/applications/admin/static/codemirror/mode/haml/test.js deleted file mode 100644 index b7178d40..00000000 --- a/applications/admin/static/codemirror/mode/haml/test.js +++ /dev/null @@ -1,94 +0,0 @@ -(function() { - var mode = CodeMirror.getMode({tabSize: 4}, "haml"); - function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } - - // Requires at least one media query - MT("elementName", - "[tag %h1] Hey There"); - - MT("oneElementPerLine", - "[tag %h1] Hey There %h2"); - - MT("idSelector", - "[tag %h1][attribute #test] Hey There"); - - MT("classSelector", - "[tag %h1][attribute .hello] Hey There"); - - MT("docType", - "[tag !!! XML]"); - - MT("comment", - "[comment / Hello WORLD]"); - - MT("notComment", - "[tag %h1] This is not a / comment "); - - MT("attributes", - "[tag %a]([variable title][operator =][string \"test\"]){[atom :title] [operator =>] [string \"test\"]}"); - - MT("htmlCode", - "[tag

]Title[tag

]"); - - MT("rubyBlock", - "[operator =][variable-2 @item]"); - - MT("selectorRubyBlock", - "[tag %a.selector=] [variable-2 @item]"); - - MT("nestedRubyBlock", - "[tag %a]", - " [operator =][variable puts] [string \"test\"]"); - - MT("multilinePlaintext", - "[tag %p]", - " Hello,", - " World"); - - MT("multilineRuby", - "[tag %p]", - " [comment -# this is a comment]", - " [comment and this is a comment too]", - " Date/Time", - " [operator -] [variable now] [operator =] [tag DateTime][operator .][variable now]", - " [tag %strong=] [variable now]", - " [operator -] [keyword if] [variable now] [operator >] [tag DateTime][operator .][variable parse]([string \"December 31, 2006\"])", - " [operator =][string \"Happy\"]", - " [operator =][string \"Belated\"]", - " [operator =][string \"Birthday\"]"); - - MT("multilineComment", - "[comment /]", - " [comment Multiline]", - " [comment Comment]"); - - MT("hamlComment", - "[comment -# this is a comment]"); - - MT("multilineHamlComment", - "[comment -# this is a comment]", - " [comment and this is a comment too]"); - - MT("multilineHTMLComment", - "[comment ]"); - - MT("hamlAfterRubyTag", - "[attribute .block]", - " [tag %strong=] [variable now]", - " [attribute .test]", - " [operator =][variable now]", - " [attribute .right]"); - - MT("stretchedRuby", - "[operator =] [variable puts] [string \"Hello\"],", - " [string \"World\"]"); - - MT("interpolationInHashAttribute", - //"[tag %div]{[atom :id] [operator =>] [string \"#{][variable test][string }_#{][variable ting][string }\"]} test"); - "[tag %div]{[atom :id] [operator =>] [string \"#{][variable test][string }_#{][variable ting][string }\"]} test"); - - MT("interpolationInHTMLAttribute", - "[tag %div]([variable title][operator =][string \"#{][variable test][string }_#{][variable ting]()[string }\"]) Test"); -})(); diff --git a/applications/admin/static/codemirror/mode/htmlembedded/htmlembedded.js b/applications/admin/static/codemirror/mode/htmlembedded/htmlembedded.js deleted file mode 100644 index 3a07c343..00000000 --- a/applications/admin/static/codemirror/mode/htmlembedded/htmlembedded.js +++ /dev/null @@ -1,83 +0,0 @@ -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror"), require("../htmlmixed/htmlmixed")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror", "../htmlmixed/htmlmixed"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { -"use strict"; - -CodeMirror.defineMode("htmlembedded", function(config, parserConfig) { - - //config settings - var scriptStartRegex = parserConfig.scriptStartRegex || /^<%/i, - scriptEndRegex = parserConfig.scriptEndRegex || /^%>/i; - - //inner modes - var scriptingMode, htmlMixedMode; - - //tokenizer when in html mode - function htmlDispatch(stream, state) { - if (stream.match(scriptStartRegex, false)) { - state.token=scriptingDispatch; - return scriptingMode.token(stream, state.scriptState); - } - else - return htmlMixedMode.token(stream, state.htmlState); - } - - //tokenizer when in scripting mode - function scriptingDispatch(stream, state) { - if (stream.match(scriptEndRegex, false)) { - state.token=htmlDispatch; - return htmlMixedMode.token(stream, state.htmlState); - } - else - return scriptingMode.token(stream, state.scriptState); - } - - - return { - startState: function() { - scriptingMode = scriptingMode || CodeMirror.getMode(config, parserConfig.scriptingModeSpec); - htmlMixedMode = htmlMixedMode || CodeMirror.getMode(config, "htmlmixed"); - return { - token : parserConfig.startOpen ? scriptingDispatch : htmlDispatch, - htmlState : CodeMirror.startState(htmlMixedMode), - scriptState : CodeMirror.startState(scriptingMode) - }; - }, - - token: function(stream, state) { - return state.token(stream, state); - }, - - indent: function(state, textAfter) { - if (state.token == htmlDispatch) - return htmlMixedMode.indent(state.htmlState, textAfter); - else if (scriptingMode.indent) - return scriptingMode.indent(state.scriptState, textAfter); - }, - - copyState: function(state) { - return { - token : state.token, - htmlState : CodeMirror.copyState(htmlMixedMode, state.htmlState), - scriptState : CodeMirror.copyState(scriptingMode, state.scriptState) - }; - }, - - innerMode: function(state) { - if (state.token == scriptingDispatch) return {state: state.scriptState, mode: scriptingMode}; - else return {state: state.htmlState, mode: htmlMixedMode}; - } - }; -}, "htmlmixed"); - -CodeMirror.defineMIME("application/x-ejs", { name: "htmlembedded", scriptingModeSpec:"javascript"}); -CodeMirror.defineMIME("application/x-aspx", { name: "htmlembedded", scriptingModeSpec:"text/x-csharp"}); -CodeMirror.defineMIME("application/x-jsp", { name: "htmlembedded", scriptingModeSpec:"text/x-java"}); -CodeMirror.defineMIME("application/x-erb", { name: "htmlembedded", scriptingModeSpec:"ruby"}); - -}); diff --git a/applications/admin/static/codemirror/mode/htmlmixed/htmlmixed.js b/applications/admin/static/codemirror/mode/htmlmixed/htmlmixed.js index d80ef9c6..1cc438f0 100644 --- a/applications/admin/static/codemirror/mode/htmlmixed/htmlmixed.js +++ b/applications/admin/static/codemirror/mode/htmlmixed/htmlmixed.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css")); @@ -27,6 +30,7 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { function html(stream, state) { var tagName = state.htmlState.tagName; + if (tagName) tagName = tagName.toLowerCase(); var style = htmlMode.token(stream, state.htmlState); if (tagName == "script" && /\btag\b/.test(style) && stream.current() == ">") { // Script block: mode to change to depends on type attribute @@ -65,7 +69,7 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { if (stream.match(/^<\/\s*script\s*>/i, false)) { state.token = html; state.localState = state.localMode = null; - return html(stream, state); + return null; } return maybeBackup(stream, /<\/\s*script\s*>/, state.localMode.token(stream, state.localState)); @@ -74,7 +78,7 @@ CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { if (stream.match(/^<\/\s*style\s*>/i, false)) { state.token = html; state.localState = state.localMode = null; - return html(stream, state); + return null; } return maybeBackup(stream, /<\/\s*style\s*>/, cssMode.token(stream, state.localState)); diff --git a/applications/admin/static/codemirror/mode/http/http.js b/applications/admin/static/codemirror/mode/http/http.js deleted file mode 100644 index 5a516360..00000000 --- a/applications/admin/static/codemirror/mode/http/http.js +++ /dev/null @@ -1,98 +0,0 @@ -CodeMirror.defineMode("http", function() { - function failFirstLine(stream, state) { - stream.skipToEnd(); - state.cur = header; - return "error"; - } - - function start(stream, state) { - if (stream.match(/^HTTP\/\d\.\d/)) { - state.cur = responseStatusCode; - return "keyword"; - } else if (stream.match(/^[A-Z]+/) && /[ \t]/.test(stream.peek())) { - state.cur = requestPath; - return "keyword"; - } else { - return failFirstLine(stream, state); - } - } - - function responseStatusCode(stream, state) { - var code = stream.match(/^\d+/); - if (!code) return failFirstLine(stream, state); - - state.cur = responseStatusText; - var status = Number(code[0]); - if (status >= 100 && status < 200) { - return "positive informational"; - } else if (status >= 200 && status < 300) { - return "positive success"; - } else if (status >= 300 && status < 400) { - return "positive redirect"; - } else if (status >= 400 && status < 500) { - return "negative client-error"; - } else if (status >= 500 && status < 600) { - return "negative server-error"; - } else { - return "error"; - } - } - - function responseStatusText(stream, state) { - stream.skipToEnd(); - state.cur = header; - return null; - } - - function requestPath(stream, state) { - stream.eatWhile(/\S/); - state.cur = requestProtocol; - return "string-2"; - } - - function requestProtocol(stream, state) { - if (stream.match(/^HTTP\/\d\.\d$/)) { - state.cur = header; - return "keyword"; - } else { - return failFirstLine(stream, state); - } - } - - function header(stream) { - if (stream.sol() && !stream.eat(/[ \t]/)) { - if (stream.match(/^.*?:/)) { - return "atom"; - } else { - stream.skipToEnd(); - return "error"; - } - } else { - stream.skipToEnd(); - return "string"; - } - } - - function body(stream) { - stream.skipToEnd(); - return null; - } - - return { - token: function(stream, state) { - var cur = state.cur; - if (cur != header && cur != body && stream.eatSpace()) return null; - return cur(stream, state); - }, - - blankLine: function(state) { - state.cur = body; - }, - - startState: function() { - return {cur: start}; - } - }; -}); - -CodeMirror.defineMIME("message/http", "http"); diff --git a/applications/admin/static/codemirror/mode/http/index.html b/applications/admin/static/codemirror/mode/http/index.html deleted file mode 100644 index 705085e2..00000000 --- a/applications/admin/static/codemirror/mode/http/index.html +++ /dev/null @@ -1,45 +0,0 @@ - - -CodeMirror: HTTP mode - - - - - - - - - -
-

HTTP mode

- - -
- - - -

MIME types defined: message/http.

-
diff --git a/applications/admin/static/codemirror/mode/javascript/javascript.js b/applications/admin/static/codemirror/mode/javascript/javascript.js index 15eccf7e..3f05ac46 100644 --- a/applications/admin/static/codemirror/mode/javascript/javascript.js +++ b/applications/admin/static/codemirror/mode/javascript/javascript.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + // TODO actually recognize syntax of TypeScript constructs (function(mod) { @@ -16,6 +19,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var jsonldMode = parserConfig.jsonld; var jsonMode = parserConfig.json || jsonldMode; var isTS = parserConfig.typescript; + var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; // Tokenizer @@ -114,7 +118,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } else if (state.lastType == "operator" || state.lastType == "keyword c" || state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) { readRegexp(stream); - stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla + stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); return ret("regexp", "string-2"); } else { stream.eatWhile(isOperatorChar); @@ -129,8 +133,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } else if (isOperatorChar.test(ch)) { stream.eatWhile(isOperatorChar); return ret("operator", "operator", stream.current()); - } else { - stream.eatWhile(/[\w\$_]/); + } else if (wordRE.test(ch)) { + stream.eatWhile(wordRE); var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; return (known && state.lastType != ".") ? ret(known.type, known.style, word) : ret("variable", "variable", word); @@ -199,8 +203,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (--depth == 0) break; } else if (bracket >= 3 && bracket < 6) { ++depth; - } else if (/[$\w]/.test(ch)) { + } else if (wordRE.test(ch)) { sawSomething = true; + } else if (/["'\/]/.test(ch)) { + return; } else if (sawSomething && !depth) { ++pos; break; @@ -235,7 +241,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var cc = state.cc; // Communicate our context to the combinators. // (Less wasteful than consing up a hundred closures on every call.) - cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; + cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style; if (!state.lexical.hasOwnProperty("align")) state.lexical.align = true; @@ -295,6 +301,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var result = function() { var state = cx.state, indent = state.indented; if (state.lexical.type == "stat") indent = state.lexical.indented; + else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev) + indent = outer.indented; state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info); }; result.lex = true; @@ -325,7 +333,11 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "keyword b") return cont(pushlex("form"), statement, poplex); if (type == "{") return cont(pushlex("}"), block, poplex); if (type == ";") return cont(); - if (type == "if") return cont(pushlex("form"), expression, statement, poplex, maybeelse); + if (type == "if") { + if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) + cx.state.cc.pop()(); + return cont(pushlex("form"), expression, statement, poplex, maybeelse); + } if (type == "function") return cont(functiondef); if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); if (type == "variable") return cont(pushlex("stat"), maybelabel); @@ -336,7 +348,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext); if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex); - if (type == "class") return cont(pushlex("form"), className, objlit, poplex); + if (type == "class") return cont(pushlex("form"), className, poplex); if (type == "export") return cont(pushlex("form"), afterExport, poplex); if (type == "import") return cont(pushlex("form"), afterImport, poplex); return pass(pushlex("stat"), expression, expect(";"), poplex); @@ -356,12 +368,13 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); - if (type == "function") return cont(functiondef); + if (type == "function") return cont(functiondef, maybeop); if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression); if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop); if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); if (type == "{") return contCommasep(objprop, "}", null, maybeop); + if (type == "quasi") { return pass(quasi, maybeop); } return cont(); } function maybeexpression(type) { @@ -380,38 +393,37 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function maybeoperatorNoComma(type, value, noComma) { var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma; var expr = noComma == false ? expression : expressionNoComma; - if (value == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); + if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); if (type == "operator") { if (/\+\+|--/.test(value)) return cont(me); if (value == "?") return cont(expression, expect(":"), expr); return cont(expr); } - if (type == "quasi") { cx.cc.push(me); return quasi(value); } + if (type == "quasi") { return pass(quasi, me); } if (type == ";") return; if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); if (type == ".") return cont(property, me); if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); } - function quasi(value) { - if (value.slice(value.length - 2) != "${") return cont(); + function quasi(type, value) { + if (type != "quasi") return pass(); + if (value.slice(value.length - 2) != "${") return cont(quasi); return cont(expression, continueQuasi); } function continueQuasi(type) { if (type == "}") { cx.marked = "string-2"; cx.state.tokenize = tokenQuasi; - return cont(); + return cont(quasi); } } function arrowBody(type) { findFatArrow(cx.stream, cx.state); - if (type == "{") return pass(statement); - return pass(expression); + return pass(type == "{" ? statement : expression); } function arrowBodyNoComma(type) { findFatArrow(cx.stream, cx.state); - if (type == "{") return pass(statement); - return pass(expressionNoComma); + return pass(type == "{" ? statement : expressionNoComma); } function maybelabel(type) { if (type == ":") return cont(poplex, statement); @@ -421,15 +433,18 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "variable") {cx.marked = "property"; return cont();} } function objprop(type, value) { - if (type == "variable") { + if (type == "variable" || cx.style == "keyword") { cx.marked = "property"; if (value == "get" || value == "set") return cont(getterSetter); + return cont(afterprop); } else if (type == "number" || type == "string") { - cx.marked = jsonldMode ? "property" : (type + " property"); + cx.marked = jsonldMode ? "property" : (cx.style + " property"); + return cont(afterprop); + } else if (type == "jsonld-keyword") { + return cont(afterprop); } else if (type == "[") { return cont(expression, expect("]"), afterprop); } - if (atomicTypes.hasOwnProperty(type)) return cont(afterprop); } function getterSetter(type) { if (type != "variable") return pass(afterprop); @@ -493,7 +508,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == ",") return cont(vardef); } function maybeelse(type, value) { - if (type == "keyword b" && value == "else") return cont(pushlex("form"), statement, poplex); + if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); } function forspec(type) { if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); @@ -528,11 +543,27 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { function className(type, value) { if (type == "variable") {register(value); return cont(classNameAfter);} } - function classNameAfter(_type, value) { - if (value == "extends") return cont(expression); + function classNameAfter(type, value) { + if (value == "extends") return cont(expression, classNameAfter); + if (type == "{") return cont(pushlex("}"), classBody, poplex); } - function objlit(type) { - if (type == "{") return contCommasep(objprop, "}"); + function classBody(type, value) { + if (type == "variable" || cx.style == "keyword") { + cx.marked = "property"; + if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody); + return cont(functiondef, classBody); + } + if (value == "*") { + cx.marked = "keyword"; + return cont(classBody); + } + if (type == ";") return cont(classBody); + if (type == "}") return cont(); + } + function classGetterSetter(type) { + if (type != "variable") return pass(); + cx.marked = "property"; + return cont(); } function afterModule(type, value) { if (type == "string") return cont(statement); @@ -561,7 +592,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { } function maybeArrayComprehension(type) { if (type == "for") return pass(comprehension, expect("]")); - if (type == ",") return cont(commasep(expressionNoComma, "]")); + if (type == ",") return cont(commasep(maybeexpressionNoComma, "]")); return pass(commasep(expressionNoComma, "]")); } function comprehension(type) { @@ -569,6 +600,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (type == "if") return cont(expression, comprehension); } + function isContinuedStatement(state, textAfter) { + return state.lastType == "operator" || state.lastType == "," || + isOperatorChar.test(textAfter.charAt(0)) || + /[,.]/.test(textAfter.charAt(0)); + } + // Interface return { @@ -606,7 +643,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { if (state.tokenize != tokenBase) return 0; var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; // Kludge to prevent 'maybelse' from blocking lexical scope pops - for (var i = state.cc.length - 1; i >= 0; --i) { + if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { var c = state.cc[i]; if (c == poplex) lexical = lexical.prev; else if (c != maybeelse) break; @@ -620,14 +657,14 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { else if (type == "form" && firstChar == "{") return lexical.indented; else if (type == "form") return lexical.indented + indentUnit; else if (type == "stat") - return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? statementIndent || indentUnit : 0); + return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0); else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false) return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); else if (lexical.align) return lexical.column + (closing ? 0 : 1); else return lexical.indented + (closing ? 0 : indentUnit); }, - electricChars: ":{}", + electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, blockCommentStart: jsonMode ? null : "/*", blockCommentEnd: jsonMode ? null : "*/", lineComment: jsonMode ? null : "//", @@ -639,9 +676,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) { }; }); +CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/); + CodeMirror.defineMIME("text/javascript", "javascript"); CodeMirror.defineMIME("text/ecmascript", "javascript"); CodeMirror.defineMIME("application/javascript", "javascript"); +CodeMirror.defineMIME("application/x-javascript", "javascript"); CodeMirror.defineMIME("application/ecmascript", "javascript"); CodeMirror.defineMIME("application/json", {name: "javascript", json: true}); CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true}); diff --git a/applications/admin/static/codemirror/mode/javascript/test.js b/applications/admin/static/codemirror/mode/javascript/test.js new file mode 100644 index 00000000..91b0e89a --- /dev/null +++ b/applications/admin/static/codemirror/mode/javascript/test.js @@ -0,0 +1,200 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var mode = CodeMirror.getMode({indentUnit: 2}, "javascript"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + + MT("locals", + "[keyword function] [variable foo]([def a], [def b]) { [keyword var] [def c] [operator =] [number 10]; [keyword return] [variable-2 a] [operator +] [variable-2 c] [operator +] [variable d]; }"); + + MT("comma-and-binop", + "[keyword function](){ [keyword var] [def x] [operator =] [number 1] [operator +] [number 2], [def y]; }"); + + MT("destructuring", + "([keyword function]([def a], [[[def b], [def c] ]]) {", + " [keyword let] {[def d], [property foo]: [def c][operator =][number 10], [def x]} [operator =] [variable foo]([variable-2 a]);", + " [[[variable-2 c], [variable y] ]] [operator =] [variable-2 c];", + "})();"); + + MT("class_body", + "[keyword class] [variable Foo] {", + " [property constructor]() {}", + " [property sayName]() {", + " [keyword return] [string-2 `foo${][variable foo][string-2 }oo`];", + " }", + "}"); + + MT("class", + "[keyword class] [variable Point] [keyword extends] [variable SuperThing] {", + " [property get] [property prop]() { [keyword return] [number 24]; }", + " [property constructor]([def x], [def y]) {", + " [keyword super]([string 'something']);", + " [keyword this].[property x] [operator =] [variable-2 x];", + " }", + "}"); + + MT("module", + "[keyword module] [string 'foo'] {", + " [keyword export] [keyword let] [def x] [operator =] [number 42];", + " [keyword export] [keyword *] [keyword from] [string 'somewhere'];", + "}"); + + MT("import", + "[keyword function] [variable foo]() {", + " [keyword import] [def $] [keyword from] [string 'jquery'];", + " [keyword module] [def crypto] [keyword from] [string 'crypto'];", + " [keyword import] { [def encrypt], [def decrypt] } [keyword from] [string 'crypto'];", + "}"); + + MT("const", + "[keyword function] [variable f]() {", + " [keyword const] [[ [def a], [def b] ]] [operator =] [[ [number 1], [number 2] ]];", + "}"); + + MT("for/of", + "[keyword for]([keyword let] [variable of] [keyword of] [variable something]) {}"); + + MT("generator", + "[keyword function*] [variable repeat]([def n]) {", + " [keyword for]([keyword var] [def i] [operator =] [number 0]; [variable-2 i] [operator <] [variable-2 n]; [operator ++][variable-2 i])", + " [keyword yield] [variable-2 i];", + "}"); + + MT("quotedStringAddition", + "[keyword let] [variable f] [operator =] [variable a] [operator +] [string 'fatarrow'] [operator +] [variable c];"); + + MT("quotedFatArrow", + "[keyword let] [variable f] [operator =] [variable a] [operator +] [string '=>'] [operator +] [variable c];"); + + MT("fatArrow", + "[variable array].[property filter]([def a] [operator =>] [variable-2 a] [operator +] [number 1]);", + "[variable a];", // No longer in scope + "[keyword let] [variable f] [operator =] ([[ [def a], [def b] ]], [def c]) [operator =>] [variable-2 a] [operator +] [variable-2 c];", + "[variable c];"); + + MT("spread", + "[keyword function] [variable f]([def a], [meta ...][def b]) {", + " [variable something]([variable-2 a], [meta ...][variable-2 b]);", + "}"); + + MT("comprehension", + "[keyword function] [variable f]() {", + " [[([variable x] [operator +] [number 1]) [keyword for] ([keyword var] [def x] [keyword in] [variable y]) [keyword if] [variable pred]([variable-2 x]) ]];", + " ([variable u] [keyword for] ([keyword var] [def u] [keyword of] [variable generateValues]()) [keyword if] ([variable-2 u].[property color] [operator ===] [string 'blue']));", + "}"); + + MT("quasi", + "[variable re][string-2 `fofdlakj${][variable x] [operator +] ([variable re][string-2 `foo`]) [operator +] [number 1][string-2 }fdsa`] [operator +] [number 2]"); + + MT("quasi_no_function", + "[variable x] [operator =] [string-2 `fofdlakj${][variable x] [operator +] [string-2 `foo`] [operator +] [number 1][string-2 }fdsa`] [operator +] [number 2]"); + + MT("indent_statement", + "[keyword var] [variable x] [operator =] [number 10]", + "[variable x] [operator +=] [variable y] [operator +]", + " [atom Infinity]", + "[keyword debugger];"); + + MT("indent_if", + "[keyword if] ([number 1])", + " [keyword break];", + "[keyword else] [keyword if] ([number 2])", + " [keyword continue];", + "[keyword else]", + " [number 10];", + "[keyword if] ([number 1]) {", + " [keyword break];", + "} [keyword else] [keyword if] ([number 2]) {", + " [keyword continue];", + "} [keyword else] {", + " [number 10];", + "}"); + + MT("indent_for", + "[keyword for] ([keyword var] [variable i] [operator =] [number 0];", + " [variable i] [operator <] [number 100];", + " [variable i][operator ++])", + " [variable doSomething]([variable i]);", + "[keyword debugger];"); + + MT("indent_c_style", + "[keyword function] [variable foo]()", + "{", + " [keyword debugger];", + "}"); + + MT("indent_else", + "[keyword for] (;;)", + " [keyword if] ([variable foo])", + " [keyword if] ([variable bar])", + " [number 1];", + " [keyword else]", + " [number 2];", + " [keyword else]", + " [number 3];"); + + MT("indent_funarg", + "[variable foo]([number 10000],", + " [keyword function]([def a]) {", + " [keyword debugger];", + "};"); + + MT("indent_below_if", + "[keyword for] (;;)", + " [keyword if] ([variable foo])", + " [number 1];", + "[number 2];"); + + MT("multilinestring", + "[keyword var] [variable x] [operator =] [string 'foo\\]", + "[string bar'];"); + + MT("scary_regexp", + "[string-2 /foo[[/]]bar/];"); + + MT("indent_strange_array", + "[keyword var] [variable x] [operator =] [[", + " [number 1],,", + " [number 2],", + "]];", + "[number 10];"); + + var jsonld_mode = CodeMirror.getMode( + {indentUnit: 2}, + {name: "javascript", jsonld: true} + ); + function LD(name) { + test.mode(name, jsonld_mode, Array.prototype.slice.call(arguments, 1)); + } + + LD("json_ld_keywords", + '{', + ' [meta "@context"]: {', + ' [meta "@base"]: [string "http://example.com"],', + ' [meta "@vocab"]: [string "http://xmlns.com/foaf/0.1/"],', + ' [property "likesFlavor"]: {', + ' [meta "@container"]: [meta "@list"]', + ' [meta "@reverse"]: [string "@beFavoriteOf"]', + ' },', + ' [property "nick"]: { [meta "@container"]: [meta "@set"] },', + ' [property "nick"]: { [meta "@container"]: [meta "@index"] }', + ' },', + ' [meta "@graph"]: [[ {', + ' [meta "@id"]: [string "http://dbpedia.org/resource/John_Lennon"],', + ' [property "name"]: [string "John Lennon"],', + ' [property "modified"]: {', + ' [meta "@value"]: [string "2010-05-29T14:17:39+02:00"],', + ' [meta "@type"]: [string "http://www.w3.org/2001/XMLSchema#dateTime"]', + ' }', + ' } ]]', + '}'); + + LD("json_ld_fake", + '{', + ' [property "@fake"]: [string "@fake"],', + ' [property "@contextual"]: [string "@identifier"],', + ' [property "user@domain.com"]: [string "@graphical"],', + ' [property "@ID"]: [string "@@ID"]', + '}'); +})(); diff --git a/applications/admin/static/codemirror/mode/javascript/typescript.html b/applications/admin/static/codemirror/mode/javascript/typescript.html deleted file mode 100644 index 9cc5f493..00000000 --- a/applications/admin/static/codemirror/mode/javascript/typescript.html +++ /dev/null @@ -1,61 +0,0 @@ - - -CodeMirror: TypeScript mode - - - - - - - - - -
-

TypeScript mode

- - -
- - - -

This is a specialization of the JavaScript mode.

-
diff --git a/applications/admin/static/codemirror/mode/jinja2/index.html b/applications/admin/static/codemirror/mode/jinja2/index.html deleted file mode 100644 index 66bf2ec6..00000000 --- a/applications/admin/static/codemirror/mode/jinja2/index.html +++ /dev/null @@ -1,50 +0,0 @@ - - -CodeMirror: Jinja2 mode - - - - - - - - - -
-

Jinja2 mode

-
- -
diff --git a/applications/admin/static/codemirror/mode/jinja2/jinja2.js b/applications/admin/static/codemirror/mode/jinja2/jinja2.js deleted file mode 100644 index 16b06c48..00000000 --- a/applications/admin/static/codemirror/mode/jinja2/jinja2.js +++ /dev/null @@ -1,42 +0,0 @@ -CodeMirror.defineMode("jinja2", function() { - var keywords = ["block", "endblock", "for", "endfor", "in", "true", "false", - "loop", "none", "self", "super", "if", "as", "not", "and", - "else", "import", "with", "without", "context"]; - keywords = new RegExp("^((" + keywords.join(")|(") + "))\\b"); - - function tokenBase (stream, state) { - var ch = stream.next(); - if (ch == "{") { - if (ch = stream.eat(/\{|%|#/)) { - stream.eat("-"); - state.tokenize = inTag(ch); - return "tag"; - } - } - } - function inTag (close) { - if (close == "{") { - close = "}"; - } - return function (stream, state) { - var ch = stream.next(); - if ((ch == close || (ch == "-" && stream.eat(close))) - && stream.eat("}")) { - state.tokenize = tokenBase; - return "tag"; - } - if (stream.match(keywords)) { - return "keyword"; - } - return close == "#" ? "comment" : "string"; - }; - } - return { - startState: function () { - return {tokenize: tokenBase}; - }, - token: function (stream, state) { - return state.tokenize(stream, state); - } - }; -}); diff --git a/applications/admin/static/codemirror/mode/less/index.html b/applications/admin/static/codemirror/mode/less/index.html deleted file mode 100644 index 7239143c..00000000 --- a/applications/admin/static/codemirror/mode/less/index.html +++ /dev/null @@ -1,753 +0,0 @@ - - -CodeMirror: LESS mode - - - - - - - - - - - -
-

LESS mode

-
- - -

MIME types defined: text/x-less, text/css (if not previously defined).

-
diff --git a/applications/admin/static/codemirror/mode/less/less.js b/applications/admin/static/codemirror/mode/less/less.js deleted file mode 100644 index 8384b3cd..00000000 --- a/applications/admin/static/codemirror/mode/less/less.js +++ /dev/null @@ -1,256 +0,0 @@ -/* - LESS mode - http://www.lesscss.org/ - Ported to CodeMirror by Peter Kroon - Report bugs/issues here: https://github.com/marijnh/CodeMirror/issues - GitHub: @peterkroon -*/ - -CodeMirror.defineMode("less", function(config) { - var indentUnit = config.indentUnit, type; - function ret(style, tp) {type = tp; return style;} - - var selectors = /(^\:root$|^\:nth\-child$|^\:nth\-last\-child$|^\:nth\-of\-type$|^\:nth\-last\-of\-type$|^\:first\-child$|^\:last\-child$|^\:first\-of\-type$|^\:last\-of\-type$|^\:only\-child$|^\:only\-of\-type$|^\:empty$|^\:link|^\:visited$|^\:active$|^\:hover$|^\:focus$|^\:target$|^\:lang$|^\:enabled^\:disabled$|^\:checked$|^\:first\-line$|^\:first\-letter$|^\:before$|^\:after$|^\:not$|^\:required$|^\:invalid$)/; - - function tokenBase(stream, state) { - var ch = stream.next(); - - if (ch == "@") {stream.eatWhile(/[\w\-]/); return ret("meta", stream.current());} - else if (ch == "/" && stream.eat("*")) { - state.tokenize = tokenCComment; - return tokenCComment(stream, state); - } else if (ch == "<" && stream.eat("!")) { - state.tokenize = tokenSGMLComment; - return tokenSGMLComment(stream, state); - } else if (ch == "=") ret(null, "compare"); - else if (ch == "|" && stream.eat("=")) return ret(null, "compare"); - else if (ch == "\"" || ch == "'") { - state.tokenize = tokenString(ch); - return state.tokenize(stream, state); - } else if (ch == "/") { // e.g.: .png will not be parsed as a class - if(stream.eat("/")){ - state.tokenize = tokenSComment; - return tokenSComment(stream, state); - } else { - if(type == "string" || type == "(") return ret("string", "string"); - if(state.stack[state.stack.length-1] != undefined) return ret(null, ch); - stream.eatWhile(/[\a-zA-Z0-9\-_.\s]/); - if( /\/|\)|#/.test(stream.peek() || (stream.eatSpace() && stream.peek() == ")")) || stream.eol() )return ret("string", "string"); // let url(/images/logo.png) without quotes return as string - } - } else if (ch == "!") { - stream.match(/^\s*\w*/); - return ret("keyword", "important"); - } else if (/\d/.test(ch)) { - stream.eatWhile(/[\w.%]/); - return ret("number", "unit"); - } else if (/[,+<>*\/]/.test(ch)) { - if(stream.peek() == "=" || type == "a")return ret("string", "string"); - if(ch === ",")return ret(null, ch); - return ret(null, "select-op"); - } else if (/[;{}:\[\]()~\|]/.test(ch)) { - if(ch == ":"){ - stream.eatWhile(/[a-z\\\-]/); - if( selectors.test(stream.current()) ){ - return ret("tag", "tag"); - } else if(stream.peek() == ":"){//::-webkit-search-decoration - stream.next(); - stream.eatWhile(/[a-z\\\-]/); - if(stream.current().match(/\:\:\-(o|ms|moz|webkit)\-/))return ret("string", "string"); - if( selectors.test(stream.current().substring(1)) )return ret("tag", "tag"); - return ret(null, ch); - } else { - return ret(null, ch); - } - } else if(ch == "~"){ - if(type == "r")return ret("string", "string"); - } else { - return ret(null, ch); - } - } else if (ch == ".") { - if(type == "(" || type == "string")return ret("string", "string"); // allow url(../image.png) - stream.eatWhile(/[\a-zA-Z0-9\-_]/); - if(stream.peek() == " ")stream.eatSpace(); - if(stream.peek() == ")")return ret("number", "unit");//rgba(0,0,0,.25); - return ret("tag", "tag"); - } else if (ch == "#") { - //we don't eat white-space, we want the hex color and or id only - stream.eatWhile(/[A-Za-z0-9]/); - //check if there is a proper hex color length e.g. #eee || #eeeEEE - if(stream.current().length == 4 || stream.current().length == 7){ - if(stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false) != null){//is there a valid hex color value present in the current stream - //when not a valid hex value, parse as id - if(stream.current().substring(1) != stream.current().match(/[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/,false))return ret("atom", "tag"); - //eat white-space - stream.eatSpace(); - //when hex value declaration doesn't end with [;,] but is does with a slash/cc comment treat it as an id, just like the other hex values that don't end with[;,] - if( /[\/<>.(){!$%^&*_\-\\?=+\|#'~`]/.test(stream.peek()) )return ret("atom", "tag"); - //#time { color: #aaa } - else if(stream.peek() == "}" )return ret("number", "unit"); - //we have a valid hex color value, parse as id whenever an element/class is defined after the hex(id) value e.g. #eee aaa || #eee .aaa - else if( /[a-zA-Z\\]/.test(stream.peek()) )return ret("atom", "tag"); - //when a hex value is on the end of a line, parse as id - else if(stream.eol())return ret("atom", "tag"); - //default - else return ret("number", "unit"); - } else {//when not a valid hexvalue in the current stream e.g. #footer - stream.eatWhile(/[\w\\\-]/); - return ret("atom", "tag"); - } - } else {//when not a valid hexvalue length - stream.eatWhile(/[\w\\\-]/); - return ret("atom", "tag"); - } - } else if (ch == "&") { - stream.eatWhile(/[\w\-]/); - return ret(null, ch); - } else { - stream.eatWhile(/[\w\\\-_%.{]/); - if(type == "string"){ - return ret("string", "string"); - } else if(stream.current().match(/(^http$|^https$)/) != null){ - stream.eatWhile(/[\w\\\-_%.{:\/]/); - return ret("string", "string"); - } else if(stream.peek() == "<" || stream.peek() == ">" || stream.peek() == "+"){ - return ret("tag", "tag"); - } else if( /\(/.test(stream.peek()) ){ - return ret(null, ch); - } else if (stream.peek() == "/" && state.stack[state.stack.length-1] != undefined){ // url(dir/center/image.png) - return ret("string", "string"); - } else if( stream.current().match(/\-\d|\-.\d/) ){ // match e.g.: -5px -0.4 etc... only colorize the minus sign - //commment out these 2 comment if you want the minus sign to be parsed as null -500px - //stream.backUp(stream.current().length-1); - //return ret(null, ch); - return ret("number", "unit"); - } else if( /\/|[\s\)]/.test(stream.peek() || stream.eol() || (stream.eatSpace() && stream.peek() == "/")) && stream.current().indexOf(".") !== -1){ - if(stream.current().substring(stream.current().length-1,stream.current().length) == "{"){ - stream.backUp(1); - return ret("tag", "tag"); - }//end if - stream.eatSpace(); - if( /[{<>.a-zA-Z\/]/.test(stream.peek()) || stream.eol() )return ret("tag", "tag"); // e.g. button.icon-plus - return ret("string", "string"); // let url(/images/logo.png) without quotes return as string - } else if( stream.eol() || stream.peek() == "[" || stream.peek() == "#" || type == "tag" ){ - if(stream.current().substring(stream.current().length-1,stream.current().length) == "{")stream.backUp(1); - return ret("tag", "tag"); - } else if(type == "compare" || type == "a" || type == "("){ - return ret("string", "string"); - } else if(type == "|" || stream.current() == "-" || type == "["){ - if(type == "|" )return ret("tag", "tag"); - return ret(null, ch); - } else if(stream.peek() == ":") { - stream.next(); - var t_v = stream.peek() == ":" ? true : false; - if(!t_v){ - var old_pos = stream.pos; - var sc = stream.current().length; - stream.eatWhile(/[a-z\\\-]/); - var new_pos = stream.pos; - if(stream.current().substring(sc-1).match(selectors) != null){ - stream.backUp(new_pos-(old_pos-1)); - return ret("tag", "tag"); - } else stream.backUp(new_pos-(old_pos-1)); - } else { - stream.backUp(1); - } - if(t_v)return ret("tag", "tag"); else return ret("variable", "variable"); - } else if(state.stack[state.stack.length-1] === "font-family"){ - return ret(null, null); - } else { - if(state.stack[state.stack.length-1] === "{" || type === "select-op" || (state.stack[state.stack.length-1] === "rule" && type === ",") )return ret("tag", "tag"); - return ret("variable", "variable"); - } - } - } - - function tokenSComment(stream, state) { // SComment = Slash comment - stream.skipToEnd(); - state.tokenize = tokenBase; - return ret("comment", "comment"); - } - - function tokenCComment(stream, state) { - var maybeEnd = false, ch; - while ((ch = stream.next()) != null) { - if (maybeEnd && ch == "/") { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return ret("comment", "comment"); - } - - function tokenSGMLComment(stream, state) { - var dashes = 0, ch; - while ((ch = stream.next()) != null) { - if (dashes >= 2 && ch == ">") { - state.tokenize = tokenBase; - break; - } - dashes = (ch == "-") ? dashes + 1 : 0; - } - return ret("comment", "comment"); - } - - function tokenString(quote) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) - break; - escaped = !escaped && ch == "\\"; - } - if (!escaped) state.tokenize = tokenBase; - return ret("string", "string"); - }; - } - - return { - startState: function(base) { - return {tokenize: tokenBase, - baseIndent: base || 0, - stack: []}; - }, - - token: function(stream, state) { - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - - var context = state.stack[state.stack.length-1]; - if (type == "hash" && context == "rule") style = "atom"; - else if (style == "variable") { - if (context == "rule") style = null; //"tag" - else if (!context || context == "@media{") { - style = stream.current() == "when" ? "variable" : - /[\s,|\s\)|\s]/.test(stream.peek()) ? "tag" : type; - } - } - - if (context == "rule" && /^[\{\};]$/.test(type)) - state.stack.pop(); - if (type == "{") { - if (context == "@media") state.stack[state.stack.length-1] = "@media{"; - else state.stack.push("{"); - } - else if (type == "}") state.stack.pop(); - else if (type == "@media") state.stack.push("@media"); - else if (stream.current() === "font-family") state.stack[state.stack.length-1] = "font-family"; - else if (context == "{" && type != "comment" && type !== "tag") state.stack.push("rule"); - else if (stream.peek() === ":" && stream.current().match(/@|#/) === null) style = type; - return style; - }, - - indent: function(state, textAfter) { - var n = state.stack.length; - - if (/^\}/.test(textAfter)) - n -= state.stack[state.stack.length-1] == "rule" ? 2 : 1; - return state.baseIndent + n * indentUnit; - }, - - electricChars: "}" - }; -}); - -CodeMirror.defineMIME("text/x-less", "less"); -if (!CodeMirror.mimeModes.hasOwnProperty("text/css")) - CodeMirror.defineMIME("text/css", "less"); diff --git a/applications/admin/static/codemirror/mode/livescript/LICENSE b/applications/admin/static/codemirror/mode/livescript/LICENSE deleted file mode 100644 index a675c402..00000000 --- a/applications/admin/static/codemirror/mode/livescript/LICENSE +++ /dev/null @@ -1,23 +0,0 @@ -The MIT License - -Copyright (c) 2013 Kenneth Bentley -Modified from the CoffeeScript CodeMirror mode, Copyright (c) 2011 Jeff Pickhardt -Modified from the Python CodeMirror mode, Copyright (c) 2010 Timothy Farrell - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/applications/admin/static/codemirror/mode/livescript/index.html b/applications/admin/static/codemirror/mode/livescript/index.html deleted file mode 100644 index b5944697..00000000 --- a/applications/admin/static/codemirror/mode/livescript/index.html +++ /dev/null @@ -1,459 +0,0 @@ - - -CodeMirror: LiveScript mode - - - - - - - - - - -
-

LiveScript mode

-
- - -

MIME types defined: text/x-livescript.

- -

The LiveScript mode was written by Kenneth Bentley (license).

- -
diff --git a/applications/admin/static/codemirror/mode/livescript/livescript.js b/applications/admin/static/codemirror/mode/livescript/livescript.js deleted file mode 100644 index c000324b..00000000 --- a/applications/admin/static/codemirror/mode/livescript/livescript.js +++ /dev/null @@ -1,267 +0,0 @@ -/** - * Link to the project's GitHub page: - * https://github.com/duralog/CodeMirror - */ -(function() { - CodeMirror.defineMode('livescript', function(){ - var tokenBase, external; - tokenBase = function(stream, state){ - var next_rule, nr, i$, len$, r, m; - if (next_rule = state.next || 'start') { - state.next = state.next; - if (Array.isArray(nr = Rules[next_rule])) { - for (i$ = 0, len$ = nr.length; i$ < len$; ++i$) { - r = nr[i$]; - if (r.regex && (m = stream.match(r.regex))) { - state.next = r.next; - return r.token; - } - } - stream.next(); - return 'error'; - } - if (stream.match(r = Rules[next_rule])) { - if (r.regex && stream.match(r.regex)) { - state.next = r.next; - return r.token; - } else { - stream.next(); - return 'error'; - } - } - } - stream.next(); - return 'error'; - }; - external = { - startState: function(){ - return { - next: 'start', - lastToken: null - }; - }, - token: function(stream, state){ - var style; - style = tokenBase(stream, state); - state.lastToken = { - style: style, - indent: stream.indentation(), - content: stream.current() - }; - return style.replace(/\./g, ' '); - }, - indent: function(state){ - var indentation; - indentation = state.lastToken.indent; - if (state.lastToken.content.match(indenter)) { - indentation += 2; - } - return indentation; - } - }; - return external; - }); - - var identifier = '(?![\\d\\s])[$\\w\\xAA-\\uFFDC](?:(?!\\s)[$\\w\\xAA-\\uFFDC]|-[A-Za-z])*'; - var indenter = RegExp('(?:[({[=:]|[-~]>|\\b(?:e(?:lse|xport)|d(?:o|efault)|t(?:ry|hen)|finally|import(?:\\s*all)?|const|var|let|new|catch(?:\\s*' + identifier + ')?))\\s*$'); - var keywordend = '(?![$\\w]|-[A-Za-z]|\\s*:(?![:=]))'; - var stringfill = { - token: 'string', - regex: '.+' - }; - var Rules = { - start: [ - { - token: 'comment.doc', - regex: '/\\*', - next: 'comment' - }, { - token: 'comment', - regex: '#.*' - }, { - token: 'keyword', - regex: '(?:t(?:h(?:is|row|en)|ry|ypeof!?)|c(?:on(?:tinue|st)|a(?:se|tch)|lass)|i(?:n(?:stanceof)?|mp(?:ort(?:\\s+all)?|lements)|[fs])|d(?:e(?:fault|lete|bugger)|o)|f(?:or(?:\\s+own)?|inally|unction)|s(?:uper|witch)|e(?:lse|x(?:tends|port)|val)|a(?:nd|rguments)|n(?:ew|ot)|un(?:less|til)|w(?:hile|ith)|o[fr]|return|break|let|var|loop)' + keywordend - }, { - token: 'constant.language', - regex: '(?:true|false|yes|no|on|off|null|void|undefined)' + keywordend - }, { - token: 'invalid.illegal', - regex: '(?:p(?:ackage|r(?:ivate|otected)|ublic)|i(?:mplements|nterface)|enum|static|yield)' + keywordend - }, { - token: 'language.support.class', - regex: '(?:R(?:e(?:gExp|ferenceError)|angeError)|S(?:tring|yntaxError)|E(?:rror|valError)|Array|Boolean|Date|Function|Number|Object|TypeError|URIError)' + keywordend - }, { - token: 'language.support.function', - regex: '(?:is(?:NaN|Finite)|parse(?:Int|Float)|Math|JSON|(?:en|de)codeURI(?:Component)?)' + keywordend - }, { - token: 'variable.language', - regex: '(?:t(?:hat|il|o)|f(?:rom|allthrough)|it|by|e)' + keywordend - }, { - token: 'identifier', - regex: identifier + '\\s*:(?![:=])' - }, { - token: 'variable', - regex: identifier - }, { - token: 'keyword.operator', - regex: '(?:\\.{3}|\\s+\\?)' - }, { - token: 'keyword.variable', - regex: '(?:@+|::|\\.\\.)', - next: 'key' - }, { - token: 'keyword.operator', - regex: '\\.\\s*', - next: 'key' - }, { - token: 'string', - regex: '\\\\\\S[^\\s,;)}\\]]*' - }, { - token: 'string.doc', - regex: '\'\'\'', - next: 'qdoc' - }, { - token: 'string.doc', - regex: '"""', - next: 'qqdoc' - }, { - token: 'string', - regex: '\'', - next: 'qstring' - }, { - token: 'string', - regex: '"', - next: 'qqstring' - }, { - token: 'string', - regex: '`', - next: 'js' - }, { - token: 'string', - regex: '<\\[', - next: 'words' - }, { - token: 'string.regex', - regex: '//', - next: 'heregex' - }, { - token: 'string.regex', - regex: '\\/(?:[^[\\/\\n\\\\]*(?:(?:\\\\.|\\[[^\\]\\n\\\\]*(?:\\\\.[^\\]\\n\\\\]*)*\\])[^[\\/\\n\\\\]*)*)\\/[gimy$]{0,4}', - next: 'key' - }, { - token: 'constant.numeric', - regex: '(?:0x[\\da-fA-F][\\da-fA-F_]*|(?:[2-9]|[12]\\d|3[0-6])r[\\da-zA-Z][\\da-zA-Z_]*|(?:\\d[\\d_]*(?:\\.\\d[\\d_]*)?|\\.\\d[\\d_]*)(?:e[+-]?\\d[\\d_]*)?[\\w$]*)' - }, { - token: 'lparen', - regex: '[({[]' - }, { - token: 'rparen', - regex: '[)}\\]]', - next: 'key' - }, { - token: 'keyword.operator', - regex: '\\S+' - }, { - token: 'text', - regex: '\\s+' - } - ], - heregex: [ - { - token: 'string.regex', - regex: '.*?//[gimy$?]{0,4}', - next: 'start' - }, { - token: 'string.regex', - regex: '\\s*#{' - }, { - token: 'comment.regex', - regex: '\\s+(?:#.*)?' - }, { - token: 'string.regex', - regex: '\\S+' - } - ], - key: [ - { - token: 'keyword.operator', - regex: '[.?@!]+' - }, { - token: 'identifier', - regex: identifier, - next: 'start' - }, { - token: 'text', - regex: '.', - next: 'start' - } - ], - comment: [ - { - token: 'comment.doc', - regex: '.*?\\*/', - next: 'start' - }, { - token: 'comment.doc', - regex: '.+' - } - ], - qdoc: [ - { - token: 'string', - regex: ".*?'''", - next: 'key' - }, stringfill - ], - qqdoc: [ - { - token: 'string', - regex: '.*?"""', - next: 'key' - }, stringfill - ], - qstring: [ - { - token: 'string', - regex: '[^\\\\\']*(?:\\\\.[^\\\\\']*)*\'', - next: 'key' - }, stringfill - ], - qqstring: [ - { - token: 'string', - regex: '[^\\\\"]*(?:\\\\.[^\\\\"]*)*"', - next: 'key' - }, stringfill - ], - js: [ - { - token: 'string', - regex: '[^\\\\`]*(?:\\\\.[^\\\\`]*)*`', - next: 'key' - }, stringfill - ], - words: [ - { - token: 'string', - regex: '.*?\\]>', - next: 'key' - }, stringfill - ] - }; - for (var idx in Rules) { - var r = Rules[idx]; - if (Array.isArray(r)) { - for (var i = 0, len = r.length; i < len; ++i) { - var rr = r[i]; - if (rr.regex) { - Rules[idx][i].regex = new RegExp('^' + rr.regex); - } - } - } else if (r.regex) { - Rules[idx].regex = new RegExp('^' + r.regex); - } - } -})(); - -CodeMirror.defineMIME('text/x-livescript', 'livescript'); diff --git a/applications/admin/static/codemirror/mode/livescript/livescript.ls b/applications/admin/static/codemirror/mode/livescript/livescript.ls deleted file mode 100644 index 06524231..00000000 --- a/applications/admin/static/codemirror/mode/livescript/livescript.ls +++ /dev/null @@ -1,266 +0,0 @@ -/** - * Link to the project's GitHub page: - * https://github.com/duralog/CodeMirror - */ -CodeMirror.defineMode 'livescript', (conf) -> - tokenBase = (stream, state) -> - #indent = - if next_rule = state.next or \start - state.next = state.next - if Array.isArray nr = Rules[next_rule] - for r in nr - if r.regex and m = stream.match r.regex - state.next = r.next - return r.token - stream.next! - return \error - if stream.match r = Rules[next_rule] - if r.regex and stream.match r.regex - state.next = r.next - return r.token - else - stream.next! - return \error - stream.next! - return 'error' - external = { - startState: (basecolumn) -> - { - next: \start - lastToken: null - } - token: (stream, state) -> - style = tokenBase stream, state #tokenLexer stream, state - state.lastToken = { - style: style - indent: stream.indentation! - content: stream.current! - } - style.replace /\./g, ' ' - indent: (state, textAfter) -> - # XXX this won't work with backcalls - indentation = state.lastToken.indent - if state.lastToken.content.match indenter then indentation += 2 - return indentation - } - external - -### Highlight Rules -# taken from mode-ls.ls - -indenter = // (? - : [({[=:] - | [-~]> - | \b (?: e(?:lse|xport) | d(?:o|efault) | t(?:ry|hen) | finally | - import (?:\s* all)? | const | var | - let | new | catch (?:\s* #identifier)? ) - ) \s* $ // - -identifier = /(?![\d\s])[$\w\xAA-\uFFDC](?:(?!\s)[$\w\xAA-\uFFDC]|-[A-Za-z])*/$ -keywordend = /(?![$\w]|-[A-Za-z]|\s*:(?![:=]))/$ -stringfill = token: \string, regex: '.+' - -Rules = - start: - * token: \comment.doc - regex: '/\\*' - next : \comment - - * token: \comment - regex: '#.*' - - * token: \keyword - regex: //(? - :t(?:h(?:is|row|en)|ry|ypeof!?) - |c(?:on(?:tinue|st)|a(?:se|tch)|lass) - |i(?:n(?:stanceof)?|mp(?:ort(?:\s+all)?|lements)|[fs]) - |d(?:e(?:fault|lete|bugger)|o) - |f(?:or(?:\s+own)?|inally|unction) - |s(?:uper|witch) - |e(?:lse|x(?:tends|port)|val) - |a(?:nd|rguments) - |n(?:ew|ot) - |un(?:less|til) - |w(?:hile|ith) - |o[fr]|return|break|let|var|loop - )//$ + keywordend - - * token: \constant.language - regex: '(?:true|false|yes|no|on|off|null|void|undefined)' + keywordend - - * token: \invalid.illegal - regex: '(? - :p(?:ackage|r(?:ivate|otected)|ublic) - |i(?:mplements|nterface) - |enum|static|yield - )' + keywordend - - * token: \language.support.class - regex: '(? - :R(?:e(?:gExp|ferenceError)|angeError) - |S(?:tring|yntaxError) - |E(?:rror|valError) - |Array|Boolean|Date|Function|Number|Object|TypeError|URIError - )' + keywordend - - * token: \language.support.function - regex: '(? - :is(?:NaN|Finite) - |parse(?:Int|Float) - |Math|JSON - |(?:en|de)codeURI(?:Component)? - )' + keywordend - - * token: \variable.language - regex: '(?:t(?:hat|il|o)|f(?:rom|allthrough)|it|by|e)' + keywordend - - * token: \identifier - regex: identifier + /\s*:(?![:=])/$ - - * token: \variable - regex: identifier - - * token: \keyword.operator - regex: /(?:\.{3}|\s+\?)/$ - - * token: \keyword.variable - regex: /(?:@+|::|\.\.)/$ - next : \key - - * token: \keyword.operator - regex: /\.\s*/$ - next : \key - - * token: \string - regex: /\\\S[^\s,;)}\]]*/$ - - * token: \string.doc - regex: \''' - next : \qdoc - - * token: \string.doc - regex: \""" - next : \qqdoc - - * token: \string - regex: \' - next : \qstring - - * token: \string - regex: \" - next : \qqstring - - * token: \string - regex: \` - next : \js - - * token: \string - regex: '<\\[' - next : \words - - * token: \string.regex - regex: \// - next : \heregex - - * token: \string.regex - regex: // - /(?: [^ [ / \n \\ ]* - (?: (?: \\. - | \[ [^\]\n\\]* (?:\\.[^\]\n\\]*)* \] - ) [^ [ / \n \\ ]* - )* - )/ [gimy$]{0,4} - //$ - next : \key - - * token: \constant.numeric - regex: '(?:0x[\\da-fA-F][\\da-fA-F_]* - |(?:[2-9]|[12]\\d|3[0-6])r[\\da-zA-Z][\\da-zA-Z_]* - |(?:\\d[\\d_]*(?:\\.\\d[\\d_]*)?|\\.\\d[\\d_]*) - (?:e[+-]?\\d[\\d_]*)?[\\w$]*)' - - * token: \lparen - regex: '[({[]' - - * token: \rparen - regex: '[)}\\]]' - next : \key - - * token: \keyword.operator - regex: \\\S+ - - * token: \text - regex: \\\s+ - - heregex: - * token: \string.regex - regex: '.*?//[gimy$?]{0,4}' - next : \start - * token: \string.regex - regex: '\\s*#{' - * token: \comment.regex - regex: '\\s+(?:#.*)?' - * token: \string.regex - regex: '\\S+' - - key: - * token: \keyword.operator - regex: '[.?@!]+' - * token: \identifier - regex: identifier - next : \start - * token: \text - regex: '.' - next : \start - - comment: - * token: \comment.doc - regex: '.*?\\*/' - next : \start - * token: \comment.doc - regex: '.+' - - qdoc: - token: \string - regex: ".*?'''" - next : \key - stringfill - - qqdoc: - token: \string - regex: '.*?"""' - next : \key - stringfill - - qstring: - token: \string - regex: /[^\\']*(?:\\.[^\\']*)*'/$ - next : \key - stringfill - - qqstring: - token: \string - regex: /[^\\"]*(?:\\.[^\\"]*)*"/$ - next : \key - stringfill - - js: - token: \string - regex: /[^\\`]*(?:\\.[^\\`]*)*`/$ - next : \key - stringfill - - words: - token: \string - regex: '.*?\\]>' - next : \key - stringfill - -# for optimization, precompile the regexps -for idx, r of Rules - if Array.isArray r - for rr, i in r - if rr.regex then Rules[idx][i].regex = new RegExp '^'+rr.regex - else if r.regex then Rules[idx].regex = new RegExp '^'+r.regex - -CodeMirror.defineMIME 'text/x-livescript', 'livescript' diff --git a/applications/admin/static/codemirror/mode/markdown/index.html b/applications/admin/static/codemirror/mode/markdown/index.html deleted file mode 100644 index a6b541e2..00000000 --- a/applications/admin/static/codemirror/mode/markdown/index.html +++ /dev/null @@ -1,359 +0,0 @@ - - -CodeMirror: Markdown mode - - - - - - - - - - - -
-

Markdown mode

-
- - - -

Optionally depends on the XML mode for properly highlighted inline XML blocks.

- -

MIME types defined: text/x-markdown.

- -

Parsing/Highlighting Tests: normal, verbose.

- -
diff --git a/applications/admin/static/codemirror/mode/markdown/markdown.js b/applications/admin/static/codemirror/mode/markdown/markdown.js deleted file mode 100644 index bf1750d5..00000000 --- a/applications/admin/static/codemirror/mode/markdown/markdown.js +++ /dev/null @@ -1,551 +0,0 @@ -CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { - - var htmlFound = CodeMirror.modes.hasOwnProperty("xml"); - var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? {name: "xml", htmlMode: true} : "text/plain"); - var aliases = { - html: "htmlmixed", - js: "javascript", - json: "application/json", - c: "text/x-csrc", - "c++": "text/x-c++src", - java: "text/x-java", - csharp: "text/x-csharp", - "c#": "text/x-csharp", - scala: "text/x-scala" - }; - - var getMode = (function () { - var i, modes = {}, mimes = {}, mime; - - var list = []; - for (var m in CodeMirror.modes) - if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m); - for (i = 0; i < list.length; i++) { - modes[list[i]] = list[i]; - } - var mimesList = []; - for (var m in CodeMirror.mimeModes) - if (CodeMirror.mimeModes.propertyIsEnumerable(m)) - mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]}); - for (i = 0; i < mimesList.length; i++) { - mime = mimesList[i].mime; - mimes[mime] = mimesList[i].mime; - } - - for (var a in aliases) { - if (aliases[a] in modes || aliases[a] in mimes) - modes[a] = aliases[a]; - } - - return function (lang) { - return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null; - }; - }()); - - // Should underscores in words open/close em/strong? - if (modeCfg.underscoresBreakWords === undefined) - modeCfg.underscoresBreakWords = true; - - // Turn on fenced code blocks? ("```" to start/end) - if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false; - - // Turn on task lists? ("- [ ] " and "- [x] ") - if (modeCfg.taskLists === undefined) modeCfg.taskLists = false; - - var codeDepth = 0; - - var header = 'header' - , code = 'comment' - , quote1 = 'atom' - , quote2 = 'number' - , list1 = 'variable-2' - , list2 = 'variable-3' - , list3 = 'keyword' - , hr = 'hr' - , image = 'tag' - , linkinline = 'link' - , linkemail = 'link' - , linktext = 'link' - , linkhref = 'string' - , em = 'em' - , strong = 'strong'; - - var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/ - , ulRE = /^[*\-+]\s+/ - , olRE = /^[0-9]+\.\s+/ - , taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE - , headerRE = /^(?:\={1,}|-{1,})$/ - , textRE = /^[^!\[\]*_\\<>` "'(]+/; - - function switchInline(stream, state, f) { - state.f = state.inline = f; - return f(stream, state); - } - - function switchBlock(stream, state, f) { - state.f = state.block = f; - return f(stream, state); - } - - - // Blocks - - function blankLine(state) { - // Reset linkTitle state - state.linkTitle = false; - // Reset EM state - state.em = false; - // Reset STRONG state - state.strong = false; - // Reset state.quote - state.quote = 0; - if (!htmlFound && state.f == htmlBlock) { - state.f = inlineNormal; - state.block = blockNormal; - } - // Reset state.trailingSpace - state.trailingSpace = 0; - state.trailingSpaceNewLine = false; - // Mark this line as blank - state.thisLineHasContent = false; - return null; - } - - function blockNormal(stream, state) { - - var prevLineIsList = (state.list !== false); - if (state.list !== false && state.indentationDiff >= 0) { // Continued list - if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block - state.indentation -= state.indentationDiff; - } - state.list = null; - } else if (state.list !== false && state.indentation > 0) { - state.list = null; - state.listDepth = Math.floor(state.indentation / 4); - } else if (state.list !== false) { // No longer a list - state.list = false; - state.listDepth = 0; - } - - if (state.indentationDiff >= 4) { - state.indentation -= 4; - stream.skipToEnd(); - return code; - } else if (stream.eatSpace()) { - return null; - } else if (stream.peek() === '#' || (state.prevLineHasContent && stream.match(headerRE)) ) { - state.header = true; - } else if (stream.eat('>')) { - state.indentation++; - state.quote = 1; - stream.eatSpace(); - while (stream.eat('>')) { - stream.eatSpace(); - state.quote++; - } - } else if (stream.peek() === '[') { - return switchInline(stream, state, footnoteLink); - } else if (stream.match(hrRE, true)) { - return hr; - } else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, true) || stream.match(olRE, true))) { - state.indentation += 4; - state.list = true; - state.listDepth++; - if (modeCfg.taskLists && stream.match(taskListRE, false)) { - state.taskList = true; - } - } else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) { - // try switching mode - state.localMode = getMode(RegExp.$1); - if (state.localMode) state.localState = state.localMode.startState(); - switchBlock(stream, state, local); - return code; - } - - return switchInline(stream, state, state.inline); - } - - function htmlBlock(stream, state) { - var style = htmlMode.token(stream, state.htmlState); - if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) { - state.f = inlineNormal; - state.block = blockNormal; - } - if (state.md_inside && stream.current().indexOf(">")!=-1) { - state.f = inlineNormal; - state.block = blockNormal; - state.htmlState.context = undefined; - } - return style; - } - - function local(stream, state) { - if (stream.sol() && stream.match(/^```/, true)) { - state.localMode = state.localState = null; - state.f = inlineNormal; - state.block = blockNormal; - return code; - } else if (state.localMode) { - return state.localMode.token(stream, state.localState); - } else { - stream.skipToEnd(); - return code; - } - } - - // Inline - function getType(state) { - var styles = []; - - if (state.taskOpen) { return "meta"; } - if (state.taskClosed) { return "property"; } - - if (state.strong) { styles.push(strong); } - if (state.em) { styles.push(em); } - - if (state.linkText) { styles.push(linktext); } - - if (state.code) { styles.push(code); } - - if (state.header) { styles.push(header); } - if (state.quote) { styles.push(state.quote % 2 ? quote1 : quote2); } - if (state.list !== false) { - var listMod = (state.listDepth - 1) % 3; - if (!listMod) { - styles.push(list1); - } else if (listMod === 1) { - styles.push(list2); - } else { - styles.push(list3); - } - } - - if (state.trailingSpaceNewLine) { - styles.push("trailing-space-new-line"); - } else if (state.trailingSpace) { - styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b")); - } - - return styles.length ? styles.join(' ') : null; - } - - function handleText(stream, state) { - if (stream.match(textRE, true)) { - return getType(state); - } - return undefined; - } - - function inlineNormal(stream, state) { - var style = state.text(stream, state); - if (typeof style !== 'undefined') - return style; - - if (state.list) { // List marker (*, +, -, 1., etc) - state.list = null; - return getType(state); - } - - if (state.taskList) { - var taskOpen = stream.match(taskListRE, true)[1] !== "x"; - if (taskOpen) state.taskOpen = true; - else state.taskClosed = true; - state.taskList = false; - return getType(state); - } - - state.taskOpen = false; - state.taskClosed = false; - - var ch = stream.next(); - - if (ch === '\\') { - stream.next(); - return getType(state); - } - - // Matches link titles present on next line - if (state.linkTitle) { - state.linkTitle = false; - var matchCh = ch; - if (ch === '(') { - matchCh = ')'; - } - matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); - var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; - if (stream.match(new RegExp(regex), true)) { - return linkhref; - } - } - - // If this block is changed, it may need to be updated in GFM mode - if (ch === '`') { - var t = getType(state); - var before = stream.pos; - stream.eatWhile('`'); - var difference = 1 + stream.pos - before; - if (!state.code) { - codeDepth = difference; - state.code = true; - return getType(state); - } else { - if (difference === codeDepth) { // Must be exact - state.code = false; - return t; - } - return getType(state); - } - } else if (state.code) { - return getType(state); - } - - if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) { - stream.match(/\[[^\]]*\]/); - state.inline = state.f = linkHref; - return image; - } - - if (ch === '[' && stream.match(/.*\](\(| ?\[)/, false)) { - state.linkText = true; - return getType(state); - } - - if (ch === ']' && state.linkText) { - var type = getType(state); - state.linkText = false; - state.inline = state.f = linkHref; - return type; - } - - if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) { - return switchInline(stream, state, inlineElement(linkinline, '>')); - } - - if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) { - return switchInline(stream, state, inlineElement(linkemail, '>')); - } - - if (ch === '<' && stream.match(/^\w/, false)) { - if (stream.string.indexOf(">")!=-1) { - var atts = stream.string.substring(1,stream.string.indexOf(">")); - if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) { - state.md_inside = true; - } - } - stream.backUp(1); - return switchBlock(stream, state, htmlBlock); - } - - if (ch === '<' && stream.match(/^\/\w*?>/)) { - state.md_inside = false; - return "tag"; - } - - var ignoreUnderscore = false; - if (!modeCfg.underscoresBreakWords) { - if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) { - var prevPos = stream.pos - 2; - if (prevPos >= 0) { - var prevCh = stream.string.charAt(prevPos); - if (prevCh !== '_' && prevCh.match(/(\w)/, false)) { - ignoreUnderscore = true; - } - } - } - } - var t = getType(state); - if (ch === '*' || (ch === '_' && !ignoreUnderscore)) { - if (state.strong === ch && stream.eat(ch)) { // Remove STRONG - state.strong = false; - return t; - } else if (!state.strong && stream.eat(ch)) { // Add STRONG - state.strong = ch; - return getType(state); - } else if (state.em === ch) { // Remove EM - state.em = false; - return t; - } else if (!state.em) { // Add EM - state.em = ch; - return getType(state); - } - } else if (ch === ' ') { - if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces - if (stream.peek() === ' ') { // Surrounded by spaces, ignore - return getType(state); - } else { // Not surrounded by spaces, back up pointer - stream.backUp(1); - } - } - } - - if (ch === ' ') { - if (stream.match(/ +$/, false)) { - state.trailingSpace++; - } else if (state.trailingSpace) { - state.trailingSpaceNewLine = true; - } - } - - return getType(state); - } - - function linkHref(stream, state) { - // Check if space, and return NULL if so (to avoid marking the space) - if(stream.eatSpace()){ - return null; - } - var ch = stream.next(); - if (ch === '(' || ch === '[') { - return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']')); - } - return 'error'; - } - - function footnoteLink(stream, state) { - if (stream.match(/^[^\]]*\]:/, true)) { - state.f = footnoteUrl; - return linktext; - } - return switchInline(stream, state, inlineNormal); - } - - function footnoteUrl(stream, state) { - // Check if space, and return NULL if so (to avoid marking the space) - if(stream.eatSpace()){ - return null; - } - // Match URL - stream.match(/^[^\s]+/, true); - // Check for link title - if (stream.peek() === undefined) { // End of line, set flag to check next line - state.linkTitle = true; - } else { // More content on line, check if link title - stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); - } - state.f = state.inline = inlineNormal; - return linkhref; - } - - var savedInlineRE = []; - function inlineRE(endChar) { - if (!savedInlineRE[endChar]) { - // Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741) - endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); - // Match any non-endChar, escaped character, as well as the closing - // endChar. - savedInlineRE[endChar] = new RegExp('^(?:[^\\\\]|\\\\.)*?(' + endChar + ')'); - } - return savedInlineRE[endChar]; - } - - function inlineElement(type, endChar, next) { - next = next || inlineNormal; - return function(stream, state) { - stream.match(inlineRE(endChar)); - state.inline = state.f = next; - return type; - }; - } - - return { - startState: function() { - return { - f: blockNormal, - - prevLineHasContent: false, - thisLineHasContent: false, - - block: blockNormal, - htmlState: CodeMirror.startState(htmlMode), - indentation: 0, - - inline: inlineNormal, - text: handleText, - - linkText: false, - linkTitle: false, - em: false, - strong: false, - header: false, - taskList: false, - list: false, - listDepth: 0, - quote: 0, - trailingSpace: 0, - trailingSpaceNewLine: false - }; - }, - - copyState: function(s) { - return { - f: s.f, - - prevLineHasContent: s.prevLineHasContent, - thisLineHasContent: s.thisLineHasContent, - - block: s.block, - htmlState: CodeMirror.copyState(htmlMode, s.htmlState), - indentation: s.indentation, - - localMode: s.localMode, - localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null, - - inline: s.inline, - text: s.text, - linkTitle: s.linkTitle, - em: s.em, - strong: s.strong, - header: s.header, - taskList: s.taskList, - list: s.list, - listDepth: s.listDepth, - quote: s.quote, - trailingSpace: s.trailingSpace, - trailingSpaceNewLine: s.trailingSpaceNewLine, - md_inside: s.md_inside - }; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (stream.match(/^\s*$/, true)) { - state.prevLineHasContent = false; - return blankLine(state); - } else { - state.prevLineHasContent = state.thisLineHasContent; - state.thisLineHasContent = true; - } - - // Reset state.header - state.header = false; - - // Reset state.taskList - state.taskList = false; - - // Reset state.code - state.code = false; - - // Reset state.trailingSpace - state.trailingSpace = 0; - state.trailingSpaceNewLine = false; - - state.f = state.block; - var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; - var difference = Math.floor((indentation - state.indentation) / 4) * 4; - if (difference > 4) difference = 4; - var adjustedIndentation = state.indentation + difference; - state.indentationDiff = adjustedIndentation - state.indentation; - state.indentation = adjustedIndentation; - if (indentation > 0) return null; - } - return state.f(stream, state); - }, - - blankLine: blankLine, - - getType: getType - }; - -}, "xml"); - -CodeMirror.defineMIME("text/x-markdown", "markdown"); diff --git a/applications/admin/static/codemirror/mode/markdown/test.js b/applications/admin/static/codemirror/mode/markdown/test.js deleted file mode 100644 index f1679172..00000000 --- a/applications/admin/static/codemirror/mode/markdown/test.js +++ /dev/null @@ -1,656 +0,0 @@ -(function() { - var mode = CodeMirror.getMode({tabSize: 4}, "markdown"); - function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } - - MT("plainText", - "foo"); - - // Don't style single trailing space - MT("trailingSpace1", - "foo "); - - // Two or more trailing spaces should be styled with line break character - MT("trailingSpace2", - "foo[trailing-space-a ][trailing-space-new-line ]"); - - MT("trailingSpace3", - "foo[trailing-space-a ][trailing-space-b ][trailing-space-new-line ]"); - - MT("trailingSpace4", - "foo[trailing-space-a ][trailing-space-b ][trailing-space-a ][trailing-space-new-line ]"); - - // Code blocks using 4 spaces (regardless of CodeMirror.tabSize value) - MT("codeBlocksUsing4Spaces", - " [comment foo]"); - - // Code blocks using 4 spaces with internal indentation - MT("codeBlocksUsing4SpacesIndentation", - " [comment bar]", - " [comment hello]", - " [comment world]", - " [comment foo]", - "bar"); - - // Code blocks using 4 spaces with internal indentation - MT("codeBlocksUsing4SpacesIndentation", - " foo", - " [comment bar]", - " [comment hello]", - " [comment world]"); - - // Code blocks using 1 tab (regardless of CodeMirror.indentWithTabs value) - MT("codeBlocksUsing1Tab", - "\t[comment foo]"); - - // Inline code using backticks - MT("inlineCodeUsingBackticks", - "foo [comment `bar`]"); - - // Block code using single backtick (shouldn't work) - MT("blockCodeSingleBacktick", - "[comment `]", - "foo", - "[comment `]"); - - // Unclosed backticks - // Instead of simply marking as CODE, it would be nice to have an - // incomplete flag for CODE, that is styled slightly different. - MT("unclosedBackticks", - "foo [comment `bar]"); - - // Per documentation: "To include a literal backtick character within a - // code span, you can use multiple backticks as the opening and closing - // delimiters" - MT("doubleBackticks", - "[comment ``foo ` bar``]"); - - // Tests based on Dingus - // http://daringfireball.net/projects/markdown/dingus - // - // Multiple backticks within an inline code block - MT("consecutiveBackticks", - "[comment `foo```bar`]"); - - // Multiple backticks within an inline code block with a second code block - MT("consecutiveBackticks", - "[comment `foo```bar`] hello [comment `world`]"); - - // Unclosed with several different groups of backticks - MT("unclosedBackticks", - "[comment ``foo ``` bar` hello]"); - - // Closed with several different groups of backticks - MT("closedBackticks", - "[comment ``foo ``` bar` hello``] world"); - - // atx headers - // http://daringfireball.net/projects/markdown/syntax#header - - MT("atxH1", - "[header # foo]"); - - MT("atxH2", - "[header ## foo]"); - - MT("atxH3", - "[header ### foo]"); - - MT("atxH4", - "[header #### foo]"); - - MT("atxH5", - "[header ##### foo]"); - - MT("atxH6", - "[header ###### foo]"); - - // H6 - 7x '#' should still be H6, per Dingus - // http://daringfireball.net/projects/markdown/dingus - MT("atxH6NotH7", - "[header ####### foo]"); - - // Setext headers - H1, H2 - // Per documentation, "Any number of underlining =’s or -’s will work." - // http://daringfireball.net/projects/markdown/syntax#header - // Ideally, the text would be marked as `header` as well, but this is - // not really feasible at the moment. So, instead, we're testing against - // what works today, to avoid any regressions. - // - // Check if single underlining = works - MT("setextH1", - "foo", - "[header =]"); - - // Check if 3+ ='s work - MT("setextH1", - "foo", - "[header ===]"); - - // Check if single underlining - works - MT("setextH2", - "foo", - "[header -]"); - - // Check if 3+ -'s work - MT("setextH2", - "foo", - "[header ---]"); - - // Single-line blockquote with trailing space - MT("blockquoteSpace", - "[atom > foo]"); - - // Single-line blockquote - MT("blockquoteNoSpace", - "[atom >foo]"); - - // No blank line before blockquote - MT("blockquoteNoBlankLine", - "foo", - "[atom > bar]"); - - // Nested blockquote - MT("blockquoteSpace", - "[atom > foo]", - "[number > > foo]", - "[atom > > > foo]"); - - // Single-line blockquote followed by normal paragraph - MT("blockquoteThenParagraph", - "[atom >foo]", - "", - "bar"); - - // Multi-line blockquote (lazy mode) - MT("multiBlockquoteLazy", - "[atom >foo]", - "[atom bar]"); - - // Multi-line blockquote followed by normal paragraph (lazy mode) - MT("multiBlockquoteLazyThenParagraph", - "[atom >foo]", - "[atom bar]", - "", - "hello"); - - // Multi-line blockquote (non-lazy mode) - MT("multiBlockquote", - "[atom >foo]", - "[atom >bar]"); - - // Multi-line blockquote followed by normal paragraph (non-lazy mode) - MT("multiBlockquoteThenParagraph", - "[atom >foo]", - "[atom >bar]", - "", - "hello"); - - // Check list types - - MT("listAsterisk", - "foo", - "bar", - "", - "[variable-2 * foo]", - "[variable-2 * bar]"); - - MT("listPlus", - "foo", - "bar", - "", - "[variable-2 + foo]", - "[variable-2 + bar]"); - - MT("listDash", - "foo", - "bar", - "", - "[variable-2 - foo]", - "[variable-2 - bar]"); - - MT("listNumber", - "foo", - "bar", - "", - "[variable-2 1. foo]", - "[variable-2 2. bar]"); - - // Lists require a preceding blank line (per Dingus) - MT("listBogus", - "foo", - "1. bar", - "2. hello"); - - // Formatting in lists (*) - MT("listAsteriskFormatting", - "[variable-2 * ][variable-2&em *foo*][variable-2 bar]", - "[variable-2 * ][variable-2&strong **foo**][variable-2 bar]", - "[variable-2 * ][variable-2&strong **][variable-2&em&strong *foo**][variable-2&em *][variable-2 bar]", - "[variable-2 * ][variable-2&comment `foo`][variable-2 bar]"); - - // Formatting in lists (+) - MT("listPlusFormatting", - "[variable-2 + ][variable-2&em *foo*][variable-2 bar]", - "[variable-2 + ][variable-2&strong **foo**][variable-2 bar]", - "[variable-2 + ][variable-2&strong **][variable-2&em&strong *foo**][variable-2&em *][variable-2 bar]", - "[variable-2 + ][variable-2&comment `foo`][variable-2 bar]"); - - // Formatting in lists (-) - MT("listDashFormatting", - "[variable-2 - ][variable-2&em *foo*][variable-2 bar]", - "[variable-2 - ][variable-2&strong **foo**][variable-2 bar]", - "[variable-2 - ][variable-2&strong **][variable-2&em&strong *foo**][variable-2&em *][variable-2 bar]", - "[variable-2 - ][variable-2&comment `foo`][variable-2 bar]"); - - // Formatting in lists (1.) - MT("listNumberFormatting", - "[variable-2 1. ][variable-2&em *foo*][variable-2 bar]", - "[variable-2 2. ][variable-2&strong **foo**][variable-2 bar]", - "[variable-2 3. ][variable-2&strong **][variable-2&em&strong *foo**][variable-2&em *][variable-2 bar]", - "[variable-2 4. ][variable-2&comment `foo`][variable-2 bar]"); - - // Paragraph lists - MT("listParagraph", - "[variable-2 * foo]", - "", - "[variable-2 * bar]"); - - // Multi-paragraph lists - // - // 4 spaces - MT("listMultiParagraph", - "[variable-2 * foo]", - "", - "[variable-2 * bar]", - "", - " [variable-2 hello]"); - - // 4 spaces, extra blank lines (should still be list, per Dingus) - MT("listMultiParagraphExtra", - "[variable-2 * foo]", - "", - "[variable-2 * bar]", - "", - "", - " [variable-2 hello]"); - - // 4 spaces, plus 1 space (should still be list, per Dingus) - MT("listMultiParagraphExtraSpace", - "[variable-2 * foo]", - "", - "[variable-2 * bar]", - "", - " [variable-2 hello]", - "", - " [variable-2 world]"); - - // 1 tab - MT("listTab", - "[variable-2 * foo]", - "", - "[variable-2 * bar]", - "", - "\t[variable-2 hello]"); - - // No indent - MT("listNoIndent", - "[variable-2 * foo]", - "", - "[variable-2 * bar]", - "", - "hello"); - - // Blockquote - MT("blockquote", - "[variable-2 * foo]", - "", - "[variable-2 * bar]", - "", - " [variable-2&atom > hello]"); - - // Code block - MT("blockquoteCode", - "[variable-2 * foo]", - "", - "[variable-2 * bar]", - "", - " [comment > hello]", - "", - " [variable-2 world]"); - - // Code block followed by text - MT("blockquoteCodeText", - "[variable-2 * foo]", - "", - " [variable-2 bar]", - "", - " [comment hello]", - "", - " [variable-2 world]"); - - // Nested list - - MT("listAsteriskNested", - "[variable-2 * foo]", - "", - " [variable-3 * bar]"); - - MT("listPlusNested", - "[variable-2 + foo]", - "", - " [variable-3 + bar]"); - - MT("listDashNested", - "[variable-2 - foo]", - "", - " [variable-3 - bar]"); - - MT("listNumberNested", - "[variable-2 1. foo]", - "", - " [variable-3 2. bar]"); - - MT("listMixed", - "[variable-2 * foo]", - "", - " [variable-3 + bar]", - "", - " [keyword - hello]", - "", - " [variable-2 1. world]"); - - MT("listBlockquote", - "[variable-2 * foo]", - "", - " [variable-3 + bar]", - "", - " [atom&variable-3 > hello]"); - - MT("listCode", - "[variable-2 * foo]", - "", - " [variable-3 + bar]", - "", - " [comment hello]"); - - // Code with internal indentation - MT("listCodeIndentation", - "[variable-2 * foo]", - "", - " [comment bar]", - " [comment hello]", - " [comment world]", - " [comment foo]", - " [variable-2 bar]"); - - // List nesting edge cases - MT("listNested", - "[variable-2 * foo]", - "", - " [variable-3 * bar]", - "", - " [variable-2 hello]" - ); - MT("listNested", - "[variable-2 * foo]", - "", - " [variable-3 * bar]", - "", - " [variable-3 * foo]" - ); - - // Code followed by text - MT("listCodeText", - "[variable-2 * foo]", - "", - " [comment bar]", - "", - "hello"); - - // Following tests directly from official Markdown documentation - // http://daringfireball.net/projects/markdown/syntax#hr - - MT("hrSpace", - "[hr * * *]"); - - MT("hr", - "[hr ***]"); - - MT("hrLong", - "[hr *****]"); - - MT("hrSpaceDash", - "[hr - - -]"); - - MT("hrDashLong", - "[hr ---------------------------------------]"); - - // Inline link with title - MT("linkTitle", - "[link [[foo]]][string (http://example.com/ \"bar\")] hello"); - - // Inline link without title - MT("linkNoTitle", - "[link [[foo]]][string (http://example.com/)] bar"); - - // Inline link with image - MT("linkImage", - "[link [[][tag ![[foo]]][string (http://example.com/)][link ]]][string (http://example.com/)] bar"); - - // Inline link with Em - MT("linkEm", - "[link [[][link&em *foo*][link ]]][string (http://example.com/)] bar"); - - // Inline link with Strong - MT("linkStrong", - "[link [[][link&strong **foo**][link ]]][string (http://example.com/)] bar"); - - // Inline link with EmStrong - MT("linkEmStrong", - "[link [[][link&strong **][link&em&strong *foo**][link&em *][link ]]][string (http://example.com/)] bar"); - - // Image with title - MT("imageTitle", - "[tag ![[foo]]][string (http://example.com/ \"bar\")] hello"); - - // Image without title - MT("imageNoTitle", - "[tag ![[foo]]][string (http://example.com/)] bar"); - - // Image with asterisks - MT("imageAsterisks", - "[tag ![[*foo*]]][string (http://example.com/)] bar"); - - // Not a link. Should be normal text due to square brackets being used - // regularly in text, especially in quoted material, and no space is allowed - // between square brackets and parentheses (per Dingus). - MT("notALink", - "[[foo]] (bar)"); - - // Reference-style links - MT("linkReference", - "[link [[foo]]][string [[bar]]] hello"); - - // Reference-style links with Em - MT("linkReferenceEm", - "[link [[][link&em *foo*][link ]]][string [[bar]]] hello"); - - // Reference-style links with Strong - MT("linkReferenceStrong", - "[link [[][link&strong **foo**][link ]]][string [[bar]]] hello"); - - // Reference-style links with EmStrong - MT("linkReferenceEmStrong", - "[link [[][link&strong **][link&em&strong *foo**][link&em *][link ]]][string [[bar]]] hello"); - - // Reference-style links with optional space separator (per docuentation) - // "You can optionally use a space to separate the sets of brackets" - MT("linkReferenceSpace", - "[link [[foo]]] [string [[bar]]] hello"); - - // Should only allow a single space ("...use *a* space...") - MT("linkReferenceDoubleSpace", - "[[foo]] [[bar]] hello"); - - // Reference-style links with implicit link name - MT("linkImplicit", - "[link [[foo]]][string [[]]] hello"); - - // @todo It would be nice if, at some point, the document was actually - // checked to see if the referenced link exists - - // Link label, for reference-style links (taken from documentation) - - MT("labelNoTitle", - "[link [[foo]]:] [string http://example.com/]"); - - MT("labelIndented", - " [link [[foo]]:] [string http://example.com/]"); - - MT("labelSpaceTitle", - "[link [[foo bar]]:] [string http://example.com/ \"hello\"]"); - - MT("labelDoubleTitle", - "[link [[foo bar]]:] [string http://example.com/ \"hello\"] \"world\""); - - MT("labelTitleDoubleQuotes", - "[link [[foo]]:] [string http://example.com/ \"bar\"]"); - - MT("labelTitleSingleQuotes", - "[link [[foo]]:] [string http://example.com/ 'bar']"); - - MT("labelTitleParenthese", - "[link [[foo]]:] [string http://example.com/ (bar)]"); - - MT("labelTitleInvalid", - "[link [[foo]]:] [string http://example.com/] bar"); - - MT("labelLinkAngleBrackets", - "[link [[foo]]:] [string \"bar\"]"); - - MT("labelTitleNextDoubleQuotes", - "[link [[foo]]:] [string http://example.com/]", - "[string \"bar\"] hello"); - - MT("labelTitleNextSingleQuotes", - "[link [[foo]]:] [string http://example.com/]", - "[string 'bar'] hello"); - - MT("labelTitleNextParenthese", - "[link [[foo]]:] [string http://example.com/]", - "[string (bar)] hello"); - - MT("labelTitleNextMixed", - "[link [[foo]]:] [string http://example.com/]", - "(bar\" hello"); - - MT("linkWeb", - "[link ] foo"); - - MT("linkWebDouble", - "[link ] foo [link ]"); - - MT("linkEmail", - "[link ] foo"); - - MT("linkEmailDouble", - "[link ] foo [link ]"); - - MT("emAsterisk", - "[em *foo*] bar"); - - MT("emUnderscore", - "[em _foo_] bar"); - - MT("emInWordAsterisk", - "foo[em *bar*]hello"); - - MT("emInWordUnderscore", - "foo[em _bar_]hello"); - - // Per documentation: "...surround an * or _ with spaces, it’ll be - // treated as a literal asterisk or underscore." - - MT("emEscapedBySpaceIn", - "foo [em _bar _ hello_] world"); - - MT("emEscapedBySpaceOut", - "foo _ bar[em _hello_]world"); - - // Unclosed emphasis characters - // Instead of simply marking as EM / STRONG, it would be nice to have an - // incomplete flag for EM and STRONG, that is styled slightly different. - MT("emIncompleteAsterisk", - "foo [em *bar]"); - - MT("emIncompleteUnderscore", - "foo [em _bar]"); - - MT("strongAsterisk", - "[strong **foo**] bar"); - - MT("strongUnderscore", - "[strong __foo__] bar"); - - MT("emStrongAsterisk", - "[em *foo][em&strong **bar*][strong hello**] world"); - - MT("emStrongUnderscore", - "[em _foo][em&strong __bar_][strong hello__] world"); - - // "...same character must be used to open and close an emphasis span."" - MT("emStrongMixed", - "[em _foo][em&strong **bar*hello__ world]"); - - MT("emStrongMixed", - "[em *foo][em&strong __bar_hello** world]"); - - // These characters should be escaped: - // \ backslash - // ` backtick - // * asterisk - // _ underscore - // {} curly braces - // [] square brackets - // () parentheses - // # hash mark - // + plus sign - // - minus sign (hyphen) - // . dot - // ! exclamation mark - - MT("escapeBacktick", - "foo \\`bar\\`"); - - MT("doubleEscapeBacktick", - "foo \\\\[comment `bar\\\\`]"); - - MT("escapeAsterisk", - "foo \\*bar\\*"); - - MT("doubleEscapeAsterisk", - "foo \\\\[em *bar\\\\*]"); - - MT("escapeUnderscore", - "foo \\_bar\\_"); - - MT("doubleEscapeUnderscore", - "foo \\\\[em _bar\\\\_]"); - - MT("escapeHash", - "\\# foo"); - - MT("doubleEscapeHash", - "\\\\# foo"); - - - // Tests to make sure GFM-specific things aren't getting through - - MT("taskList", - "[variable-2 * [ ]] bar]"); - - MT("fencedCodeBlocks", - "[comment ```]", - "foo", - "[comment ```]"); -})(); diff --git a/applications/admin/static/codemirror/mode/meta.js b/applications/admin/static/codemirror/mode/meta.js deleted file mode 100644 index 142d9595..00000000 --- a/applications/admin/static/codemirror/mode/meta.js +++ /dev/null @@ -1,106 +0,0 @@ -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../lib/codemirror")); - else if (typeof define == "function" && define.amd) // AMD - define(["../lib/codemirror"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - -CodeMirror.modeInfo = [ - {name: 'APL', mime: 'text/apl', mode: 'apl'}, - {name: 'Asterisk', mime: 'text/x-asterisk', mode: 'asterisk'}, - {name: 'C', mime: 'text/x-csrc', mode: 'clike'}, - {name: 'C++', mime: 'text/x-c++src', mode: 'clike'}, - {name: 'Cobol', mime: 'text/x-cobol', mode: 'cobol'}, - {name: 'Java', mime: 'text/x-java', mode: 'clike'}, - {name: 'C#', mime: 'text/x-csharp', mode: 'clike'}, - {name: 'Scala', mime: 'text/x-scala', mode: 'clike'}, - {name: 'Clojure', mime: 'text/x-clojure', mode: 'clojure'}, - {name: 'CoffeeScript', mime: 'text/x-coffeescript', mode: 'coffeescript'}, - {name: 'Common Lisp', mime: 'text/x-common-lisp', mode: 'commonlisp'}, - {name: 'CSS', mime: 'text/css', mode: 'css'}, - {name: 'D', mime: 'text/x-d', mode: 'd'}, - {name: 'diff', mime: 'text/x-diff', mode: 'diff'}, - {name: 'DTD', mime: 'application/xml-dtd', mode: 'dtd'}, - {name: 'ECL', mime: 'text/x-ecl', mode: 'ecl'}, - {name: 'Eiffel', mime: 'text/x-eiffel', mode: 'eiffel'}, - {name: 'Erlang', mime: 'text/x-erlang', mode: 'erlang'}, - {name: 'Fortran', mime: 'text/x-fortran', mode: 'fortran'}, - {name: 'F#', mime: 'text/x-fsharp', mode: 'mllike'}, - {name: 'Gas', mime: 'text/x-gas', mode: 'gas'}, - {name: 'Gherkin', mime: 'text/x-feature', mode: 'gherkin'}, - {name: 'GitHub Flavored Markdown', mime: 'text/x-gfm', mode: 'gfm'}, - {name: 'Go', mime: 'text/x-go', mode: 'go'}, - {name: 'Groovy', mime: 'text/x-groovy', mode: 'groovy'}, - {name: 'HAML', mime: 'text/x-haml', mode: 'haml'}, - {name: 'Haskell', mime: 'text/x-haskell', mode: 'haskell'}, - {name: 'Haxe', mime: 'text/x-haxe', mode: 'haxe'}, - {name: 'ASP.NET', mime: 'application/x-aspx', mode: 'htmlembedded'}, - {name: 'Embedded Javascript', mime: 'application/x-ejs', mode: 'htmlembedded'}, - {name: 'JavaServer Pages', mime: 'application/x-jsp', mode: 'htmlembedded'}, - {name: 'HTML', mime: 'text/html', mode: 'htmlmixed'}, - {name: 'HTTP', mime: 'message/http', mode: 'http'}, - {name: 'Jade', mime: 'text/x-jade', mode: 'jade'}, - {name: 'JavaScript', mime: 'text/javascript', mode: 'javascript'}, - {name: 'JSON', mime: 'application/x-json', mode: 'javascript'}, - {name: 'JSON', mime: 'application/json', mode: 'javascript'}, - {name: 'JSON-LD', mime: 'application/ld+json', mode: 'javascript'}, - {name: 'TypeScript', mime: 'application/typescript', mode: 'javascript'}, - {name: 'Jinja2', mime: null, mode: 'jinja2'}, - {name: 'Julia', mime: 'text/x-julia', mode: 'julia'}, - {name: 'LESS', mime: 'text/x-less', mode: 'css'}, - {name: 'LiveScript', mime: 'text/x-livescript', mode: 'livescript'}, - {name: 'Lua', mime: 'text/x-lua', mode: 'lua'}, - {name: 'Markdown (GitHub-flavour)', mime: 'text/x-markdown', mode: 'markdown'}, - {name: 'mIRC', mime: 'text/mirc', mode: 'mirc'}, - {name: 'Nginx', mime: 'text/x-nginx-conf', mode: 'nginx'}, - {name: 'NTriples', mime: 'text/n-triples', mode: 'ntriples'}, - {name: 'OCaml', mime: 'text/x-ocaml', mode: 'mllike'}, - {name: 'Octave', mime: 'text/x-octave', mode: 'octave'}, - {name: 'Pascal', mime: 'text/x-pascal', mode: 'pascal'}, - {name: 'PEG.js', mime: null, mode: 'pegjs'}, - {name: 'Perl', mime: 'text/x-perl', mode: 'perl'}, - {name: 'PHP', mime: 'text/x-php', mode: 'php'}, - {name: 'PHP(HTML)', mime: 'application/x-httpd-php', mode: 'php'}, - {name: 'Pig', mime: 'text/x-pig', mode: 'pig'}, - {name: 'Plain Text', mime: 'text/plain', mode: 'null'}, - {name: 'Properties files', mime: 'text/x-properties', mode: 'properties'}, - {name: 'Python', mime: 'text/x-python', mode: 'python'}, - {name: 'Puppet', mime: 'text/x-puppet', mode: 'puppet'}, - {name: 'Cython', mime: 'text/x-cython', mode: 'python'}, - {name: 'R', mime: 'text/x-rsrc', mode: 'r'}, - {name: 'reStructuredText', mime: 'text/x-rst', mode: 'rst'}, - {name: 'Ruby', mime: 'text/x-ruby', mode: 'ruby'}, - {name: 'Rust', mime: 'text/x-rustsrc', mode: 'rust'}, - {name: 'Sass', mime: 'text/x-sass', mode: 'sass'}, - {name: 'Scheme', mime: 'text/x-scheme', mode: 'scheme'}, - {name: 'SCSS', mime: 'text/x-scss', mode: 'css'}, - {name: 'Shell', mime: 'text/x-sh', mode: 'shell'}, - {name: 'Sieve', mime: 'application/sieve', mode: 'sieve'}, - {name: 'Smalltalk', mime: 'text/x-stsrc', mode: 'smalltalk'}, - {name: 'Smarty', mime: 'text/x-smarty', mode: 'smarty'}, - {name: 'SmartyMixed', mime: 'text/x-smarty', mode: 'smartymixed'}, - {name: 'Solr', mime: 'text/x-solr', mode: 'solr'}, - {name: 'SPARQL', mime: 'application/x-sparql-query', mode: 'sparql'}, - {name: 'SQL', mime: 'text/x-sql', mode: 'sql'}, - {name: 'MariaDB', mime: 'text/x-mariadb', mode: 'sql'}, - {name: 'sTeX', mime: 'text/x-stex', mode: 'stex'}, - {name: 'LaTeX', mime: 'text/x-latex', mode: 'stex'}, - {name: 'Tcl', mime: 'text/x-tcl', mode: 'tcl'}, - {name: 'TiddlyWiki ', mime: 'text/x-tiddlywiki', mode: 'tiddlywiki'}, - {name: 'Tiki wiki', mime: 'text/tiki', mode: 'tiki'}, - {name: 'TOML', mime: 'text/x-toml', mode: 'toml'}, - {name: 'Turtle', mime: 'text/turtle', mode: 'turtle'}, - {name: 'VB.NET', mime: 'text/x-vb', mode: 'vb'}, - {name: 'VBScript', mime: 'text/vbscript', mode: 'vbscript'}, - {name: 'Velocity', mime: 'text/velocity', mode: 'velocity'}, - {name: 'Verilog', mime: 'text/x-verilog', mode: 'verilog'}, - {name: 'XML', mime: 'application/xml', mode: 'xml'}, - {name: 'XQuery', mime: 'application/xquery', mode: 'xquery'}, - {name: 'YAML', mime: 'text/x-yaml', mode: 'yaml'}, - {name: 'Z80', mime: 'text/x-z80', mode: 'z80'} -]; - -}); diff --git a/applications/admin/static/codemirror/mode/properties/index.html b/applications/admin/static/codemirror/mode/properties/index.html deleted file mode 100644 index 40ee1a37..00000000 --- a/applications/admin/static/codemirror/mode/properties/index.html +++ /dev/null @@ -1,53 +0,0 @@ - - -CodeMirror: Properties files mode - - - - - - - - - -
-

Properties files mode

-
- - -

MIME types defined: text/x-properties, - text/x-ini.

- -
diff --git a/applications/admin/static/codemirror/mode/properties/properties.js b/applications/admin/static/codemirror/mode/properties/properties.js deleted file mode 100644 index d3a13c76..00000000 --- a/applications/admin/static/codemirror/mode/properties/properties.js +++ /dev/null @@ -1,63 +0,0 @@ -CodeMirror.defineMode("properties", function() { - return { - token: function(stream, state) { - var sol = stream.sol() || state.afterSection; - var eol = stream.eol(); - - state.afterSection = false; - - if (sol) { - if (state.nextMultiline) { - state.inMultiline = true; - state.nextMultiline = false; - } else { - state.position = "def"; - } - } - - if (eol && ! state.nextMultiline) { - state.inMultiline = false; - state.position = "def"; - } - - if (sol) { - while(stream.eatSpace()); - } - - var ch = stream.next(); - - if (sol && (ch === "#" || ch === "!" || ch === ";")) { - state.position = "comment"; - stream.skipToEnd(); - return "comment"; - } else if (sol && ch === "[") { - state.afterSection = true; - stream.skipTo("]"); stream.eat("]"); - return "header"; - } else if (ch === "=" || ch === ":") { - state.position = "quote"; - return null; - } else if (ch === "\\" && state.position === "quote") { - if (stream.next() !== "u") { // u = Unicode sequence \u1234 - // Multiline value - state.nextMultiline = true; - } - } - - return state.position; - }, - - startState: function() { - return { - position : "def", // Current position, "def", "quote" or "comment" - nextMultiline : false, // Is the next line multiline value - inMultiline : false, // Is the current line a multiline value - afterSection : false // Did we just open a section - }; - } - - }; -}); - -CodeMirror.defineMIME("text/x-properties", "properties"); -CodeMirror.defineMIME("text/x-ini", "properties"); diff --git a/applications/admin/static/codemirror/mode/python/python.js b/applications/admin/static/codemirror/mode/python/python.js index f5530bcf..98c0409a 100644 --- a/applications/admin/static/codemirror/mode/python/python.js +++ b/applications/admin/static/codemirror/mode/python/python.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -6,383 +9,351 @@ else // Plain browser env mod(CodeMirror); })(function(CodeMirror) { -"use strict"; + "use strict"; + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b"); + } -CodeMirror.defineMode("python", function(conf, parserConf) { - var ERRORCLASS = 'error'; + var wordOperators = wordRegexp(["and", "or", "not", "is"]); + var commonKeywords = ["as", "assert", "break", "class", "continue", + "def", "del", "elif", "else", "except", "finally", + "for", "from", "global", "if", "import", + "lambda", "pass", "raise", "return", + "try", "while", "with", "yield", "in"]; + var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr", + "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod", + "enumerate", "eval", "filter", "float", "format", "frozenset", + "getattr", "globals", "hasattr", "hash", "help", "hex", "id", + "input", "int", "isinstance", "issubclass", "iter", "len", + "list", "locals", "map", "max", "memoryview", "min", "next", + "object", "oct", "open", "ord", "pow", "property", "range", + "repr", "reversed", "round", "set", "setattr", "slice", + "sorted", "staticmethod", "str", "sum", "super", "tuple", + "type", "vars", "zip", "__import__", "NotImplemented", + "Ellipsis", "__debug__"]; + var py2 = {builtins: ["apply", "basestring", "buffer", "cmp", "coerce", "execfile", + "file", "intern", "long", "raw_input", "reduce", "reload", + "unichr", "unicode", "xrange", "False", "True", "None"], + keywords: ["exec", "print"]}; + var py3 = {builtins: ["ascii", "bytes", "exec", "print"], + keywords: ["nonlocal", "False", "True", "None"]}; - function wordRegexp(words) { - return new RegExp("^((" + words.join(")|(") + "))\\b"); - } + CodeMirror.registerHelper("hintWords", "python", commonKeywords.concat(commonBuiltins)); - var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!]"); - var singleDelimiters = parserConf.singleDelimiters || new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]'); + function top(state) { + return state.scopes[state.scopes.length - 1]; + } + + CodeMirror.defineMode("python", function(conf, parserConf) { + var ERRORCLASS = "error"; + + var singleDelimiters = parserConf.singleDelimiters || new RegExp("^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]"); var doubleOperators = parserConf.doubleOperators || new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))"); var doubleDelimiters = parserConf.doubleDelimiters || new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); var tripleDelimiters = parserConf.tripleDelimiters || new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))"); - var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); - var hangingIndent = parserConf.hangingIndent || parserConf.indentUnit; - var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']); - var commonkeywords = ['as', 'assert', 'break', 'class', 'continue', - 'def', 'del', 'elif', 'else', 'except', 'finally', - 'for', 'from', 'global', 'if', 'import', - 'lambda', 'pass', 'raise', 'return', - 'try', 'while', 'with', 'yield']; - var commonBuiltins = ['abs', 'all', 'any', 'bin', 'bool', 'bytearray', 'callable', 'chr', - 'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod', - 'enumerate', 'eval', 'filter', 'float', 'format', 'frozenset', - 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', - 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', - 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', - 'object', 'oct', 'open', 'ord', 'pow', 'property', 'range', - 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', - 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', - 'type', 'vars', 'zip', '__import__', 'NotImplemented', - 'Ellipsis', '__debug__']; - var py2 = {'builtins': ['apply', 'basestring', 'buffer', 'cmp', 'coerce', 'execfile', - 'file', 'intern', 'long', 'raw_input', 'reduce', 'reload', - 'unichr', 'unicode', 'xrange', 'False', 'True', 'None'], - 'keywords': ['exec', 'print']}; - var py3 = {'builtins': ['ascii', 'bytes', 'exec', 'print'], - 'keywords': ['nonlocal', 'False', 'True', 'None']}; + if (parserConf.version && parseInt(parserConf.version, 10) == 3){ + // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator + var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!@]"); + var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*"); + } else { + var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!]"); + var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); + } + var hangingIndent = parserConf.hangingIndent || conf.indentUnit; + + var myKeywords = commonKeywords, myBuiltins = commonBuiltins; if(parserConf.extra_keywords != undefined){ - commonkeywords = commonkeywords.concat(parserConf.extra_keywords); + myKeywords = myKeywords.concat(parserConf.extra_keywords); } if(parserConf.extra_builtins != undefined){ - commonBuiltins = commonBuiltins.concat(parserConf.extra_builtins); + myBuiltins = myBuiltins.concat(parserConf.extra_builtins); } - if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) { - commonkeywords = commonkeywords.concat(py3.keywords); - commonBuiltins = commonBuiltins.concat(py3.builtins); - var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i"); + if (parserConf.version && parseInt(parserConf.version, 10) == 3) { + myKeywords = myKeywords.concat(py3.keywords); + myBuiltins = myBuiltins.concat(py3.builtins); + var stringPrefixes = new RegExp("^(([rb]|(br))?('{3}|\"{3}|['\"]))", "i"); } else { - commonkeywords = commonkeywords.concat(py2.keywords); - commonBuiltins = commonBuiltins.concat(py2.builtins); - var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i"); + myKeywords = myKeywords.concat(py2.keywords); + myBuiltins = myBuiltins.concat(py2.builtins); + var stringPrefixes = new RegExp("^(([rub]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i"); } - var keywords = wordRegexp(commonkeywords); - var builtins = wordRegexp(commonBuiltins); - - var indentInfo = null; + var keywords = wordRegexp(myKeywords); + var builtins = wordRegexp(myBuiltins); // tokenizers function tokenBase(stream, state) { - // Handle scope changes - if (stream.sol()) { - var scopeOffset = state.scopes[0].offset; - if (stream.eatSpace()) { - var lineOffset = stream.indentation(); - if (lineOffset > scopeOffset) { - indentInfo = 'indent'; - } else if (lineOffset < scopeOffset) { - indentInfo = 'dedent'; - } - return null; - } else { - if (scopeOffset > 0) { - dedent(stream, state); - } - } - } + // Handle scope changes + if (stream.sol() && top(state).type == "py") { + var scopeOffset = top(state).offset; if (stream.eatSpace()) { - return null; + var lineOffset = stream.indentation(); + if (lineOffset > scopeOffset) + pushScope(stream, state, "py"); + else if (lineOffset < scopeOffset && dedent(stream, state)) + state.errorToken = true; + return null; + } else { + var style = tokenBaseInner(stream, state); + if (scopeOffset > 0 && dedent(stream, state)) + style += " " + ERRORCLASS; + return style; } + } + return tokenBaseInner(stream, state); + } - var ch = stream.peek(); + function tokenBaseInner(stream, state) { + if (stream.eatSpace()) return null; - // Handle Comments - if (ch === '#') { - stream.skipToEnd(); - return 'comment'; - } + var ch = stream.peek(); - // Handle Number Literals - if (stream.match(/^[0-9\.]/, false)) { - var floatLiteral = false; - // Floats - if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; } - if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; } - if (stream.match(/^\.\d+/)) { floatLiteral = true; } - if (floatLiteral) { - // Float literals may be "imaginary" - stream.eat(/J/i); - return 'number'; - } - // Integers - var intLiteral = false; - // Hex - if (stream.match(/^0x[0-9a-f]+/i)) { intLiteral = true; } - // Binary - if (stream.match(/^0b[01]+/i)) { intLiteral = true; } - // Octal - if (stream.match(/^0o[0-7]+/i)) { intLiteral = true; } - // Decimal - if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) { - // Decimal literals may be "imaginary" - stream.eat(/J/i); - // TODO - Can you have imaginary longs? - intLiteral = true; - } - // Zero by itself with no other piece of number. - if (stream.match(/^0(?![\dx])/i)) { intLiteral = true; } - if (intLiteral) { - // Integer literals may be "long" - stream.eat(/L/i); - return 'number'; - } - } + // Handle Comments + if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } - // Handle Strings - if (stream.match(stringPrefixes)) { - state.tokenize = tokenStringFactory(stream.current()); - return state.tokenize(stream, state); + // Handle Number Literals + if (stream.match(/^[0-9\.]/, false)) { + var floatLiteral = false; + // Floats + if (stream.match(/^\d*\.\d+(e[\+\-]?\d+)?/i)) { floatLiteral = true; } + if (stream.match(/^\d+\.\d*/)) { floatLiteral = true; } + if (stream.match(/^\.\d+/)) { floatLiteral = true; } + if (floatLiteral) { + // Float literals may be "imaginary" + stream.eat(/J/i); + return "number"; } + // Integers + var intLiteral = false; + // Hex + if (stream.match(/^0x[0-9a-f]+/i)) intLiteral = true; + // Binary + if (stream.match(/^0b[01]+/i)) intLiteral = true; + // Octal + if (stream.match(/^0o[0-7]+/i)) intLiteral = true; + // Decimal + if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) { + // Decimal literals may be "imaginary" + stream.eat(/J/i); + // TODO - Can you have imaginary longs? + intLiteral = true; + } + // Zero by itself with no other piece of number. + if (stream.match(/^0(?![\dx])/i)) intLiteral = true; + if (intLiteral) { + // Integer literals may be "long" + stream.eat(/L/i); + return "number"; + } + } - // Handle operators and Delimiters - if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { - return null; - } - if (stream.match(doubleOperators) - || stream.match(singleOperators) - || stream.match(wordOperators)) { - return 'operator'; - } - if (stream.match(singleDelimiters)) { - return null; - } + // Handle Strings + if (stream.match(stringPrefixes)) { + state.tokenize = tokenStringFactory(stream.current()); + return state.tokenize(stream, state); + } - if (stream.match(keywords)) { - return 'keyword'; - } + // Handle operators and Delimiters + if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) + return null; - if (stream.match(builtins)) { - return 'builtin'; - } + if (stream.match(doubleOperators) + || stream.match(singleOperators) + || stream.match(wordOperators)) + return "operator"; - if (stream.match(/^(self|cls)\b/)) { - return "variable-2"; - } + if (stream.match(singleDelimiters)) + return null; - if (stream.match(identifiers)) { - if (state.lastToken == 'def' || state.lastToken == 'class') { - return 'def'; - } - return 'variable'; - } + if (stream.match(keywords)) + return "keyword"; - // Handle non-detected items - stream.next(); - return ERRORCLASS; + if (stream.match(builtins)) + return "builtin"; + + if (stream.match(/^(self|cls)\b/)) + return "variable-2"; + + if (stream.match(identifiers)) { + if (state.lastToken == "def" || state.lastToken == "class") + return "def"; + return "variable"; + } + + // Handle non-detected items + stream.next(); + return ERRORCLASS; } function tokenStringFactory(delimiter) { - while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) { - delimiter = delimiter.substr(1); - } - var singleline = delimiter.length == 1; - var OUTCLASS = 'string'; + while ("rub".indexOf(delimiter.charAt(0).toLowerCase()) >= 0) + delimiter = delimiter.substr(1); - function tokenString(stream, state) { - while (!stream.eol()) { - stream.eatWhile(/[^'"\\]/); - if (stream.eat('\\')) { - stream.next(); - if (singleline && stream.eol()) { - return OUTCLASS; - } - } else if (stream.match(delimiter)) { - state.tokenize = tokenBase; - return OUTCLASS; - } else { - stream.eat(/['"]/); - } - } - if (singleline) { - if (parserConf.singleLineStringErrors) { - return ERRORCLASS; - } else { - state.tokenize = tokenBase; - } - } + var singleline = delimiter.length == 1; + var OUTCLASS = "string"; + + function tokenString(stream, state) { + while (!stream.eol()) { + stream.eatWhile(/[^'"\\]/); + if (stream.eat("\\")) { + stream.next(); + if (singleline && stream.eol()) + return OUTCLASS; + } else if (stream.match(delimiter)) { + state.tokenize = tokenBase; return OUTCLASS; + } else { + stream.eat(/['"]/); + } } - tokenString.isString = true; - return tokenString; + if (singleline) { + if (parserConf.singleLineStringErrors) + return ERRORCLASS; + else + state.tokenize = tokenBase; + } + return OUTCLASS; + } + tokenString.isString = true; + return tokenString; } - function indent(stream, state, type) { - type = type || 'py'; - var indentUnit = 0; - if (type === 'py') { - if (state.scopes[0].type !== 'py') { - state.scopes[0].offset = stream.indentation(); - return; - } - for (var i = 0; i < state.scopes.length; ++i) { - if (state.scopes[i].type === 'py') { - indentUnit = state.scopes[i].offset + conf.indentUnit; - break; - } - } - } else if (stream.match(/\s*($|#)/, false)) { - // An open paren/bracket/brace with only space or comments after it - // on the line will indent the next line a fixed amount, to make it - // easier to put arguments, list items, etc. on their own lines. - indentUnit = stream.indentation() + hangingIndent; - } else { - indentUnit = stream.column() + stream.current().length; - } - state.scopes.unshift({ - offset: indentUnit, - type: type - }); + function pushScope(stream, state, type) { + var offset = 0, align = null; + if (type == "py") { + while (top(state).type != "py") + state.scopes.pop(); + } + offset = top(state).offset + (type == "py" ? conf.indentUnit : hangingIndent); + if (type != "py" && !stream.match(/^(\s|#.*)*$/, false)) + align = stream.column() + 1; + state.scopes.push({offset: offset, type: type, align: align}); } - function dedent(stream, state, type) { - type = type || 'py'; - if (state.scopes.length == 1) return; - if (state.scopes[0].type === 'py') { - var _indent = stream.indentation(); - var _indent_index = -1; - for (var i = 0; i < state.scopes.length; ++i) { - if (_indent === state.scopes[i].offset) { - _indent_index = i; - break; - } - } - if (_indent_index === -1) { - return true; - } - while (state.scopes[0].offset !== _indent) { - state.scopes.shift(); - } - return false; - } else { - if (type === 'py') { - state.scopes[0].offset = stream.indentation(); - return false; - } else { - if (state.scopes[0].type != type) { - return true; - } - state.scopes.shift(); - return false; - } - } + function dedent(stream, state) { + var indented = stream.indentation(); + while (top(state).offset > indented) { + if (top(state).type != "py") return true; + state.scopes.pop(); + } + return top(state).offset != indented; } function tokenLexer(stream, state) { - indentInfo = null; - var style = state.tokenize(stream, state); - var current = stream.current(); + var style = state.tokenize(stream, state); + var current = stream.current(); - // Handle '.' connected identifiers - if (current === '.') { - style = stream.match(identifiers, false) ? null : ERRORCLASS; - if (style === null && state.lastStyle === 'meta') { - // Apply 'meta' style to '.' connected identifiers when - // appropriate. - style = 'meta'; - } - return style; + // Handle '.' connected identifiers + if (current == ".") { + style = stream.match(identifiers, false) ? null : ERRORCLASS; + if (style == null && state.lastStyle == "meta") { + // Apply 'meta' style to '.' connected identifiers when + // appropriate. + style = "meta"; } - - // Handle decorators - if (current === '@') { - return stream.match(identifiers, false) ? 'meta' : ERRORCLASS; - } - - if ((style === 'variable' || style === 'builtin') - && state.lastStyle === 'meta') { - style = 'meta'; - } - - // Handle scope changes. - if (current === 'pass' || current === 'return') { - state.dedent += 1; - } - if (current === 'lambda') state.lambda = true; - if ((current === ':' && !state.lambda && state.scopes[0].type == 'py') - || indentInfo === 'indent') { - indent(stream, state); - } - var delimiter_index = '[({'.indexOf(current); - if (delimiter_index !== -1) { - indent(stream, state, '])}'.slice(delimiter_index, delimiter_index+1)); - } - if (indentInfo === 'dedent') { - if (dedent(stream, state)) { - return ERRORCLASS; - } - } - delimiter_index = '])}'.indexOf(current); - if (delimiter_index !== -1) { - if (dedent(stream, state, current)) { - return ERRORCLASS; - } - } - if (state.dedent > 0 && stream.eol() && state.scopes[0].type == 'py') { - if (state.scopes.length > 1) state.scopes.shift(); - state.dedent -= 1; - } - return style; + } + + // Handle decorators + if (current == "@"){ + if(parserConf.version && parseInt(parserConf.version, 10) == 3){ + return stream.match(identifiers, false) ? "meta" : "operator"; + } else { + return stream.match(identifiers, false) ? "meta" : ERRORCLASS; + } + } + + if ((style == "variable" || style == "builtin") + && state.lastStyle == "meta") + style = "meta"; + + // Handle scope changes. + if (current == "pass" || current == "return") + state.dedent += 1; + + if (current == "lambda") state.lambda = true; + if (current == ":" && !state.lambda && top(state).type == "py") + pushScope(stream, state, "py"); + + var delimiter_index = current.length == 1 ? "[({".indexOf(current) : -1; + if (delimiter_index != -1) + pushScope(stream, state, "])}".slice(delimiter_index, delimiter_index+1)); + + delimiter_index = "])}".indexOf(current); + if (delimiter_index != -1) { + if (top(state).type == current) state.scopes.pop(); + else return ERRORCLASS; + } + if (state.dedent > 0 && stream.eol() && top(state).type == "py") { + if (state.scopes.length > 1) state.scopes.pop(); + state.dedent -= 1; + } + + return style; } var external = { - startState: function(basecolumn) { - return { - tokenize: tokenBase, - scopes: [{offset:basecolumn || 0, type:'py'}], - lastStyle: null, - lastToken: null, - lambda: false, - dedent: 0 - }; - }, + startState: function(basecolumn) { + return { + tokenize: tokenBase, + scopes: [{offset: basecolumn || 0, type: "py", align: null}], + lastStyle: null, + lastToken: null, + lambda: false, + dedent: 0 + }; + }, - token: function(stream, state) { - var style = tokenLexer(stream, state); + token: function(stream, state) { + var addErr = state.errorToken; + if (addErr) state.errorToken = false; + var style = tokenLexer(stream, state); - state.lastStyle = style; + state.lastStyle = style; - var current = stream.current(); - if (current && style) { - state.lastToken = current; - } + var current = stream.current(); + if (current && style) + state.lastToken = current; - if (stream.eol() && state.lambda) { - state.lambda = false; - } - return style; - }, + if (stream.eol() && state.lambda) + state.lambda = false; + return addErr ? style + " " + ERRORCLASS : style; + }, - indent: function(state) { - if (state.tokenize != tokenBase) { - return state.tokenize.isString ? CodeMirror.Pass : 0; - } + indent: function(state, textAfter) { + if (state.tokenize != tokenBase) + return state.tokenize.isString ? CodeMirror.Pass : 0; - return state.scopes[0].offset; - }, + var scope = top(state); + var closing = textAfter && textAfter.charAt(0) == scope.type; + if (scope.align != null) + return scope.align - (closing ? 1 : 0); + else if (closing && state.scopes.length > 1) + return state.scopes[state.scopes.length - 2].offset; + else + return scope.offset; + }, - lineComment: "#", - fold: "indent" + lineComment: "#", + fold: "indent" }; return external; -}); + }); -CodeMirror.defineMIME("text/x-python", "python"); + CodeMirror.defineMIME("text/x-python", "python"); -var words = function(str){return str.split(' ');}; + var words = function(str) { return str.split(" "); }; -CodeMirror.defineMIME("text/x-cython", { - name: "python", - extra_keywords: words("by cdef cimport cpdef ctypedef enum except"+ - "extern gil include nogil property public"+ - "readonly struct union DEF IF ELIF ELSE") -}); + CodeMirror.defineMIME("text/x-cython", { + name: "python", + extra_keywords: words("by cdef cimport cpdef ctypedef enum except"+ + "extern gil include nogil property public"+ + "readonly struct union DEF IF ELIF ELSE") + }); }); diff --git a/applications/admin/static/codemirror/mode/r/LICENSE b/applications/admin/static/codemirror/mode/r/LICENSE deleted file mode 100644 index 2510ae16..00000000 --- a/applications/admin/static/codemirror/mode/r/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2011, Ubalo, Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the Ubalo, Inc nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL UBALO, INC BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/applications/admin/static/codemirror/mode/r/index.html b/applications/admin/static/codemirror/mode/r/index.html deleted file mode 100644 index f73e13d6..00000000 --- a/applications/admin/static/codemirror/mode/r/index.html +++ /dev/null @@ -1,86 +0,0 @@ - - -CodeMirror: R mode - - - - - - - - - -
-

R mode

-
- - -

MIME types defined: text/x-rsrc.

- -

Development of the CodeMirror R mode was kindly sponsored - by Ubalo, who hold - the license.

- -
diff --git a/applications/admin/static/codemirror/mode/r/r.js b/applications/admin/static/codemirror/mode/r/r.js deleted file mode 100644 index 6410efbb..00000000 --- a/applications/admin/static/codemirror/mode/r/r.js +++ /dev/null @@ -1,141 +0,0 @@ -CodeMirror.defineMode("r", function(config) { - function wordObj(str) { - var words = str.split(" "), res = {}; - for (var i = 0; i < words.length; ++i) res[words[i]] = true; - return res; - } - var atoms = wordObj("NULL NA Inf NaN NA_integer_ NA_real_ NA_complex_ NA_character_"); - var builtins = wordObj("list quote bquote eval return call parse deparse"); - var keywords = wordObj("if else repeat while function for in next break"); - var blockkeywords = wordObj("if else repeat while function for"); - var opChars = /[+\-*\/^<>=!&|~$:]/; - var curPunc; - - function tokenBase(stream, state) { - curPunc = null; - var ch = stream.next(); - if (ch == "#") { - stream.skipToEnd(); - return "comment"; - } else if (ch == "0" && stream.eat("x")) { - stream.eatWhile(/[\da-f]/i); - return "number"; - } else if (ch == "." && stream.eat(/\d/)) { - stream.match(/\d*(?:e[+\-]?\d+)?/); - return "number"; - } else if (/\d/.test(ch)) { - stream.match(/\d*(?:\.\d+)?(?:e[+\-]\d+)?L?/); - return "number"; - } else if (ch == "'" || ch == '"') { - state.tokenize = tokenString(ch); - return "string"; - } else if (ch == "." && stream.match(/.[.\d]+/)) { - return "keyword"; - } else if (/[\w\.]/.test(ch) && ch != "_") { - stream.eatWhile(/[\w\.]/); - var word = stream.current(); - if (atoms.propertyIsEnumerable(word)) return "atom"; - if (keywords.propertyIsEnumerable(word)) { - if (blockkeywords.propertyIsEnumerable(word)) curPunc = "block"; - return "keyword"; - } - if (builtins.propertyIsEnumerable(word)) return "builtin"; - return "variable"; - } else if (ch == "%") { - if (stream.skipTo("%")) stream.next(); - return "variable-2"; - } else if (ch == "<" && stream.eat("-")) { - return "arrow"; - } else if (ch == "=" && state.ctx.argList) { - return "arg-is"; - } else if (opChars.test(ch)) { - if (ch == "$") return "dollar"; - stream.eatWhile(opChars); - return "operator"; - } else if (/[\(\){}\[\];]/.test(ch)) { - curPunc = ch; - if (ch == ";") return "semi"; - return null; - } else { - return null; - } - } - - function tokenString(quote) { - return function(stream, state) { - if (stream.eat("\\")) { - var ch = stream.next(); - if (ch == "x") stream.match(/^[a-f0-9]{2}/i); - else if ((ch == "u" || ch == "U") && stream.eat("{") && stream.skipTo("}")) stream.next(); - else if (ch == "u") stream.match(/^[a-f0-9]{4}/i); - else if (ch == "U") stream.match(/^[a-f0-9]{8}/i); - else if (/[0-7]/.test(ch)) stream.match(/^[0-7]{1,2}/); - return "string-2"; - } else { - var next; - while ((next = stream.next()) != null) { - if (next == quote) { state.tokenize = tokenBase; break; } - if (next == "\\") { stream.backUp(1); break; } - } - return "string"; - } - }; - } - - function push(state, type, stream) { - state.ctx = {type: type, - indent: state.indent, - align: null, - column: stream.column(), - prev: state.ctx}; - } - function pop(state) { - state.indent = state.ctx.indent; - state.ctx = state.ctx.prev; - } - - return { - startState: function() { - return {tokenize: tokenBase, - ctx: {type: "top", - indent: -config.indentUnit, - align: false}, - indent: 0, - afterIdent: false}; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (state.ctx.align == null) state.ctx.align = false; - state.indent = stream.indentation(); - } - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - if (style != "comment" && state.ctx.align == null) state.ctx.align = true; - - var ctype = state.ctx.type; - if ((curPunc == ";" || curPunc == "{" || curPunc == "}") && ctype == "block") pop(state); - if (curPunc == "{") push(state, "}", stream); - else if (curPunc == "(") { - push(state, ")", stream); - if (state.afterIdent) state.ctx.argList = true; - } - else if (curPunc == "[") push(state, "]", stream); - else if (curPunc == "block") push(state, "block", stream); - else if (curPunc == ctype) pop(state); - state.afterIdent = style == "variable" || style == "keyword"; - return style; - }, - - indent: function(state, textAfter) { - if (state.tokenize != tokenBase) return 0; - var firstChar = textAfter && textAfter.charAt(0), ctx = state.ctx, - closing = firstChar == ctx.type; - if (ctx.type == "block") return ctx.indent + (firstChar == "{" ? 0 : config.indentUnit); - else if (ctx.align) return ctx.column + (closing ? 0 : 1); - else return ctx.indent + (closing ? 0 : config.indentUnit); - } - }; -}); - -CodeMirror.defineMIME("text/x-rsrc", "r"); diff --git a/applications/admin/static/codemirror/mode/rpm/changes/changes.js b/applications/admin/static/codemirror/mode/rpm/changes/changes.js deleted file mode 100644 index 14a08d97..00000000 --- a/applications/admin/static/codemirror/mode/rpm/changes/changes.js +++ /dev/null @@ -1,19 +0,0 @@ -CodeMirror.defineMode("changes", function() { - var headerSeperator = /^-+$/; - var headerLine = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?\d{1,2} \d{2}:\d{2}(:\d{2})? [A-Z]{3,4} \d{4} - /; - var simpleEmail = /^[\w+.-]+@[\w.-]+/; - - return { - token: function(stream) { - if (stream.sol()) { - if (stream.match(headerSeperator)) { return 'tag'; } - if (stream.match(headerLine)) { return 'tag'; } - } - if (stream.match(simpleEmail)) { return 'string'; } - stream.next(); - return null; - } - }; -}); - -CodeMirror.defineMIME("text/x-rpm-changes", "changes"); diff --git a/applications/admin/static/codemirror/mode/rpm/changes/index.html b/applications/admin/static/codemirror/mode/rpm/changes/index.html deleted file mode 100644 index 18fe7ab7..00000000 --- a/applications/admin/static/codemirror/mode/rpm/changes/index.html +++ /dev/null @@ -1,67 +0,0 @@ - - -CodeMirror: RPM changes mode - - - - - - - - - - - -
-

RPM changes mode

- -
- - -

MIME types defined: text/x-rpm-changes.

-
diff --git a/applications/admin/static/codemirror/mode/rpm/spec/index.html b/applications/admin/static/codemirror/mode/rpm/spec/index.html deleted file mode 100644 index 127b72ee..00000000 --- a/applications/admin/static/codemirror/mode/rpm/spec/index.html +++ /dev/null @@ -1,114 +0,0 @@ - - -CodeMirror: RPM spec mode - - - - - - - - - - - - -
-

RPM spec mode

- -
- - -

MIME types defined: text/x-rpm-spec.

- -
diff --git a/applications/admin/static/codemirror/mode/rpm/spec/spec.css b/applications/admin/static/codemirror/mode/rpm/spec/spec.css deleted file mode 100644 index d0a5d430..00000000 --- a/applications/admin/static/codemirror/mode/rpm/spec/spec.css +++ /dev/null @@ -1,5 +0,0 @@ -.cm-s-default span.cm-preamble {color: #b26818; font-weight: bold;} -.cm-s-default span.cm-macro {color: #b218b2;} -.cm-s-default span.cm-section {color: green; font-weight: bold;} -.cm-s-default span.cm-script {color: red;} -.cm-s-default span.cm-issue {color: yellow;} diff --git a/applications/admin/static/codemirror/mode/rpm/spec/spec.js b/applications/admin/static/codemirror/mode/rpm/spec/spec.js deleted file mode 100644 index 9f339c21..00000000 --- a/applications/admin/static/codemirror/mode/rpm/spec/spec.js +++ /dev/null @@ -1,66 +0,0 @@ -// Quick and dirty spec file highlighting - -CodeMirror.defineMode("spec", function() { - var arch = /^(i386|i586|i686|x86_64|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/; - - var preamble = /^(Name|Version|Release|License|Summary|Url|Group|Source|BuildArch|BuildRequires|BuildRoot|AutoReqProv|Provides|Requires(\(\w+\))?|Obsoletes|Conflicts|Recommends|Source\d*|Patch\d*|ExclusiveArch|NoSource|Supplements):/; - var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preun|postun|pre|post|triggerin|triggerun|pretrans|posttrans|verifyscript|check|triggerpostun|triggerprein|trigger)/; - var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros - var control_flow_simple = /^%(else|endif)/; // rpm control flow macros - var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros - - return { - startState: function () { - return { - controlFlow: false, - macroParameters: false, - section: false - }; - }, - token: function (stream, state) { - var ch = stream.peek(); - if (ch == "#") { stream.skipToEnd(); return "comment"; } - - if (stream.sol()) { - if (stream.match(preamble)) { return "preamble"; } - if (stream.match(section)) { return "section"; } - } - - if (stream.match(/^\$\w+/)) { return "def"; } // Variables like '$RPM_BUILD_ROOT' - if (stream.match(/^\$\{\w+\}/)) { return "def"; } // Variables like '${RPM_BUILD_ROOT}' - - if (stream.match(control_flow_simple)) { return "keyword"; } - if (stream.match(control_flow_complex)) { - state.controlFlow = true; - return "keyword"; - } - if (state.controlFlow) { - if (stream.match(operators)) { return "operator"; } - if (stream.match(/^(\d+)/)) { return "number"; } - if (stream.eol()) { state.controlFlow = false; } - } - - if (stream.match(arch)) { return "number"; } - - // Macros like '%make_install' or '%attr(0775,root,root)' - if (stream.match(/^%[\w]+/)) { - if (stream.match(/^\(/)) { state.macroParameters = true; } - return "macro"; - } - if (state.macroParameters) { - if (stream.match(/^\d+/)) { return "number";} - if (stream.match(/^\)/)) { - state.macroParameters = false; - return "macro"; - } - } - if (stream.match(/^%\{\??[\w \-]+\}/)) { return "macro"; } // Macros like '%{defined fedora}' - - //TODO: Include bash script sub-parser (CodeMirror supports that) - stream.next(); - return null; - } - }; -}); - -CodeMirror.defineMIME("text/x-rpm-spec", "spec"); diff --git a/applications/admin/static/codemirror/mode/rst/LICENSE.txt b/applications/admin/static/codemirror/mode/rst/LICENSE.txt deleted file mode 100644 index 39484fab..00000000 --- a/applications/admin/static/codemirror/mode/rst/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License - -Copyright (c) 2013 Hasan Karahan - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/applications/admin/static/codemirror/mode/rst/index.html b/applications/admin/static/codemirror/mode/rst/index.html deleted file mode 100644 index 78030ebe..00000000 --- a/applications/admin/static/codemirror/mode/rst/index.html +++ /dev/null @@ -1,534 +0,0 @@ - - -CodeMirror: reStructuredText mode - - - - - - - - - -
-

reStructuredText mode

-
- - -

- The python mode will be used for highlighting blocks - containing Python/IPython terminal sessions: blocks starting with - >>> (for Python) or In [num]: (for - IPython). - - Further, the stex mode will be used for highlighting - blocks containing LaTex code. -

- -

MIME types defined: text/x-rst.

-
diff --git a/applications/admin/static/codemirror/mode/rst/rst.js b/applications/admin/static/codemirror/mode/rst/rst.js deleted file mode 100644 index 75563ba9..00000000 --- a/applications/admin/static/codemirror/mode/rst/rst.js +++ /dev/null @@ -1,560 +0,0 @@ -CodeMirror.defineMode('rst-base', function (config) { - - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// - - function format(string) { - var args = Array.prototype.slice.call(arguments, 1); - return string.replace(/{(\d+)}/g, function (match, n) { - return typeof args[n] != 'undefined' ? args[n] : match; - }); - } - - function AssertException(message) { - this.message = message; - } - - AssertException.prototype.toString = function () { - return 'AssertException: ' + this.message; - }; - - function assert(expression, message) { - if (!expression) throw new AssertException(message); - return expression; - } - - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// - - var mode_python = CodeMirror.getMode(config, 'python'); - var mode_stex = CodeMirror.getMode(config, 'stex'); - - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// - - var SEPA = "\\s+"; - var TAIL = "(?:\\s*|\\W|$)", - rx_TAIL = new RegExp(format('^{0}', TAIL)); - - var NAME = - "(?:[^\\W\\d_](?:[\\w!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)", - rx_NAME = new RegExp(format('^{0}', NAME)); - var NAME_WWS = - "(?:[^\\W\\d_](?:[\\w\\s!\"#$%&'()\\*\\+,\\-\\.\/:;<=>\\?]*[^\\W_])?)"; - var REF_NAME = format('(?:{0}|`{1}`)', NAME, NAME_WWS); - - var TEXT1 = "(?:[^\\s\\|](?:[^\\|]*[^\\s\\|])?)"; - var TEXT2 = "(?:[^\\`]+)", - rx_TEXT2 = new RegExp(format('^{0}', TEXT2)); - - var rx_section = new RegExp( - "^([!'#$%&\"()*+,-./:;<=>?@\\[\\\\\\]^_`{|}~])\\1{3,}\\s*$"); - var rx_explicit = new RegExp( - format('^\\.\\.{0}', SEPA)); - var rx_link = new RegExp( - format('^_{0}:{1}|^__:{1}', REF_NAME, TAIL)); - var rx_directive = new RegExp( - format('^{0}::{1}', REF_NAME, TAIL)); - var rx_substitution = new RegExp( - format('^\\|{0}\\|{1}{2}::{3}', TEXT1, SEPA, REF_NAME, TAIL)); - var rx_footnote = new RegExp( - format('^\\[(?:\\d+|#{0}?|\\*)]{1}', REF_NAME, TAIL)); - var rx_citation = new RegExp( - format('^\\[{0}\\]{1}', REF_NAME, TAIL)); - - var rx_substitution_ref = new RegExp( - format('^\\|{0}\\|', TEXT1)); - var rx_footnote_ref = new RegExp( - format('^\\[(?:\\d+|#{0}?|\\*)]_', REF_NAME)); - var rx_citation_ref = new RegExp( - format('^\\[{0}\\]_', REF_NAME)); - var rx_link_ref1 = new RegExp( - format('^{0}__?', REF_NAME)); - var rx_link_ref2 = new RegExp( - format('^`{0}`_', TEXT2)); - - var rx_role_pre = new RegExp( - format('^:{0}:`{1}`{2}', NAME, TEXT2, TAIL)); - var rx_role_suf = new RegExp( - format('^`{1}`:{0}:{2}', NAME, TEXT2, TAIL)); - var rx_role = new RegExp( - format('^:{0}:{1}', NAME, TAIL)); - - var rx_directive_name = new RegExp(format('^{0}', REF_NAME)); - var rx_directive_tail = new RegExp(format('^::{0}', TAIL)); - var rx_substitution_text = new RegExp(format('^\\|{0}\\|', TEXT1)); - var rx_substitution_sepa = new RegExp(format('^{0}', SEPA)); - var rx_substitution_name = new RegExp(format('^{0}', REF_NAME)); - var rx_substitution_tail = new RegExp(format('^::{0}', TAIL)); - var rx_link_head = new RegExp("^_"); - var rx_link_name = new RegExp(format('^{0}|_', REF_NAME)); - var rx_link_tail = new RegExp(format('^:{0}', TAIL)); - - var rx_verbatim = new RegExp('^::\\s*$'); - var rx_examples = new RegExp('^\\s+(?:>>>|In \\[\\d+\\]:)\\s'); - - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// - - function to_normal(stream, state) { - var token = null; - - if (stream.sol() && stream.match(rx_examples, false)) { - change(state, to_mode, { - mode: mode_python, local: mode_python.startState() - }); - } else if (stream.sol() && stream.match(rx_explicit)) { - change(state, to_explicit); - token = 'meta'; - } else if (stream.sol() && stream.match(rx_section)) { - change(state, to_normal); - token = 'header'; - } else if (phase(state) == rx_role_pre || - stream.match(rx_role_pre, false)) { - - switch (stage(state)) { - case 0: - change(state, to_normal, context(rx_role_pre, 1)); - assert(stream.match(/^:/)); - token = 'meta'; - break; - case 1: - change(state, to_normal, context(rx_role_pre, 2)); - assert(stream.match(rx_NAME)); - token = 'keyword'; - - if (stream.current().match(/^(?:math|latex)/)) { - state.tmp_stex = true; - } - break; - case 2: - change(state, to_normal, context(rx_role_pre, 3)); - assert(stream.match(/^:`/)); - token = 'meta'; - break; - case 3: - if (state.tmp_stex) { - state.tmp_stex = undefined; state.tmp = { - mode: mode_stex, local: mode_stex.startState() - }; - } - - if (state.tmp) { - if (stream.peek() == '`') { - change(state, to_normal, context(rx_role_pre, 4)); - state.tmp = undefined; - break; - } - - token = state.tmp.mode.token(stream, state.tmp.local); - break; - } - - change(state, to_normal, context(rx_role_pre, 4)); - assert(stream.match(rx_TEXT2)); - token = 'string'; - break; - case 4: - change(state, to_normal, context(rx_role_pre, 5)); - assert(stream.match(/^`/)); - token = 'meta'; - break; - case 5: - change(state, to_normal, context(rx_role_pre, 6)); - assert(stream.match(rx_TAIL)); - break; - default: - change(state, to_normal); - assert(stream.current() == ''); - } - } else if (phase(state) == rx_role_suf || - stream.match(rx_role_suf, false)) { - - switch (stage(state)) { - case 0: - change(state, to_normal, context(rx_role_suf, 1)); - assert(stream.match(/^`/)); - token = 'meta'; - break; - case 1: - change(state, to_normal, context(rx_role_suf, 2)); - assert(stream.match(rx_TEXT2)); - token = 'string'; - break; - case 2: - change(state, to_normal, context(rx_role_suf, 3)); - assert(stream.match(/^`:/)); - token = 'meta'; - break; - case 3: - change(state, to_normal, context(rx_role_suf, 4)); - assert(stream.match(rx_NAME)); - token = 'keyword'; - break; - case 4: - change(state, to_normal, context(rx_role_suf, 5)); - assert(stream.match(/^:/)); - token = 'meta'; - break; - case 5: - change(state, to_normal, context(rx_role_suf, 6)); - assert(stream.match(rx_TAIL)); - break; - default: - change(state, to_normal); - assert(stream.current() == ''); - } - } else if (phase(state) == rx_role || stream.match(rx_role, false)) { - - switch (stage(state)) { - case 0: - change(state, to_normal, context(rx_role, 1)); - assert(stream.match(/^:/)); - token = 'meta'; - break; - case 1: - change(state, to_normal, context(rx_role, 2)); - assert(stream.match(rx_NAME)); - token = 'keyword'; - break; - case 2: - change(state, to_normal, context(rx_role, 3)); - assert(stream.match(/^:/)); - token = 'meta'; - break; - case 3: - change(state, to_normal, context(rx_role, 4)); - assert(stream.match(rx_TAIL)); - break; - default: - change(state, to_normal); - assert(stream.current() == ''); - } - } else if (phase(state) == rx_substitution_ref || - stream.match(rx_substitution_ref, false)) { - - switch (stage(state)) { - case 0: - change(state, to_normal, context(rx_substitution_ref, 1)); - assert(stream.match(rx_substitution_text)); - token = 'variable-2'; - break; - case 1: - change(state, to_normal, context(rx_substitution_ref, 2)); - if (stream.match(/^_?_?/)) token = 'link'; - break; - default: - change(state, to_normal); - assert(stream.current() == ''); - } - } else if (stream.match(rx_footnote_ref)) { - change(state, to_normal); - token = 'quote'; - } else if (stream.match(rx_citation_ref)) { - change(state, to_normal); - token = 'quote'; - } else if (stream.match(rx_link_ref1)) { - change(state, to_normal); - if (!stream.peek() || stream.peek().match(/^\W$/)) { - token = 'link'; - } - } else if (phase(state) == rx_link_ref2 || - stream.match(rx_link_ref2, false)) { - - switch (stage(state)) { - case 0: - if (!stream.peek() || stream.peek().match(/^\W$/)) { - change(state, to_normal, context(rx_link_ref2, 1)); - } else { - stream.match(rx_link_ref2); - } - break; - case 1: - change(state, to_normal, context(rx_link_ref2, 2)); - assert(stream.match(/^`/)); - token = 'link'; - break; - case 2: - change(state, to_normal, context(rx_link_ref2, 3)); - assert(stream.match(rx_TEXT2)); - break; - case 3: - change(state, to_normal, context(rx_link_ref2, 4)); - assert(stream.match(/^`_/)); - token = 'link'; - break; - default: - change(state, to_normal); - assert(stream.current() == ''); - } - } else if (stream.match(rx_verbatim)) { - change(state, to_verbatim); - } - - else { - if (stream.next()) change(state, to_normal); - } - - return token; - } - - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// - - function to_explicit(stream, state) { - var token = null; - - if (phase(state) == rx_substitution || - stream.match(rx_substitution, false)) { - - switch (stage(state)) { - case 0: - change(state, to_explicit, context(rx_substitution, 1)); - assert(stream.match(rx_substitution_text)); - token = 'variable-2'; - break; - case 1: - change(state, to_explicit, context(rx_substitution, 2)); - assert(stream.match(rx_substitution_sepa)); - break; - case 2: - change(state, to_explicit, context(rx_substitution, 3)); - assert(stream.match(rx_substitution_name)); - token = 'keyword'; - break; - case 3: - change(state, to_explicit, context(rx_substitution, 4)); - assert(stream.match(rx_substitution_tail)); - token = 'meta'; - break; - default: - change(state, to_normal); - assert(stream.current() == ''); - } - } else if (phase(state) == rx_directive || - stream.match(rx_directive, false)) { - - switch (stage(state)) { - case 0: - change(state, to_explicit, context(rx_directive, 1)); - assert(stream.match(rx_directive_name)); - token = 'keyword'; - - if (stream.current().match(/^(?:math|latex)/)) - state.tmp_stex = true; - else if (stream.current().match(/^python/)) - state.tmp_py = true; - break; - case 1: - change(state, to_explicit, context(rx_directive, 2)); - assert(stream.match(rx_directive_tail)); - token = 'meta'; - - if (stream.match(/^latex\s*$/) || state.tmp_stex) { - state.tmp_stex = undefined; change(state, to_mode, { - mode: mode_stex, local: mode_stex.startState() - }); - } - break; - case 2: - change(state, to_explicit, context(rx_directive, 3)); - if (stream.match(/^python\s*$/) || state.tmp_py) { - state.tmp_py = undefined; change(state, to_mode, { - mode: mode_python, local: mode_python.startState() - }); - } - break; - default: - change(state, to_normal); - assert(stream.current() == ''); - } - } else if (phase(state) == rx_link || stream.match(rx_link, false)) { - - switch (stage(state)) { - case 0: - change(state, to_explicit, context(rx_link, 1)); - assert(stream.match(rx_link_head)); - assert(stream.match(rx_link_name)); - token = 'link'; - break; - case 1: - change(state, to_explicit, context(rx_link, 2)); - assert(stream.match(rx_link_tail)); - token = 'meta'; - break; - default: - change(state, to_normal); - assert(stream.current() == ''); - } - } else if (stream.match(rx_footnote)) { - change(state, to_normal); - token = 'quote'; - } else if (stream.match(rx_citation)) { - change(state, to_normal); - token = 'quote'; - } - - else { - stream.eatSpace(); - if (stream.eol()) { - change(state, to_normal); - } else { - stream.skipToEnd(); - change(state, to_comment); - token = 'comment'; - } - } - - return token; - } - - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// - - function to_comment(stream, state) { - return as_block(stream, state, 'comment'); - } - - function to_verbatim(stream, state) { - return as_block(stream, state, 'meta'); - } - - function as_block(stream, state, token) { - if (stream.eol() || stream.eatSpace()) { - stream.skipToEnd(); - return token; - } else { - change(state, to_normal); - return null; - } - } - - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// - - function to_mode(stream, state) { - - if (state.ctx.mode && state.ctx.local) { - - if (stream.sol()) { - if (!stream.eatSpace()) change(state, to_normal); - return null; - } - - return state.ctx.mode.token(stream, state.ctx.local); - } - - change(state, to_normal); - return null; - } - - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// - - function context(phase, stage, mode, local) { - return {phase: phase, stage: stage, mode: mode, local: local}; - } - - function change(state, tok, ctx) { - state.tok = tok; - state.ctx = ctx || {}; - } - - function stage(state) { - return state.ctx.stage || 0; - } - - function phase(state) { - return state.ctx.phase; - } - - /////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////// - - return { - startState: function () { - return {tok: to_normal, ctx: context(undefined, 0)}; - }, - - copyState: function (state) { - return {tok: state.tok, ctx: state.ctx}; - }, - - innerMode: function (state) { - return state.tmp ? {state: state.tmp.local, mode: state.tmp.mode} - : state.ctx ? {state: state.ctx.local, mode: state.ctx.mode} - : null; - }, - - token: function (stream, state) { - return state.tok(stream, state); - } - }; -}, 'python', 'stex'); - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// - -CodeMirror.defineMode('rst', function (config, options) { - - var rx_strong = /^\*\*[^\*\s](?:[^\*]*[^\*\s])?\*\*/; - var rx_emphasis = /^\*[^\*\s](?:[^\*]*[^\*\s])?\*/; - var rx_literal = /^``[^`\s](?:[^`]*[^`\s])``/; - - var rx_number = /^(?:[\d]+(?:[\.,]\d+)*)/; - var rx_positive = /^(?:\s\+[\d]+(?:[\.,]\d+)*)/; - var rx_negative = /^(?:\s\-[\d]+(?:[\.,]\d+)*)/; - - var rx_uri_protocol = "[Hh][Tt][Tt][Pp][Ss]?://"; - var rx_uri_domain = "(?:[\\d\\w.-]+)\\.(?:\\w{2,6})"; - var rx_uri_path = "(?:/[\\d\\w\\#\\%\\&\\-\\.\\,\\/\\:\\=\\?\\~]+)*"; - var rx_uri = new RegExp("^" + - rx_uri_protocol + rx_uri_domain + rx_uri_path - ); - - var overlay = { - token: function (stream) { - - if (stream.match(rx_strong) && stream.match (/\W+|$/, false)) - return 'strong'; - if (stream.match(rx_emphasis) && stream.match (/\W+|$/, false)) - return 'em'; - if (stream.match(rx_literal) && stream.match (/\W+|$/, false)) - return 'string-2'; - if (stream.match(rx_number)) - return 'number'; - if (stream.match(rx_positive)) - return 'positive'; - if (stream.match(rx_negative)) - return 'negative'; - if (stream.match(rx_uri)) - return 'link'; - - while (stream.next() != null) { - if (stream.match(rx_strong, false)) break; - if (stream.match(rx_emphasis, false)) break; - if (stream.match(rx_literal, false)) break; - if (stream.match(rx_number, false)) break; - if (stream.match(rx_positive, false)) break; - if (stream.match(rx_negative, false)) break; - if (stream.match(rx_uri, false)) break; - } - - return null; - } - }; - - var mode = CodeMirror.getMode( - config, options.backdrop || 'rst-base' - ); - - return CodeMirror.overlayMode(mode, overlay, true); // combine -}, 'python', 'stex'); - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// - -CodeMirror.defineMIME('text/x-rst', 'rst'); - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// diff --git a/applications/admin/static/codemirror/mode/sass/index.html b/applications/admin/static/codemirror/mode/sass/index.html deleted file mode 100644 index 66d46778..00000000 --- a/applications/admin/static/codemirror/mode/sass/index.html +++ /dev/null @@ -1,66 +0,0 @@ - - -CodeMirror: Sass mode - - - - - - - - - - -
-

Sass mode

-
- - -

MIME types defined: text/x-sass.

-
diff --git a/applications/admin/static/codemirror/mode/sass/sass.js b/applications/admin/static/codemirror/mode/sass/sass.js deleted file mode 100644 index 9c9a0dae..00000000 --- a/applications/admin/static/codemirror/mode/sass/sass.js +++ /dev/null @@ -1,330 +0,0 @@ -CodeMirror.defineMode("sass", function(config) { - var tokenRegexp = function(words){ - return new RegExp("^" + words.join("|")); - }; - - var keywords = ["true", "false", "null", "auto"]; - var keywordsRegexp = new RegExp("^" + keywords.join("|")); - - var operators = ["\\(", "\\)", "=", ">", "<", "==", ">=", "<=", "\\+", "-", "\\!=", "/", "\\*", "%", "and", "or", "not"]; - var opRegexp = tokenRegexp(operators); - - var pseudoElementsRegexp = /^::?[\w\-]+/; - - var urlTokens = function(stream, state){ - var ch = stream.peek(); - - if (ch === ")"){ - stream.next(); - state.tokenizer = tokenBase; - return "operator"; - }else if (ch === "("){ - stream.next(); - stream.eatSpace(); - - return "operator"; - }else if (ch === "'" || ch === '"'){ - state.tokenizer = buildStringTokenizer(stream.next()); - return "string"; - }else{ - state.tokenizer = buildStringTokenizer(")", false); - return "string"; - } - }; - var multilineComment = function(stream, state) { - if (stream.skipTo("*/")){ - stream.next(); - stream.next(); - state.tokenizer = tokenBase; - }else { - stream.next(); - } - - return "comment"; - }; - - var buildStringTokenizer = function(quote, greedy){ - if(greedy == null){ greedy = true; } - - function stringTokenizer(stream, state){ - var nextChar = stream.next(); - var peekChar = stream.peek(); - var previousChar = stream.string.charAt(stream.pos-2); - - var endingString = ((nextChar !== "\\" && peekChar === quote) || (nextChar === quote && previousChar !== "\\")); - - /* - console.log("previousChar: " + previousChar); - console.log("nextChar: " + nextChar); - console.log("peekChar: " + peekChar); - console.log("ending: " + endingString); - */ - - if (endingString){ - if (nextChar !== quote && greedy) { stream.next(); } - state.tokenizer = tokenBase; - return "string"; - }else if (nextChar === "#" && peekChar === "{"){ - state.tokenizer = buildInterpolationTokenizer(stringTokenizer); - stream.next(); - return "operator"; - }else { - return "string"; - } - } - - return stringTokenizer; - }; - - var buildInterpolationTokenizer = function(currentTokenizer){ - return function(stream, state){ - if (stream.peek() === "}"){ - stream.next(); - state.tokenizer = currentTokenizer; - return "operator"; - }else{ - return tokenBase(stream, state); - } - }; - }; - - var indent = function(state){ - if (state.indentCount == 0){ - state.indentCount++; - var lastScopeOffset = state.scopes[0].offset; - var currentOffset = lastScopeOffset + config.indentUnit; - state.scopes.unshift({ offset:currentOffset }); - } - }; - - var dedent = function(state){ - if (state.scopes.length == 1) { return; } - - state.scopes.shift(); - }; - - var tokenBase = function(stream, state) { - var ch = stream.peek(); - - // Single line Comment - if (stream.match('//')) { - stream.skipToEnd(); - return "comment"; - } - - // Multiline Comment - if (stream.match('/*')){ - state.tokenizer = multilineComment; - return state.tokenizer(stream, state); - } - - // Interpolation - if (stream.match('#{')){ - state.tokenizer = buildInterpolationTokenizer(tokenBase); - return "operator"; - } - - if (ch === "."){ - stream.next(); - - // Match class selectors - if (stream.match(/^[\w-]+/)){ - indent(state); - return "atom"; - }else if (stream.peek() === "#"){ - indent(state); - return "atom"; - }else{ - return "operator"; - } - } - - if (ch === "#"){ - stream.next(); - - // Hex numbers - if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){ - return "number"; - } - - // ID selectors - if (stream.match(/^[\w-]+/)){ - indent(state); - return "atom"; - } - - if (stream.peek() === "#"){ - indent(state); - return "atom"; - } - } - - // Numbers - if (stream.match(/^-?[0-9\.]+/)){ - return "number"; - } - - // Units - if (stream.match(/^(px|em|in)\b/)){ - return "unit"; - } - - if (stream.match(keywordsRegexp)){ - return "keyword"; - } - - if (stream.match(/^url/) && stream.peek() === "("){ - state.tokenizer = urlTokens; - return "atom"; - } - - // Variables - if (ch === "$"){ - stream.next(); - stream.eatWhile(/[\w-]/); - - if (stream.peek() === ":"){ - stream.next(); - return "variable-2"; - }else{ - return "variable-3"; - } - } - - if (ch === "!"){ - stream.next(); - - if (stream.match(/^[\w]+/)){ - return "keyword"; - } - - return "operator"; - } - - if (ch === "="){ - stream.next(); - - // Match shortcut mixin definition - if (stream.match(/^[\w-]+/)){ - indent(state); - return "meta"; - }else { - return "operator"; - } - } - - if (ch === "+"){ - stream.next(); - - // Match shortcut mixin definition - if (stream.match(/^[\w-]+/)){ - return "variable-3"; - }else { - return "operator"; - } - } - - // Indent Directives - if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)){ - indent(state); - return "meta"; - } - - // Other Directives - if (ch === "@"){ - stream.next(); - stream.eatWhile(/[\w-]/); - return "meta"; - } - - // Strings - if (ch === '"' || ch === "'"){ - stream.next(); - state.tokenizer = buildStringTokenizer(ch); - return "string"; - } - - // Pseudo element selectors - if (ch == ':' && stream.match(pseudoElementsRegexp)){ - return "keyword"; - } - - // atoms - if (stream.eatWhile(/[\w-&]/)){ - // matches a property definition - if (stream.peek() === ":" && !stream.match(pseudoElementsRegexp, false)) - return "property"; - else - return "atom"; - } - - if (stream.match(opRegexp)){ - return "operator"; - } - - // If we haven't returned by now, we move 1 character - // and return an error - stream.next(); - return null; - }; - - var tokenLexer = function(stream, state) { - if (stream.sol()){ - state.indentCount = 0; - } - var style = state.tokenizer(stream, state); - var current = stream.current(); - - if (current === "@return"){ - dedent(state); - } - - if (style === "atom"){ - indent(state); - } - - if (style !== null){ - var startOfToken = stream.pos - current.length; - var withCurrentIndent = startOfToken + (config.indentUnit * state.indentCount); - - var newScopes = []; - - for (var i = 0; i < state.scopes.length; i++){ - var scope = state.scopes[i]; - - if (scope.offset <= withCurrentIndent){ - newScopes.push(scope); - } - } - - state.scopes = newScopes; - } - - - return style; - }; - - return { - startState: function() { - return { - tokenizer: tokenBase, - scopes: [{offset: 0, type: 'sass'}], - definedVars: [], - definedMixins: [] - }; - }, - token: function(stream, state) { - var style = tokenLexer(stream, state); - - state.lastToken = { style: style, content: stream.current() }; - - return style; - }, - - indent: function(state) { - return state.scopes[0].offset; - } - }; -}); - -CodeMirror.defineMIME("text/x-sass", "sass"); diff --git a/applications/admin/static/codemirror/mode/shell/index.html b/applications/admin/static/codemirror/mode/shell/index.html deleted file mode 100644 index cf415e83..00000000 --- a/applications/admin/static/codemirror/mode/shell/index.html +++ /dev/null @@ -1,66 +0,0 @@ - - -CodeMirror: Shell mode - - - - - - - - - - -
-

Shell mode

- - - - - - -

MIME types defined: text/x-sh.

-
diff --git a/applications/admin/static/codemirror/mode/shell/shell.js b/applications/admin/static/codemirror/mode/shell/shell.js deleted file mode 100644 index abfd2144..00000000 --- a/applications/admin/static/codemirror/mode/shell/shell.js +++ /dev/null @@ -1,118 +0,0 @@ -CodeMirror.defineMode('shell', function() { - - var words = {}; - function define(style, string) { - var split = string.split(' '); - for(var i = 0; i < split.length; i++) { - words[split[i]] = style; - } - }; - - // Atoms - define('atom', 'true false'); - - // Keywords - define('keyword', 'if then do else elif while until for in esac fi fin ' + - 'fil done exit set unset export function'); - - // Commands - define('builtin', 'ab awk bash beep cat cc cd chown chmod chroot clear cp ' + - 'curl cut diff echo find gawk gcc get git grep kill killall ln ls make ' + - 'mkdir openssl mv nc node npm ping ps restart rm rmdir sed service sh ' + - 'shopt shred source sort sleep ssh start stop su sudo tee telnet top ' + - 'touch vi vim wall wc wget who write yes zsh'); - - function tokenBase(stream, state) { - - var sol = stream.sol(); - var ch = stream.next(); - - if (ch === '\'' || ch === '"' || ch === '`') { - state.tokens.unshift(tokenString(ch)); - return tokenize(stream, state); - } - if (ch === '#') { - if (sol && stream.eat('!')) { - stream.skipToEnd(); - return 'meta'; // 'comment'? - } - stream.skipToEnd(); - return 'comment'; - } - if (ch === '$') { - state.tokens.unshift(tokenDollar); - return tokenize(stream, state); - } - if (ch === '+' || ch === '=') { - return 'operator'; - } - if (ch === '-') { - stream.eat('-'); - stream.eatWhile(/\w/); - return 'attribute'; - } - if (/\d/.test(ch)) { - stream.eatWhile(/\d/); - if(!/\w/.test(stream.peek())) { - return 'number'; - } - } - stream.eatWhile(/[\w-]/); - var cur = stream.current(); - if (stream.peek() === '=' && /\w+/.test(cur)) return 'def'; - return words.hasOwnProperty(cur) ? words[cur] : null; - } - - function tokenString(quote) { - return function(stream, state) { - var next, end = false, escaped = false; - while ((next = stream.next()) != null) { - if (next === quote && !escaped) { - end = true; - break; - } - if (next === '$' && !escaped && quote !== '\'') { - escaped = true; - stream.backUp(1); - state.tokens.unshift(tokenDollar); - break; - } - escaped = !escaped && next === '\\'; - } - if (end || !escaped) { - state.tokens.shift(); - } - return (quote === '`' || quote === ')' ? 'quote' : 'string'); - }; - }; - - var tokenDollar = function(stream, state) { - if (state.tokens.length > 1) stream.eat('$'); - var ch = stream.next(), hungry = /\w/; - if (ch === '{') hungry = /[^}]/; - if (ch === '(') { - state.tokens[0] = tokenString(')'); - return tokenize(stream, state); - } - if (!/\d/.test(ch)) { - stream.eatWhile(hungry); - stream.eat('}'); - } - state.tokens.shift(); - return 'def'; - }; - - function tokenize(stream, state) { - return (state.tokens[0] || tokenBase) (stream, state); - }; - - return { - startState: function() {return {tokens:[]};}, - token: function(stream, state) { - if (stream.eatSpace()) return null; - return tokenize(stream, state); - } - }; -}); - -CodeMirror.defineMIME('text/x-sh', 'shell'); diff --git a/applications/admin/static/codemirror/mode/sparql/index.html b/applications/admin/static/codemirror/mode/sparql/index.html deleted file mode 100644 index 7c41e17b..00000000 --- a/applications/admin/static/codemirror/mode/sparql/index.html +++ /dev/null @@ -1,54 +0,0 @@ - - -CodeMirror: SPARQL mode - - - - - - - - - - -
-

SPARQL mode

-
- - -

MIME types defined: application/x-sparql-query.

- -
diff --git a/applications/admin/static/codemirror/mode/sparql/sparql.js b/applications/admin/static/codemirror/mode/sparql/sparql.js deleted file mode 100644 index 0329057f..00000000 --- a/applications/admin/static/codemirror/mode/sparql/sparql.js +++ /dev/null @@ -1,145 +0,0 @@ -CodeMirror.defineMode("sparql", function(config) { - var indentUnit = config.indentUnit; - var curPunc; - - function wordRegexp(words) { - return new RegExp("^(?:" + words.join("|") + ")$", "i"); - } - var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", - "isblank", "isliteral", "a"]); - var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", - "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", - "graph", "by", "asc", "desc", "as", "having", "undef", "values", "group", - "minus", "in", "not", "service", "silent", "using", "insert", "delete", "union", - "data", "copy", "to", "move", "add", "create", "drop", "clear", "load"]); - var operatorChars = /[*+\-<>=&|]/; - - function tokenBase(stream, state) { - var ch = stream.next(); - curPunc = null; - if (ch == "$" || ch == "?") { - stream.match(/^[\w\d]*/); - return "variable-2"; - } - else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { - stream.match(/^[^\s\u00a0>]*>?/); - return "atom"; - } - else if (ch == "\"" || ch == "'") { - state.tokenize = tokenLiteral(ch); - return state.tokenize(stream, state); - } - else if (/[{}\(\),\.;\[\]]/.test(ch)) { - curPunc = ch; - return null; - } - else if (ch == "#") { - stream.skipToEnd(); - return "comment"; - } - else if (operatorChars.test(ch)) { - stream.eatWhile(operatorChars); - return null; - } - else if (ch == ":") { - stream.eatWhile(/[\w\d\._\-]/); - return "atom"; - } - else { - stream.eatWhile(/[_\w\d]/); - if (stream.eat(":")) { - stream.eatWhile(/[\w\d_\-]/); - return "atom"; - } - var word = stream.current(); - if (ops.test(word)) - return null; - else if (keywords.test(word)) - return "keyword"; - else - return "variable"; - } - } - - function tokenLiteral(quote) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) { - state.tokenize = tokenBase; - break; - } - escaped = !escaped && ch == "\\"; - } - return "string"; - }; - } - - function pushContext(state, type, col) { - state.context = {prev: state.context, indent: state.indent, col: col, type: type}; - } - function popContext(state) { - state.indent = state.context.indent; - state.context = state.context.prev; - } - - return { - startState: function() { - return {tokenize: tokenBase, - context: null, - indent: 0, - col: 0}; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (state.context && state.context.align == null) state.context.align = false; - state.indent = stream.indentation(); - } - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - - if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { - state.context.align = true; - } - - if (curPunc == "(") pushContext(state, ")", stream.column()); - else if (curPunc == "[") pushContext(state, "]", stream.column()); - else if (curPunc == "{") pushContext(state, "}", stream.column()); - else if (/[\]\}\)]/.test(curPunc)) { - while (state.context && state.context.type == "pattern") popContext(state); - if (state.context && curPunc == state.context.type) popContext(state); - } - else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); - else if (/atom|string|variable/.test(style) && state.context) { - if (/[\}\]]/.test(state.context.type)) - pushContext(state, "pattern", stream.column()); - else if (state.context.type == "pattern" && !state.context.align) { - state.context.align = true; - state.context.col = stream.column(); - } - } - - return style; - }, - - indent: function(state, textAfter) { - var firstChar = textAfter && textAfter.charAt(0); - var context = state.context; - if (/[\]\}]/.test(firstChar)) - while (context && context.type == "pattern") context = context.prev; - - var closing = context && firstChar == context.type; - if (!context) - return 0; - else if (context.type == "pattern") - return context.col; - else if (context.align) - return context.col + (closing ? 0 : 1); - else - return context.indent + (closing ? 0 : indentUnit); - } - }; -}); - -CodeMirror.defineMIME("application/x-sparql-query", "sparql"); diff --git a/applications/admin/static/codemirror/mode/sql/index.html b/applications/admin/static/codemirror/mode/sql/index.html deleted file mode 100644 index 0dbdb672..00000000 --- a/applications/admin/static/codemirror/mode/sql/index.html +++ /dev/null @@ -1,74 +0,0 @@ - - -CodeMirror: SQL Mode for CodeMirror - - - - - - - - - -
-

SQL Mode for CodeMirror

-
- -
-

MIME types defined: - text/x-sql, - text/x-mysql, - text/x-mariadb, - text/x-cassandra, - text/x-plsql. -

- - -
diff --git a/applications/admin/static/codemirror/mode/sql/sql.js b/applications/admin/static/codemirror/mode/sql/sql.js deleted file mode 100644 index 63ce3fa8..00000000 --- a/applications/admin/static/codemirror/mode/sql/sql.js +++ /dev/null @@ -1,352 +0,0 @@ -CodeMirror.defineMode("sql", function(config, parserConfig) { - "use strict"; - - var client = parserConfig.client || {}, - atoms = parserConfig.atoms || {"false": true, "true": true, "null": true}, - builtin = parserConfig.builtin || {}, - keywords = parserConfig.keywords || {}, - operatorChars = parserConfig.operatorChars || /^[*+\-%<>!=&|~^]/, - support = parserConfig.support || {}, - hooks = parserConfig.hooks || {}, - dateSQL = parserConfig.dateSQL || {"date" : true, "time" : true, "timestamp" : true}; - - function tokenBase(stream, state) { - var ch = stream.next(); - - // call hooks from the mime type - if (hooks[ch]) { - var result = hooks[ch](stream, state); - if (result !== false) return result; - } - - if (support.hexNumber == true && - ((ch == "0" && stream.match(/^[xX][0-9a-fA-F]+/)) - || (ch == "x" || ch == "X") && stream.match(/^'[0-9a-fA-F]+'/))) { - // hex - // ref: http://dev.mysql.com/doc/refman/5.5/en/hexadecimal-literals.html - return "number"; - } else if (support.binaryNumber == true && - (((ch == "b" || ch == "B") && stream.match(/^'[01]+'/)) - || (ch == "0" && stream.match(/^b[01]+/)))) { - // bitstring - // ref: http://dev.mysql.com/doc/refman/5.5/en/bit-field-literals.html - return "number"; - } else if (ch.charCodeAt(0) > 47 && ch.charCodeAt(0) < 58) { - // numbers - // ref: http://dev.mysql.com/doc/refman/5.5/en/number-literals.html - stream.match(/^[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?/); - support.decimallessFloat == true && stream.eat('.'); - return "number"; - } else if (ch == "?" && (stream.eatSpace() || stream.eol() || stream.eat(";"))) { - // placeholders - return "variable-3"; - } else if (ch == "'" || (ch == '"' && support.doubleQuote)) { - // strings - // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html - state.tokenize = tokenLiteral(ch); - return state.tokenize(stream, state); - } else if ((((support.nCharCast == true && (ch == "n" || ch == "N")) - || (support.charsetCast == true && ch == "_" && stream.match(/[a-z][a-z0-9]*/i))) - && (stream.peek() == "'" || stream.peek() == '"'))) { - // charset casting: _utf8'str', N'str', n'str' - // ref: http://dev.mysql.com/doc/refman/5.5/en/string-literals.html - return "keyword"; - } else if (/^[\(\),\;\[\]]/.test(ch)) { - // no highlightning - return null; - } else if (support.commentSlashSlash && ch == "/" && stream.eat("/")) { - // 1-line comment - stream.skipToEnd(); - return "comment"; - } else if ((support.commentHash && ch == "#") - || (ch == "-" && stream.eat("-") && (!support.commentSpaceRequired || stream.eat(" ")))) { - // 1-line comments - // ref: https://kb.askmonty.org/en/comment-syntax/ - stream.skipToEnd(); - return "comment"; - } else if (ch == "/" && stream.eat("*")) { - // multi-line comments - // ref: https://kb.askmonty.org/en/comment-syntax/ - state.tokenize = tokenComment; - return state.tokenize(stream, state); - } else if (ch == ".") { - // .1 for 0.1 - if (support.zerolessFloat == true && stream.match(/^(?:\d+(?:e[+-]?\d+)?)/i)) { - return "number"; - } - // .table_name (ODBC) - // // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html - if (support.ODBCdotTable == true && stream.match(/^[a-zA-Z_]+/)) { - return "variable-2"; - } - } else if (operatorChars.test(ch)) { - // operators - stream.eatWhile(operatorChars); - return null; - } else if (ch == '{' && - (stream.match(/^( )*(d|D|t|T|ts|TS)( )*'[^']*'( )*}/) || stream.match(/^( )*(d|D|t|T|ts|TS)( )*"[^"]*"( )*}/))) { - // dates (weird ODBC syntax) - // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html - return "number"; - } else { - stream.eatWhile(/^[_\w\d]/); - var word = stream.current().toLowerCase(); - // dates (standard SQL syntax) - // ref: http://dev.mysql.com/doc/refman/5.5/en/date-and-time-literals.html - if (dateSQL.hasOwnProperty(word) && (stream.match(/^( )+'[^']*'/) || stream.match(/^( )+"[^"]*"/))) - return "number"; - if (atoms.hasOwnProperty(word)) return "atom"; - if (builtin.hasOwnProperty(word)) return "builtin"; - if (keywords.hasOwnProperty(word)) return "keyword"; - if (client.hasOwnProperty(word)) return "string-2"; - return null; - } - } - - // 'string', with char specified in quote escaped by '\' - function tokenLiteral(quote) { - return function(stream, state) { - var escaped = false, ch; - while ((ch = stream.next()) != null) { - if (ch == quote && !escaped) { - state.tokenize = tokenBase; - break; - } - escaped = !escaped && ch == "\\"; - } - return "string"; - }; - } - function tokenComment(stream, state) { - while (true) { - if (stream.skipTo("*")) { - stream.next(); - if (stream.eat("/")) { - state.tokenize = tokenBase; - break; - } - } else { - stream.skipToEnd(); - break; - } - } - return "comment"; - } - - function pushContext(stream, state, type) { - state.context = { - prev: state.context, - indent: stream.indentation(), - col: stream.column(), - type: type - }; - } - - function popContext(state) { - state.indent = state.context.indent; - state.context = state.context.prev; - } - - return { - startState: function() { - return {tokenize: tokenBase, context: null}; - }, - - token: function(stream, state) { - if (stream.sol()) { - if (state.context && state.context.align == null) - state.context.align = false; - } - if (stream.eatSpace()) return null; - - var style = state.tokenize(stream, state); - if (style == "comment") return style; - - if (state.context && state.context.align == null) - state.context.align = true; - - var tok = stream.current(); - if (tok == "(") - pushContext(stream, state, ")"); - else if (tok == "[") - pushContext(stream, state, "]"); - else if (state.context && state.context.type == tok) - popContext(state); - return style; - }, - - indent: function(state, textAfter) { - var cx = state.context; - if (!cx) return CodeMirror.Pass; - if (cx.align) return cx.col + (textAfter.charAt(0) == cx.type ? 0 : 1); - else return cx.indent + config.indentUnit; - }, - - blockCommentStart: "/*", - blockCommentEnd: "*/", - lineComment: support.commentSlashSlash ? "//" : support.commentHash ? "#" : null - }; -}); - -(function() { - "use strict"; - - // `identifier` - function hookIdentifier(stream) { - // MySQL/MariaDB identifiers - // ref: http://dev.mysql.com/doc/refman/5.6/en/identifier-qualifiers.html - var ch; - while ((ch = stream.next()) != null) { - if (ch == "`" && !stream.eat("`")) return "variable-2"; - } - return null; - } - - // variable token - function hookVar(stream) { - // variables - // @@prefix.varName @varName - // varName can be quoted with ` or ' or " - // ref: http://dev.mysql.com/doc/refman/5.5/en/user-variables.html - if (stream.eat("@")) { - stream.match(/^session\./); - stream.match(/^local\./); - stream.match(/^global\./); - } - - if (stream.eat("'")) { - stream.match(/^.*'/); - return "variable-2"; - } else if (stream.eat('"')) { - stream.match(/^.*"/); - return "variable-2"; - } else if (stream.eat("`")) { - stream.match(/^.*`/); - return "variable-2"; - } else if (stream.match(/^[0-9a-zA-Z$\.\_]+/)) { - return "variable-2"; - } - return null; - }; - - // short client keyword token - function hookClient(stream) { - // \N means NULL - // ref: http://dev.mysql.com/doc/refman/5.5/en/null-values.html - if (stream.eat("N")) { - return "atom"; - } - // \g, etc - // ref: http://dev.mysql.com/doc/refman/5.5/en/mysql-commands.html - return stream.match(/^[a-zA-Z.#!?]/) ? "variable-2" : null; - } - - // these keywords are used by all SQL dialects (however, a mode can still overwrite it) - var sqlKeywords = "alter and as asc between by count create delete desc distinct drop from having in insert into is join like not on or order select set table union update values where "; - - // turn a space-separated list into an array - function set(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - - // A generic SQL Mode. It's not a standard, it just try to support what is generally supported - CodeMirror.defineMIME("text/x-sql", { - name: "sql", - keywords: set(sqlKeywords + "begin"), - builtin: set("bool boolean bit blob enum long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision real date datetime year unsigned signed decimal numeric"), - atoms: set("false true null unknown"), - operatorChars: /^[*+\-%<>!=]/, - dateSQL: set("date time timestamp"), - support: set("ODBCdotTable doubleQuote binaryNumber hexNumber") - }); - - CodeMirror.defineMIME("text/x-mysql", { - name: "sql", - client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), - keywords: set(sqlKeywords + "accessible action add after algorithm all analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general global grant grants group groupby_concat handler hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"), - builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"), - atoms: set("false true null unknown"), - operatorChars: /^[*+\-%<>!=&|^]/, - dateSQL: set("date time timestamp"), - support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"), - hooks: { - "@": hookVar, - "`": hookIdentifier, - "\\": hookClient - } - }); - - CodeMirror.defineMIME("text/x-mariadb", { - name: "sql", - client: set("charset clear connect edit ego exit go help nopager notee nowarning pager print prompt quit rehash source status system tee"), - keywords: set(sqlKeywords + "accessible action add after algorithm all always analyze asensitive at authors auto_increment autocommit avg avg_row_length before binary binlog both btree cache call cascade cascaded case catalog_name chain change changed character check checkpoint checksum class_origin client_statistics close coalesce code collate collation collations column columns comment commit committed completion concurrent condition connection consistent constraint contains continue contributors convert cross current_date current_time current_timestamp current_user cursor data database databases day_hour day_microsecond day_minute day_second deallocate dec declare default delay_key_write delayed delimiter des_key_file describe deterministic dev_pop dev_samp deviance directory disable discard distinctrow div dual dumpfile each elseif enable enclosed end ends engine engines enum errors escape escaped even event events every execute exists exit explain extended fast fetch field fields first flush for force foreign found_rows full fulltext function general generated global grant grants group groupby_concat handler hard hash help high_priority hosts hour_microsecond hour_minute hour_second if ignore ignore_server_ids import index index_statistics infile inner innodb inout insensitive insert_method install interval invoker isolation iterate key keys kill language last leading leave left level limit linear lines list load local localtime localtimestamp lock logs low_priority master master_heartbeat_period master_ssl_verify_server_cert masters match max max_rows maxvalue message_text middleint migrate min min_rows minute_microsecond minute_second mod mode modifies modify mutex mysql_errno natural next no no_write_to_binlog offline offset one online open optimize option optionally out outer outfile pack_keys parser partition partitions password persistent phase plugin plugins prepare preserve prev primary privileges procedure processlist profile profiles purge query quick range read read_write reads real rebuild recover references regexp relaylog release remove rename reorganize repair repeatable replace require resignal restrict resume return returns revoke right rlike rollback rollup row row_format rtree savepoint schedule schema schema_name schemas second_microsecond security sensitive separator serializable server session share show signal slave slow smallint snapshot soft soname spatial specific sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sqlexception sqlstate sqlwarning ssl start starting starts status std stddev stddev_pop stddev_samp storage straight_join subclass_origin sum suspend table_name table_statistics tables tablespace temporary terminated to trailing transaction trigger triggers truncate uncommitted undo uninstall unique unlock upgrade usage use use_frm user user_resources user_statistics using utc_date utc_time utc_timestamp value variables varying view views virtual warnings when while with work write xa xor year_month zerofill begin do then else loop repeat"), - builtin: set("bool boolean bit blob decimal double float long longblob longtext medium mediumblob mediumint mediumtext time timestamp tinyblob tinyint tinytext text bigint int int1 int2 int3 int4 int8 integer float float4 float8 double char varbinary varchar varcharacter precision date datetime year unsigned signed numeric"), - atoms: set("false true null unknown"), - operatorChars: /^[*+\-%<>!=&|^]/, - dateSQL: set("date time timestamp"), - support: set("ODBCdotTable decimallessFloat zerolessFloat binaryNumber hexNumber doubleQuote nCharCast charsetCast commentHash commentSpaceRequired"), - hooks: { - "@": hookVar, - "`": hookIdentifier, - "\\": hookClient - } - }); - - // the query language used by Apache Cassandra is called CQL, but this mime type - // is called Cassandra to avoid confusion with Contextual Query Language - CodeMirror.defineMIME("text/x-cassandra", { - name: "sql", - client: { }, - keywords: set("use select from using consistency where limit first reversed first and in insert into values using consistency ttl update set delete truncate begin batch apply create keyspace with columnfamily primary key index on drop alter type add any one quorum all local_quorum each_quorum"), - builtin: set("ascii bigint blob boolean counter decimal double float int text timestamp uuid varchar varint"), - atoms: set("false true"), - operatorChars: /^[<>=]/, - dateSQL: { }, - support: set("commentSlashSlash decimallessFloat"), - hooks: { } - }); - - // this is based on Peter Raganitsch's 'plsql' mode - CodeMirror.defineMIME("text/x-plsql", { - name: "sql", - client: set("appinfo arraysize autocommit autoprint autorecovery autotrace blockterminator break btitle cmdsep colsep compatibility compute concat copycommit copytypecheck define describe echo editfile embedded escape exec execute feedback flagger flush heading headsep instance linesize lno loboffset logsource long longchunksize markup native newpage numformat numwidth pagesize pause pno recsep recsepchar release repfooter repheader serveroutput shiftinout show showmode size spool sqlblanklines sqlcase sqlcode sqlcontinue sqlnumber sqlpluscompatibility sqlprefix sqlprompt sqlterminator suffix tab term termout time timing trimout trimspool ttitle underline verify version wrap"), - keywords: set("abort accept access add all alter and any array arraylen as asc assert assign at attributes audit authorization avg base_table begin between binary_integer body boolean by case cast char char_base check close cluster clusters colauth column comment commit compress connect connected constant constraint crash create current currval cursor data_base database date dba deallocate debugoff debugon decimal declare default definition delay delete desc digits dispose distinct do drop else elsif enable end entry escape exception exception_init exchange exclusive exists exit external fast fetch file for force form from function generic goto grant group having identified if immediate in increment index indexes indicator initial initrans insert interface intersect into is key level library like limited local lock log logging long loop master maxextents maxtrans member minextents minus mislabel mode modify multiset new next no noaudit nocompress nologging noparallel not nowait number_base object of off offline on online only open option or order out package parallel partition pctfree pctincrease pctused pls_integer positive positiven pragma primary prior private privileges procedure public raise range raw read rebuild record ref references refresh release rename replace resource restrict return returning reverse revoke rollback row rowid rowlabel rownum rows run savepoint schema segment select separate session set share snapshot some space split sql start statement storage subtype successful synonym tabauth table tables tablespace task terminate then to trigger truncate type union unique unlimited unrecoverable unusable update use using validate value values variable view views when whenever where while with work"), - builtin: set("bfile blob character clob dec float int integer mlslabel natural naturaln nchar nclob number numeric nvarchar2 real rowtype signtype smallint string varchar varchar2 abs acos add_months ascii asin atan atan2 average bfilename ceil chartorowid chr concat convert cos cosh count decode deref dual dump dup_val_on_index empty error exp false floor found glb greatest hextoraw initcap instr instrb isopen last_day least lenght lenghtb ln lower lpad ltrim lub make_ref max min mod months_between new_time next_day nextval nls_charset_decl_len nls_charset_id nls_charset_name nls_initcap nls_lower nls_sort nls_upper nlssort no_data_found notfound null nvl others power rawtohex reftohex round rowcount rowidtochar rpad rtrim sign sin sinh soundex sqlcode sqlerrm sqrt stddev substr substrb sum sysdate tan tanh to_char to_date to_label to_multi_byte to_number to_single_byte translate true trunc uid upper user userenv variance vsize"), - operatorChars: /^[*+\-%<>!=~]/, - dateSQL: set("date time timestamp"), - support: set("doubleQuote nCharCast zerolessFloat binaryNumber hexNumber") - }); -}()); - -/* - How Properties of Mime Types are used by SQL Mode - ================================================= - - keywords: - A list of keywords you want to be highlighted. - functions: - A list of function names you want to be highlighted. - builtin: - A list of builtin types you want to be highlighted (if you want types to be of class "builtin" instead of "keyword"). - operatorChars: - All characters that must be handled as operators. - client: - Commands parsed and executed by the client (not the server). - support: - A list of supported syntaxes which are not common, but are supported by more than 1 DBMS. - * ODBCdotTable: .tableName - * zerolessFloat: .1 - * doubleQuote - * nCharCast: N'string' - * charsetCast: _utf8'string' - * commentHash: use # char for comments - * commentSlashSlash: use // for comments - * commentSpaceRequired: require a space after -- for comments - atoms: - Keywords that must be highlighted as atoms,. Some DBMS's support more atoms than others: - UNKNOWN, INFINITY, UNDERFLOW, NaN... - dateSQL: - Used for date/time SQL standard syntax, because not all DBMS's support same temporal types. -*/ diff --git a/applications/admin/static/codemirror/mode/tcl/index.html b/applications/admin/static/codemirror/mode/tcl/index.html deleted file mode 100644 index 2c0d1a2d..00000000 --- a/applications/admin/static/codemirror/mode/tcl/index.html +++ /dev/null @@ -1,143 +0,0 @@ - - -CodeMirror: Tcl mode - - - - - - - - - - -
-

Tcl mode

-
- - -

MIME types defined: text/x-tcl.

- -
diff --git a/applications/admin/static/codemirror/mode/tcl/tcl.js b/applications/admin/static/codemirror/mode/tcl/tcl.js deleted file mode 100644 index ed2c6972..00000000 --- a/applications/admin/static/codemirror/mode/tcl/tcl.js +++ /dev/null @@ -1,131 +0,0 @@ -//tcl mode by Ford_Lawnmower :: Based on Velocity mode by Steve O'Hara -CodeMirror.defineMode("tcl", function() { - function parseWords(str) { - var obj = {}, words = str.split(" "); - for (var i = 0; i < words.length; ++i) obj[words[i]] = true; - return obj; - } - var keywords = parseWords("Tcl safe after append array auto_execok auto_import auto_load " + - "auto_mkindex auto_mkindex_old auto_qualify auto_reset bgerror " + - "binary break catch cd close concat continue dde eof encoding error " + - "eval exec exit expr fblocked fconfigure fcopy file fileevent filename " + - "filename flush for foreach format gets glob global history http if " + - "incr info interp join lappend lindex linsert list llength load lrange " + - "lreplace lsearch lset lsort memory msgcat namespace open package parray " + - "pid pkg::create pkg_mkIndex proc puts pwd re_syntax read regex regexp " + - "registry regsub rename resource return scan seek set socket source split " + - "string subst switch tcl_endOfWord tcl_findLibrary tcl_startOfNextWord " + - "tcl_wordBreakAfter tcl_startOfPreviousWord tcl_wordBreakBefore tcltest " + - "tclvars tell time trace unknown unset update uplevel upvar variable " + - "vwait"); - var functions = parseWords("if elseif else and not or eq ne in ni for foreach while switch"); - var isOperatorChar = /[+\-*&%=<>!?^\/\|]/; - function chain(stream, state, f) { - state.tokenize = f; - return f(stream, state); - } - function tokenBase(stream, state) { - var beforeParams = state.beforeParams; - state.beforeParams = false; - var ch = stream.next(); - if ((ch == '"' || ch == "'") && state.inParams) - return chain(stream, state, tokenString(ch)); - else if (/[\[\]{}\(\),;\.]/.test(ch)) { - if (ch == "(" && beforeParams) state.inParams = true; - else if (ch == ")") state.inParams = false; - return null; - } - else if (/\d/.test(ch)) { - stream.eatWhile(/[\w\.]/); - return "number"; - } - else if (ch == "#" && stream.eat("*")) { - return chain(stream, state, tokenComment); - } - else if (ch == "#" && stream.match(/ *\[ *\[/)) { - return chain(stream, state, tokenUnparsed); - } - else if (ch == "#" && stream.eat("#")) { - stream.skipToEnd(); - return "comment"; - } - else if (ch == '"') { - stream.skipTo(/"/); - return "comment"; - } - else if (ch == "$") { - stream.eatWhile(/[$_a-z0-9A-Z\.{:]/); - stream.eatWhile(/}/); - state.beforeParams = true; - return "builtin"; - } - else if (isOperatorChar.test(ch)) { - stream.eatWhile(isOperatorChar); - return "comment"; - } - else { - stream.eatWhile(/[\w\$_{}]/); - var word = stream.current().toLowerCase(); - if (keywords && keywords.propertyIsEnumerable(word)) - return "keyword"; - if (functions && functions.propertyIsEnumerable(word)) { - state.beforeParams = true; - return "keyword"; - } - return null; - } - } - function tokenString(quote) { - return function(stream, state) { - var escaped = false, next, end = false; - while ((next = stream.next()) != null) { - if (next == quote && !escaped) { - end = true; - break; - } - escaped = !escaped && next == "\\"; - } - if (end) state.tokenize = tokenBase; - return "string"; - }; - } - function tokenComment(stream, state) { - var maybeEnd = false, ch; - while (ch = stream.next()) { - if (ch == "#" && maybeEnd) { - state.tokenize = tokenBase; - break; - } - maybeEnd = (ch == "*"); - } - return "comment"; - } - function tokenUnparsed(stream, state) { - var maybeEnd = 0, ch; - while (ch = stream.next()) { - if (ch == "#" && maybeEnd == 2) { - state.tokenize = tokenBase; - break; - } - if (ch == "]") - maybeEnd++; - else if (ch != " ") - maybeEnd = 0; - } - return "meta"; - } - return { - startState: function() { - return { - tokenize: tokenBase, - beforeParams: false, - inParams: false - }; - }, - token: function(stream, state) { - if (stream.eatSpace()) return null; - return state.tokenize(stream, state); - } - }; -}); -CodeMirror.defineMIME("text/x-tcl", "tcl"); diff --git a/applications/admin/static/codemirror/mode/xml/test.js b/applications/admin/static/codemirror/mode/xml/test.js new file mode 100644 index 00000000..f48156b5 --- /dev/null +++ b/applications/admin/static/codemirror/mode/xml/test.js @@ -0,0 +1,51 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var mode = CodeMirror.getMode({indentUnit: 2}, "xml"), mname = "xml"; + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), mname); } + + MT("matching", + "[tag&bracket <][tag top][tag&bracket >]", + " text", + " [tag&bracket <][tag inner][tag&bracket />]", + "[tag&bracket ]"); + + MT("nonmatching", + "[tag&bracket <][tag top][tag&bracket >]", + " [tag&bracket <][tag inner][tag&bracket />]", + " [tag&bracket ]"); + + MT("doctype", + "[meta ]", + "[tag&bracket <][tag top][tag&bracket />]"); + + MT("cdata", + "[tag&bracket <][tag top][tag&bracket >]", + " [atom ]", + "[tag&bracket ]"); + + // HTML tests + mode = CodeMirror.getMode({indentUnit: 2}, "text/html"); + + MT("selfclose", + "[tag&bracket <][tag html][tag&bracket >]", + " [tag&bracket <][tag link] [attribute rel]=[string stylesheet] [attribute href]=[string \"/foobar\"][tag&bracket >]", + "[tag&bracket ]"); + + MT("list", + "[tag&bracket <][tag ol][tag&bracket >]", + " [tag&bracket <][tag li][tag&bracket >]one", + " [tag&bracket <][tag li][tag&bracket >]two", + "[tag&bracket ]"); + + MT("valueless", + "[tag&bracket <][tag input] [attribute type]=[string checkbox] [attribute checked][tag&bracket />]"); + + MT("pThenArticle", + "[tag&bracket <][tag p][tag&bracket >]", + " foo", + "[tag&bracket <][tag article][tag&bracket >]bar"); + +})(); diff --git a/applications/admin/static/codemirror/mode/xml/xml.js b/applications/admin/static/codemirror/mode/xml/xml.js index 880b74d8..2f3b8f87 100644 --- a/applications/admin/static/codemirror/mode/xml/xml.js +++ b/applications/admin/static/codemirror/mode/xml/xml.js @@ -1,3 +1,6 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + (function(mod) { if (typeof exports == "object" && typeof module == "object") // CommonJS mod(require("../../lib/codemirror")); @@ -18,7 +21,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, - 'track': true, 'wbr': true}, + 'track': true, 'wbr': true, 'menuitem': true}, implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, 'th': true, 'tr': true}, @@ -58,7 +61,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { var alignCDATA = parserConfig.alignCDATA; // Return variables for tokenizers - var tagName, type, setStyle; + var type, setStyle; function inText(stream, state) { function chain(parser) { @@ -85,15 +88,9 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { state.tokenize = inBlock("meta", "?>"); return "meta"; } else { - var isClose = stream.eat("/"); - tagName = ""; - var c; - while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; - if (Kludges.caseFold) tagName = tagName.toLowerCase(); - if (!tagName) return "tag error"; - type = isClose ? "closeTag" : "openTag"; + type = stream.eat("/") ? "closeTag" : "openTag"; state.tokenize = inTag; - return "tag"; + return "tag bracket"; } } else if (ch == "&") { var ok; @@ -118,7 +115,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { if (ch == ">" || (ch == "/" && stream.eat(">"))) { state.tokenize = inText; type = ch == ">" ? "endTag" : "selfcloseTag"; - return "tag"; + return "tag bracket"; } else if (ch == "=") { type = "equals"; return null; @@ -127,13 +124,13 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { state.state = baseState; state.tagName = state.tagStart = null; var next = state.tokenize(stream, state); - return next ? next + " error" : "error"; + return next ? next + " tag error" : "tag error"; } else if (/[\'\"]/.test(ch)) { state.tokenize = inAttribute(ch); state.stringStartCol = stream.column(); return state.tokenize(stream, state); } else { - stream.eatWhile(/[^\s\u00a0=<>\"\']/); + stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/); return "word"; } } @@ -213,26 +210,42 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { function baseState(type, stream, state) { if (type == "openTag") { - state.tagName = tagName; state.tagStart = stream.column(); - return attrState; + return tagNameState; } else if (type == "closeTag") { - var err = false; - if (state.context) { - if (state.context.tagName != tagName) { - if (Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName)) - popContext(state); - err = !state.context || state.context.tagName != tagName; - } - } else { - err = true; - } - if (err) setStyle = "error"; - return err ? closeStateErr : closeState; + return closeTagNameState; } else { return baseState; } } + function tagNameState(type, stream, state) { + if (type == "word") { + state.tagName = stream.current(); + setStyle = "tag"; + return attrState; + } else { + setStyle = "error"; + return tagNameState; + } + } + function closeTagNameState(type, stream, state) { + if (type == "word") { + var tagName = stream.current(); + if (state.context && state.context.tagName != tagName && + Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName)) + popContext(state); + if (state.context && state.context.tagName == tagName) { + setStyle = "tag"; + return closeState; + } else { + setStyle = "tag error"; + return closeStateErr; + } + } else { + setStyle = "error"; + return closeStateErr; + } + } function closeState(type, _stream, state) { if (type != "endTag") { @@ -296,7 +309,7 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { state.indented = stream.indentation(); if (stream.eatSpace()) return null; - tagName = type = null; + type = null; var style = state.tokenize(stream, state); if ((style || type) && style != "comment") { setStyle = null; @@ -311,7 +324,10 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { var context = state.context; // Indent multi-line strings (e.g. css). if (state.tokenize.isInAttribute) { - return state.stringStartCol + 1; + if (state.tagStart == state.indented) + return state.stringStartCol + 1; + else + return state.indented + indentUnit; } if (context && context.noIndent) return CodeMirror.Pass; if (state.tokenize != inTag && state.tokenize != inText) @@ -324,15 +340,34 @@ CodeMirror.defineMode("xml", function(config, parserConfig) { return state.tagStart + indentUnit * multilineTagIndentFactor; } if (alignCDATA && /$/, blockCommentStart: "", diff --git a/applications/admin/static/codemirror/mode/xquery/LICENSE b/applications/admin/static/codemirror/mode/xquery/LICENSE deleted file mode 100644 index 2a2d47be..00000000 --- a/applications/admin/static/codemirror/mode/xquery/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (C) 2011 by MarkLogic Corporation -Author: Mike Brevoort - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/applications/admin/static/codemirror/mode/xquery/index.html b/applications/admin/static/codemirror/mode/xquery/index.html deleted file mode 100644 index 3baf5e3e..00000000 --- a/applications/admin/static/codemirror/mode/xquery/index.html +++ /dev/null @@ -1,210 +0,0 @@ - - -CodeMirror: XQuery mode - - - - - - - - - - -
-

XQuery mode

- - -
- -
- - - -

MIME types defined: application/xquery.

- -

Development of the CodeMirror XQuery mode was sponsored by - MarkLogic and developed by - Mike Brevoort. -

- -
diff --git a/applications/admin/static/codemirror/mode/xquery/test.js b/applications/admin/static/codemirror/mode/xquery/test.js deleted file mode 100644 index 41719dd1..00000000 --- a/applications/admin/static/codemirror/mode/xquery/test.js +++ /dev/null @@ -1,64 +0,0 @@ -// Don't take these too seriously -- the expected results appear to be -// based on the results of actual runs without any serious manual -// verification. If a change you made causes them to fail, the test is -// as likely to wrong as the code. - -(function() { - var mode = CodeMirror.getMode({tabSize: 4}, "xquery"); - function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } - - MT("eviltest", - "[keyword xquery] [keyword version] [variable "1][keyword .][atom 0][keyword -][variable ml"][def&variable ;] [comment (: this is : a \"comment\" :)]", - " [keyword let] [variable $let] [keyword :=] [variable <x] [variable attr][keyword =][variable "value">"test"<func>][def&variable ;function]() [variable $var] {[keyword function]()} {[variable $var]}[variable <][keyword /][variable func><][keyword /][variable x>]", - " [keyword let] [variable $joe][keyword :=][atom 1]", - " [keyword return] [keyword element] [variable element] {", - " [keyword attribute] [variable attribute] { [atom 1] },", - " [keyword element] [variable test] { [variable 'a'] }, [keyword attribute] [variable foo] { [variable "bar"] },", - " [def&variable fn:doc]()[[ [variable foo][keyword /][variable @bar] [keyword eq] [variable $let] ]],", - " [keyword //][variable x] } [comment (: a more 'evil' test :)]", - " [comment (: Modified Blakeley example (: with nested comment :) ... :)]", - " [keyword declare] [keyword private] [keyword function] [def&variable local:declare]() {()}[variable ;]", - " [keyword declare] [keyword private] [keyword function] [def&variable local:private]() {()}[variable ;]", - " [keyword declare] [keyword private] [keyword function] [def&variable local:function]() {()}[variable ;]", - " [keyword declare] [keyword private] [keyword function] [def&variable local:local]() {()}[variable ;]", - " [keyword let] [variable $let] [keyword :=] [variable <let>let] [variable $let] [keyword :=] [variable "let"<][keyword /let][variable >]", - " [keyword return] [keyword element] [variable element] {", - " [keyword attribute] [variable attribute] { [keyword try] { [def&variable xdmp:version]() } [keyword catch]([variable $e]) { [def&variable xdmp:log]([variable $e]) } },", - " [keyword attribute] [variable fn:doc] { [variable "bar"] [variable castable] [keyword as] [atom xs:string] },", - " [keyword element] [variable text] { [keyword text] { [variable "text"] } },", - " [def&variable fn:doc]()[[ [qualifier child::][variable eq][keyword /]([variable @bar] [keyword |] [qualifier attribute::][variable attribute]) [keyword eq] [variable $let] ]],", - " [keyword //][variable fn:doc]", - " }"); - - MT("testEmptySequenceKeyword", - "[string \"foo\"] [keyword instance] [keyword of] [keyword empty-sequence]()"); - - MT("testMultiAttr", - "[tag

][variable hello] [variable world][tag

]"); - - MT("test namespaced variable", - "[keyword declare] [keyword namespace] [variable e] [keyword =] [string \"http://example.com/ANamespace\"][variable ;declare] [keyword variable] [variable $e:exampleComThisVarIsNotRecognized] [keyword as] [keyword element]([keyword *]) [variable external;]"); - - MT("test EQName variable", - "[keyword declare] [keyword variable] [variable $\"http://www.example.com/ns/my\":var] [keyword :=] [atom 12][variable ;]", - "[tag ]{[variable $\"http://www.example.com/ns/my\":var]}[tag ]"); - - MT("test EQName function", - "[keyword declare] [keyword function] [def&variable \"http://www.example.com/ns/my\":fn] ([variable $a] [keyword as] [atom xs:integer]) [keyword as] [atom xs:integer] {", - " [variable $a] [keyword +] [atom 2]", - "}[variable ;]", - "[tag ]{[def&variable \"http://www.example.com/ns/my\":fn]([atom 12])}[tag ]"); - - MT("test EQName function with single quotes", - "[keyword declare] [keyword function] [def&variable 'http://www.example.com/ns/my':fn] ([variable $a] [keyword as] [atom xs:integer]) [keyword as] [atom xs:integer] {", - " [variable $a] [keyword +] [atom 2]", - "}[variable ;]", - "[tag ]{[def&variable 'http://www.example.com/ns/my':fn]([atom 12])}[tag ]"); - - MT("testProcessingInstructions", - "[def&variable data]([comment&meta ]) [keyword instance] [keyword of] [atom xs:string]"); - - MT("testQuoteEscapeDouble", - "[keyword let] [variable $rootfolder] [keyword :=] [string \"c:\\builds\\winnt\\HEAD\\qa\\scripts\\\"]", - "[keyword let] [variable $keysfolder] [keyword :=] [def&variable concat]([variable $rootfolder], [string \"keys\\\"])"); -})(); diff --git a/applications/admin/static/codemirror/mode/xquery/xquery.js b/applications/admin/static/codemirror/mode/xquery/xquery.js deleted file mode 100644 index be179846..00000000 --- a/applications/admin/static/codemirror/mode/xquery/xquery.js +++ /dev/null @@ -1,432 +0,0 @@ -CodeMirror.defineMode("xquery", function() { - - // The keywords object is set to the result of this self executing - // function. Each keyword is a property of the keywords object whose - // value is {type: atype, style: astyle} - var keywords = function(){ - // conveinence functions used to build keywords object - function kw(type) {return {type: type, style: "keyword"};} - var A = kw("keyword a") - , B = kw("keyword b") - , C = kw("keyword c") - , operator = kw("operator") - , atom = {type: "atom", style: "atom"} - , punctuation = {type: "punctuation", style: null} - , qualifier = {type: "axis_specifier", style: "qualifier"}; - - // kwObj is what is return from this function at the end - var kwObj = { - 'if': A, 'switch': A, 'while': A, 'for': A, - 'else': B, 'then': B, 'try': B, 'finally': B, 'catch': B, - 'element': C, 'attribute': C, 'let': C, 'implements': C, 'import': C, 'module': C, 'namespace': C, - 'return': C, 'super': C, 'this': C, 'throws': C, 'where': C, 'private': C, - ',': punctuation, - 'null': atom, 'fn:false()': atom, 'fn:true()': atom - }; - - // a list of 'basic' keywords. For each add a property to kwObj with the value of - // {type: basic[i], style: "keyword"} e.g. 'after' --> {type: "after", style: "keyword"} - var basic = ['after','ancestor','ancestor-or-self','and','as','ascending','assert','attribute','before', - 'by','case','cast','child','comment','declare','default','define','descendant','descendant-or-self', - 'descending','document','document-node','element','else','eq','every','except','external','following', - 'following-sibling','follows','for','function','if','import','in','instance','intersect','item', - 'let','module','namespace','node','node','of','only','or','order','parent','precedes','preceding', - 'preceding-sibling','processing-instruction','ref','return','returns','satisfies','schema','schema-element', - 'self','some','sortby','stable','text','then','to','treat','typeswitch','union','variable','version','where', - 'xquery', 'empty-sequence']; - for(var i=0, l=basic.length; i < l; i++) { kwObj[basic[i]] = kw(basic[i]);}; - - // a list of types. For each add a property to kwObj with the value of - // {type: "atom", style: "atom"} - var types = ['xs:string', 'xs:float', 'xs:decimal', 'xs:double', 'xs:integer', 'xs:boolean', 'xs:date', 'xs:dateTime', - 'xs:time', 'xs:duration', 'xs:dayTimeDuration', 'xs:time', 'xs:yearMonthDuration', 'numeric', 'xs:hexBinary', - 'xs:base64Binary', 'xs:anyURI', 'xs:QName', 'xs:byte','xs:boolean','xs:anyURI','xf:yearMonthDuration']; - for(var i=0, l=types.length; i < l; i++) { kwObj[types[i]] = atom;}; - - // each operator will add a property to kwObj with value of {type: "operator", style: "keyword"} - var operators = ['eq', 'ne', 'lt', 'le', 'gt', 'ge', ':=', '=', '>', '>=', '<', '<=', '.', '|', '?', 'and', 'or', 'div', 'idiv', 'mod', '*', '/', '+', '-']; - for(var i=0, l=operators.length; i < l; i++) { kwObj[operators[i]] = operator;}; - - // each axis_specifiers will add a property to kwObj with value of {type: "axis_specifier", style: "qualifier"} - var axis_specifiers = ["self::", "attribute::", "child::", "descendant::", "descendant-or-self::", "parent::", - "ancestor::", "ancestor-or-self::", "following::", "preceding::", "following-sibling::", "preceding-sibling::"]; - for(var i=0, l=axis_specifiers.length; i < l; i++) { kwObj[axis_specifiers[i]] = qualifier; }; - - return kwObj; - }(); - - // Used as scratch variables to communicate multiple values without - // consing up tons of objects. - var type, content; - - function ret(tp, style, cont) { - type = tp; content = cont; - return style; - } - - function chain(stream, state, f) { - state.tokenize = f; - return f(stream, state); - } - - // the primary mode tokenizer - function tokenBase(stream, state) { - var ch = stream.next(), - mightBeFunction = false, - isEQName = isEQNameAhead(stream); - - // an XML tag (if not in some sub, chained tokenizer) - if (ch == "<") { - if(stream.match("!--", true)) - return chain(stream, state, tokenXMLComment); - - if(stream.match("![CDATA", false)) { - state.tokenize = tokenCDATA; - return ret("tag", "tag"); - } - - if(stream.match("?", false)) { - return chain(stream, state, tokenPreProcessing); - } - - var isclose = stream.eat("/"); - stream.eatSpace(); - var tagName = "", c; - while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; - - return chain(stream, state, tokenTag(tagName, isclose)); - } - // start code block - else if(ch == "{") { - pushStateStack(state,{ type: "codeblock"}); - return ret("", null); - } - // end code block - else if(ch == "}") { - popStateStack(state); - return ret("", null); - } - // if we're in an XML block - else if(isInXmlBlock(state)) { - if(ch == ">") - return ret("tag", "tag"); - else if(ch == "/" && stream.eat(">")) { - popStateStack(state); - return ret("tag", "tag"); - } - else - return ret("word", "variable"); - } - // if a number - else if (/\d/.test(ch)) { - stream.match(/^\d*(?:\.\d*)?(?:E[+\-]?\d+)?/); - return ret("number", "atom"); - } - // comment start - else if (ch === "(" && stream.eat(":")) { - pushStateStack(state, { type: "comment"}); - return chain(stream, state, tokenComment); - } - // quoted string - else if ( !isEQName && (ch === '"' || ch === "'")) - return chain(stream, state, tokenString(ch)); - // variable - else if(ch === "$") { - return chain(stream, state, tokenVariable); - } - // assignment - else if(ch ===":" && stream.eat("=")) { - return ret("operator", "keyword"); - } - // open paren - else if(ch === "(") { - pushStateStack(state, { type: "paren"}); - return ret("", null); - } - // close paren - else if(ch === ")") { - popStateStack(state); - return ret("", null); - } - // open paren - else if(ch === "[") { - pushStateStack(state, { type: "bracket"}); - return ret("", null); - } - // close paren - else if(ch === "]") { - popStateStack(state); - return ret("", null); - } - else { - var known = keywords.propertyIsEnumerable(ch) && keywords[ch]; - - // if there's a EQName ahead, consume the rest of the string portion, it's likely a function - if(isEQName && ch === '\"') while(stream.next() !== '"'){} - if(isEQName && ch === '\'') while(stream.next() !== '\''){} - - // gobble up a word if the character is not known - if(!known) stream.eatWhile(/[\w\$_-]/); - - // gobble a colon in the case that is a lib func type call fn:doc - var foundColon = stream.eat(":"); - - // if there's not a second colon, gobble another word. Otherwise, it's probably an axis specifier - // which should get matched as a keyword - if(!stream.eat(":") && foundColon) { - stream.eatWhile(/[\w\$_-]/); - } - // if the next non whitespace character is an open paren, this is probably a function (if not a keyword of other sort) - if(stream.match(/^[ \t]*\(/, false)) { - mightBeFunction = true; - } - // is the word a keyword? - var word = stream.current(); - known = keywords.propertyIsEnumerable(word) && keywords[word]; - - // if we think it's a function call but not yet known, - // set style to variable for now for lack of something better - if(mightBeFunction && !known) known = {type: "function_call", style: "variable def"}; - - // if the previous word was element, attribute, axis specifier, this word should be the name of that - if(isInXmlConstructor(state)) { - popStateStack(state); - return ret("word", "variable", word); - } - // as previously checked, if the word is element,attribute, axis specifier, call it an "xmlconstructor" and - // push the stack so we know to look for it on the next word - if(word == "element" || word == "attribute" || known.type == "axis_specifier") pushStateStack(state, {type: "xmlconstructor"}); - - // if the word is known, return the details of that else just call this a generic 'word' - return known ? ret(known.type, known.style, word) : - ret("word", "variable", word); - } - } - - // handle comments, including nested - function tokenComment(stream, state) { - var maybeEnd = false, maybeNested = false, nestedCount = 0, ch; - while (ch = stream.next()) { - if (ch == ")" && maybeEnd) { - if(nestedCount > 0) - nestedCount--; - else { - popStateStack(state); - break; - } - } - else if(ch == ":" && maybeNested) { - nestedCount++; - } - maybeEnd = (ch == ":"); - maybeNested = (ch == "("); - } - - return ret("comment", "comment"); - } - - // tokenizer for string literals - // optionally pass a tokenizer function to set state.tokenize back to when finished - function tokenString(quote, f) { - return function(stream, state) { - var ch; - - if(isInString(state) && stream.current() == quote) { - popStateStack(state); - if(f) state.tokenize = f; - return ret("string", "string"); - } - - pushStateStack(state, { type: "string", name: quote, tokenize: tokenString(quote, f) }); - - // if we're in a string and in an XML block, allow an embedded code block - if(stream.match("{", false) && isInXmlAttributeBlock(state)) { - state.tokenize = tokenBase; - return ret("string", "string"); - } - - - while (ch = stream.next()) { - if (ch == quote) { - popStateStack(state); - if(f) state.tokenize = f; - break; - } - else { - // if we're in a string and in an XML block, allow an embedded code block in an attribute - if(stream.match("{", false) && isInXmlAttributeBlock(state)) { - state.tokenize = tokenBase; - return ret("string", "string"); - } - - } - } - - return ret("string", "string"); - }; - } - - // tokenizer for variables - function tokenVariable(stream, state) { - var isVariableChar = /[\w\$_-]/; - - // a variable may start with a quoted EQName so if the next character is quote, consume to the next quote - if(stream.eat("\"")) { - while(stream.next() !== '\"'){}; - stream.eat(":"); - } else { - stream.eatWhile(isVariableChar); - if(!stream.match(":=", false)) stream.eat(":"); - } - stream.eatWhile(isVariableChar); - state.tokenize = tokenBase; - return ret("variable", "variable"); - } - - // tokenizer for XML tags - function tokenTag(name, isclose) { - return function(stream, state) { - stream.eatSpace(); - if(isclose && stream.eat(">")) { - popStateStack(state); - state.tokenize = tokenBase; - return ret("tag", "tag"); - } - // self closing tag without attributes? - if(!stream.eat("/")) - pushStateStack(state, { type: "tag", name: name, tokenize: tokenBase}); - if(!stream.eat(">")) { - state.tokenize = tokenAttribute; - return ret("tag", "tag"); - } - else { - state.tokenize = tokenBase; - } - return ret("tag", "tag"); - }; - } - - // tokenizer for XML attributes - function tokenAttribute(stream, state) { - var ch = stream.next(); - - if(ch == "/" && stream.eat(">")) { - if(isInXmlAttributeBlock(state)) popStateStack(state); - if(isInXmlBlock(state)) popStateStack(state); - return ret("tag", "tag"); - } - if(ch == ">") { - if(isInXmlAttributeBlock(state)) popStateStack(state); - return ret("tag", "tag"); - } - if(ch == "=") - return ret("", null); - // quoted string - if (ch == '"' || ch == "'") - return chain(stream, state, tokenString(ch, tokenAttribute)); - - if(!isInXmlAttributeBlock(state)) - pushStateStack(state, { type: "attribute", tokenize: tokenAttribute}); - - stream.eat(/[a-zA-Z_:]/); - stream.eatWhile(/[-a-zA-Z0-9_:.]/); - stream.eatSpace(); - - // the case where the attribute has not value and the tag was closed - if(stream.match(">", false) || stream.match("/", false)) { - popStateStack(state); - state.tokenize = tokenBase; - } - - return ret("attribute", "attribute"); - } - - // handle comments, including nested - function tokenXMLComment(stream, state) { - var ch; - while (ch = stream.next()) { - if (ch == "-" && stream.match("->", true)) { - state.tokenize = tokenBase; - return ret("comment", "comment"); - } - } - } - - - // handle CDATA - function tokenCDATA(stream, state) { - var ch; - while (ch = stream.next()) { - if (ch == "]" && stream.match("]", true)) { - state.tokenize = tokenBase; - return ret("comment", "comment"); - } - } - } - - // handle preprocessing instructions - function tokenPreProcessing(stream, state) { - var ch; - while (ch = stream.next()) { - if (ch == "?" && stream.match(">", true)) { - state.tokenize = tokenBase; - return ret("comment", "comment meta"); - } - } - } - - - // functions to test the current context of the state - function isInXmlBlock(state) { return isIn(state, "tag"); } - function isInXmlAttributeBlock(state) { return isIn(state, "attribute"); } - function isInXmlConstructor(state) { return isIn(state, "xmlconstructor"); } - function isInString(state) { return isIn(state, "string"); } - - function isEQNameAhead(stream) { - // assume we've already eaten a quote (") - if(stream.current() === '"') - return stream.match(/^[^\"]+\"\:/, false); - else if(stream.current() === '\'') - return stream.match(/^[^\"]+\'\:/, false); - else - return false; - } - - function isIn(state, type) { - return (state.stack.length && state.stack[state.stack.length - 1].type == type); - } - - function pushStateStack(state, newState) { - state.stack.push(newState); - } - - function popStateStack(state) { - state.stack.pop(); - var reinstateTokenize = state.stack.length && state.stack[state.stack.length-1].tokenize; - state.tokenize = reinstateTokenize || tokenBase; - } - - // the interface for the mode API - return { - startState: function() { - return { - tokenize: tokenBase, - cc: [], - stack: [] - }; - }, - - token: function(stream, state) { - if (stream.eatSpace()) return null; - var style = state.tokenize(stream, state); - return style; - }, - - blockCommentStart: "(:", - blockCommentEnd: ":)" - - }; - -}); - -CodeMirror.defineMIME("application/xquery", "xquery"); diff --git a/applications/admin/static/codemirror/mode/yaml/index.html b/applications/admin/static/codemirror/mode/yaml/index.html deleted file mode 100644 index bbb40a3c..00000000 --- a/applications/admin/static/codemirror/mode/yaml/index.html +++ /dev/null @@ -1,80 +0,0 @@ - - -CodeMirror: YAML mode - - - - - - - - - -
-

YAML mode

-
- - -

MIME types defined: text/x-yaml.

- -
diff --git a/applications/admin/static/codemirror/mode/yaml/yaml.js b/applications/admin/static/codemirror/mode/yaml/yaml.js deleted file mode 100644 index efacd7d5..00000000 --- a/applications/admin/static/codemirror/mode/yaml/yaml.js +++ /dev/null @@ -1,97 +0,0 @@ -CodeMirror.defineMode("yaml", function() { - - var cons = ['true', 'false', 'on', 'off', 'yes', 'no']; - var keywordRegex = new RegExp("\\b(("+cons.join(")|(")+"))$", 'i'); - - return { - token: function(stream, state) { - var ch = stream.peek(); - var esc = state.escaped; - state.escaped = false; - /* comments */ - if (ch == "#" && (stream.pos == 0 || /\s/.test(stream.string.charAt(stream.pos - 1)))) { - stream.skipToEnd(); return "comment"; - } - if (state.literal && stream.indentation() > state.keyCol) { - stream.skipToEnd(); return "string"; - } else if (state.literal) { state.literal = false; } - if (stream.sol()) { - state.keyCol = 0; - state.pair = false; - state.pairStart = false; - /* document start */ - if(stream.match(/---/)) { return "def"; } - /* document end */ - if (stream.match(/\.\.\./)) { return "def"; } - /* array list item */ - if (stream.match(/\s*-\s+/)) { return 'meta'; } - } - /* inline pairs/lists */ - if (stream.match(/^(\{|\}|\[|\])/)) { - if (ch == '{') - state.inlinePairs++; - else if (ch == '}') - state.inlinePairs--; - else if (ch == '[') - state.inlineList++; - else - state.inlineList--; - return 'meta'; - } - - /* list seperator */ - if (state.inlineList > 0 && !esc && ch == ',') { - stream.next(); - return 'meta'; - } - /* pairs seperator */ - if (state.inlinePairs > 0 && !esc && ch == ',') { - state.keyCol = 0; - state.pair = false; - state.pairStart = false; - stream.next(); - return 'meta'; - } - - /* start of value of a pair */ - if (state.pairStart) { - /* block literals */ - if (stream.match(/^\s*(\||\>)\s*/)) { state.literal = true; return 'meta'; }; - /* references */ - if (stream.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i)) { return 'variable-2'; } - /* numbers */ - if (state.inlinePairs == 0 && stream.match(/^\s*-?[0-9\.\,]+\s?$/)) { return 'number'; } - if (state.inlinePairs > 0 && stream.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/)) { return 'number'; } - /* keywords */ - if (stream.match(keywordRegex)) { return 'keyword'; } - } - - /* pairs (associative arrays) -> key */ - if (!state.pair && stream.match(/^\s*\S+(?=\s*:($|\s))/i)) { - state.pair = true; - state.keyCol = stream.indentation(); - return "atom"; - } - if (state.pair && stream.match(/^:\s*/)) { state.pairStart = true; return 'meta'; } - - /* nothing found, continue */ - state.pairStart = false; - state.escaped = (ch == '\\'); - stream.next(); - return null; - }, - startState: function() { - return { - pair: false, - pairStart: false, - keyCol: 0, - inlinePairs: 0, - inlineList: 0, - literal: false, - escaped: false - }; - } - }; -}); - -CodeMirror.defineMIME("text/x-yaml", "yaml"); diff --git a/applications/admin/static/codemirror/theme/3024-day.css b/applications/admin/static/codemirror/theme/3024-day.css index cbb9a4fa..3c01c2bf 100644 --- a/applications/admin/static/codemirror/theme/3024-day.css +++ b/applications/admin/static/codemirror/theme/3024-day.css @@ -10,8 +10,12 @@ .cm-s-3024-day.CodeMirror {background: #f7f7f7; color: #3a3432;} .cm-s-3024-day div.CodeMirror-selected {background: #d6d5d4 !important;} + .cm-s-3024-day .CodeMirror-gutters {background: #f7f7f7; border-right: 0px;} +.cm-s-3024-day .CodeMirror-guttermarker { color: #db2d20; } +.cm-s-3024-day .CodeMirror-guttermarker-subtle { color: #807d7c; } .cm-s-3024-day .CodeMirror-linenumber {color: #807d7c;} + .cm-s-3024-day .CodeMirror-cursor {border-left: 1px solid #5c5855 !important;} .cm-s-3024-day span.cm-comment {color: #cdab53;} @@ -31,4 +35,4 @@ .cm-s-3024-day span.cm-error {background: #db2d20; color: #5c5855;} .cm-s-3024-day .CodeMirror-activeline-background {background: #e8f2ff !important;} -.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} +.cm-s-3024-day .CodeMirror-matchingbracket { text-decoration: underline; color: #a16a94 !important;} diff --git a/applications/admin/static/codemirror/theme/3024-night.css b/applications/admin/static/codemirror/theme/3024-night.css index 2c62e221..631757fc 100644 --- a/applications/admin/static/codemirror/theme/3024-night.css +++ b/applications/admin/static/codemirror/theme/3024-night.css @@ -11,7 +11,10 @@ .cm-s-3024-night.CodeMirror {background: #090300; color: #d6d5d4;} .cm-s-3024-night div.CodeMirror-selected {background: #3a3432 !important;} .cm-s-3024-night .CodeMirror-gutters {background: #090300; border-right: 0px;} +.cm-s-3024-night .CodeMirror-guttermarker { color: #db2d20; } +.cm-s-3024-night .CodeMirror-guttermarker-subtle { color: #5c5855; } .cm-s-3024-night .CodeMirror-linenumber {color: #5c5855;} + .cm-s-3024-night .CodeMirror-cursor {border-left: 1px solid #807d7c !important;} .cm-s-3024-night span.cm-comment {color: #cdab53;} diff --git a/applications/admin/static/codemirror/theme/ambiance.css b/applications/admin/static/codemirror/theme/ambiance.css index 3a54b2a0..c844566e 100644 --- a/applications/admin/static/codemirror/theme/ambiance.css +++ b/applications/admin/static/codemirror/theme/ambiance.css @@ -41,7 +41,6 @@ .cm-s-ambiance.CodeMirror { line-height: 1.40em; - font-family: Monaco, Menlo,"Andale Mono","lucida console","Courier New",monospace !important; color: #E6E1DC; background-color: #202020; -webkit-box-shadow: inset 0 0 10px black; @@ -57,10 +56,13 @@ .cm-s-ambiance .CodeMirror-linenumber { text-shadow: 0px 1px 1px #4d4d4d; - color: #222; + color: #111; padding: 0 5px; } +.cm-s-ambiance .CodeMirror-guttermarker { color: #aaa; } +.cm-s-ambiance .CodeMirror-guttermarker-subtle { color: #111; } + .cm-s-ambiance .CodeMirror-lines .CodeMirror-cursor { border-left: 1px solid #7991E8; } diff --git a/applications/admin/static/codemirror/theme/base16-dark.css b/applications/admin/static/codemirror/theme/base16-dark.css index 3b7b21c7..a46abdbb 100644 --- a/applications/admin/static/codemirror/theme/base16-dark.css +++ b/applications/admin/static/codemirror/theme/base16-dark.css @@ -9,8 +9,10 @@ */ .cm-s-base16-dark.CodeMirror {background: #151515; color: #e0e0e0;} -.cm-s-base16-dark div.CodeMirror-selected {background: #202020 !important;} +.cm-s-base16-dark div.CodeMirror-selected {background: #303030 !important;} .cm-s-base16-dark .CodeMirror-gutters {background: #151515; border-right: 0px;} +.cm-s-base16-dark .CodeMirror-guttermarker { color: #ac4142; } +.cm-s-base16-dark .CodeMirror-guttermarker-subtle { color: #505050; } .cm-s-base16-dark .CodeMirror-linenumber {color: #505050;} .cm-s-base16-dark .CodeMirror-cursor {border-left: 1px solid #b0b0b0 !important;} @@ -30,5 +32,5 @@ .cm-s-base16-dark span.cm-link {color: #aa759f;} .cm-s-base16-dark span.cm-error {background: #ac4142; color: #b0b0b0;} -.cm-s-base16-dark .CodeMirror-activeline-background {background: #2F2F2F !important;} +.cm-s-base16-dark .CodeMirror-activeline-background {background: #202020 !important;} .cm-s-base16-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} diff --git a/applications/admin/static/codemirror/theme/base16-light.css b/applications/admin/static/codemirror/theme/base16-light.css index 5aa4b538..12ff2eb0 100644 --- a/applications/admin/static/codemirror/theme/base16-light.css +++ b/applications/admin/static/codemirror/theme/base16-light.css @@ -11,6 +11,8 @@ .cm-s-base16-light.CodeMirror {background: #f5f5f5; color: #202020;} .cm-s-base16-light div.CodeMirror-selected {background: #e0e0e0 !important;} .cm-s-base16-light .CodeMirror-gutters {background: #f5f5f5; border-right: 0px;} +.cm-s-base16-light .CodeMirror-guttermarker { color: #ac4142; } +.cm-s-base16-light .CodeMirror-guttermarker-subtle { color: #b0b0b0; } .cm-s-base16-light .CodeMirror-linenumber {color: #b0b0b0;} .cm-s-base16-light .CodeMirror-cursor {border-left: 1px solid #505050 !important;} diff --git a/applications/admin/static/codemirror/theme/blackboard.css b/applications/admin/static/codemirror/theme/blackboard.css index 8b760847..d7a2dc96 100644 --- a/applications/admin/static/codemirror/theme/blackboard.css +++ b/applications/admin/static/codemirror/theme/blackboard.css @@ -3,6 +3,8 @@ .cm-s-blackboard.CodeMirror { background: #0C1021; color: #F8F8F8; } .cm-s-blackboard .CodeMirror-selected { background: #253B76 !important; } .cm-s-blackboard .CodeMirror-gutters { background: #0C1021; border-right: 0; } +.cm-s-blackboard .CodeMirror-guttermarker { color: #FBDE2D; } +.cm-s-blackboard .CodeMirror-guttermarker-subtle { color: #888; } .cm-s-blackboard .CodeMirror-linenumber { color: #888; } .cm-s-blackboard .CodeMirror-cursor { border-left: 1px solid #A7A7A7 !important; } diff --git a/applications/admin/static/codemirror/theme/cobalt.css b/applications/admin/static/codemirror/theme/cobalt.css index b4a91773..47440531 100644 --- a/applications/admin/static/codemirror/theme/cobalt.css +++ b/applications/admin/static/codemirror/theme/cobalt.css @@ -1,6 +1,8 @@ .cm-s-cobalt.CodeMirror { background: #002240; color: white; } .cm-s-cobalt div.CodeMirror-selected { background: #b36539 !important; } .cm-s-cobalt .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } +.cm-s-cobalt .CodeMirror-guttermarker { color: #ffee80; } +.cm-s-cobalt .CodeMirror-guttermarker-subtle { color: #d0d0d0; } .cm-s-cobalt .CodeMirror-linenumber { color: #d0d0d0; } .cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white !important; } diff --git a/applications/admin/static/codemirror/theme/colorforth.css b/applications/admin/static/codemirror/theme/colorforth.css new file mode 100644 index 00000000..73fbf808 --- /dev/null +++ b/applications/admin/static/codemirror/theme/colorforth.css @@ -0,0 +1,33 @@ +.cm-s-colorforth.CodeMirror { background: #000000; color: #f8f8f8; } +.cm-s-colorforth .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } +.cm-s-colorforth .CodeMirror-guttermarker { color: #FFBD40; } +.cm-s-colorforth .CodeMirror-guttermarker-subtle { color: #78846f; } +.cm-s-colorforth .CodeMirror-linenumber { color: #bababa; } +.cm-s-colorforth .CodeMirror-cursor { border-left: 1px solid white !important; } + +.cm-s-colorforth span.cm-comment { color: #ededed; } +.cm-s-colorforth span.cm-def { color: #ff1c1c; font-weight:bold; } +.cm-s-colorforth span.cm-keyword { color: #ffd900; } +.cm-s-colorforth span.cm-builtin { color: #00d95a; } +.cm-s-colorforth span.cm-variable { color: #73ff00; } +.cm-s-colorforth span.cm-string { color: #007bff; } +.cm-s-colorforth span.cm-number { color: #00c4ff; } +.cm-s-colorforth span.cm-atom { color: #606060; } + +.cm-s-colorforth span.cm-variable-2 { color: #EEE; } +.cm-s-colorforth span.cm-variable-3 { color: #DDD; } +.cm-s-colorforth span.cm-property {} +.cm-s-colorforth span.cm-operator {} + +.cm-s-colorforth span.cm-meta { color: yellow; } +.cm-s-colorforth span.cm-qualifier { color: #FFF700; } +.cm-s-colorforth span.cm-bracket { color: #cc7; } +.cm-s-colorforth span.cm-tag { color: #FFBD40; } +.cm-s-colorforth span.cm-attribute { color: #FFF700; } +.cm-s-colorforth span.cm-error { color: #f00; } + +.cm-s-colorforth .CodeMirror-selected { background: #333d53 !important; } + +.cm-s-colorforth span.cm-compilation { background: rgba(255, 255, 255, 0.12); } + +.cm-s-colorforth .CodeMirror-activeline-background {background: #253540 !important;} diff --git a/applications/admin/static/codemirror/theme/erlang-dark.css b/applications/admin/static/codemirror/theme/erlang-dark.css index db56b108..ff47d7f8 100644 --- a/applications/admin/static/codemirror/theme/erlang-dark.css +++ b/applications/admin/static/codemirror/theme/erlang-dark.css @@ -1,6 +1,8 @@ .cm-s-erlang-dark.CodeMirror { background: #002240; color: white; } .cm-s-erlang-dark div.CodeMirror-selected { background: #b36539 !important; } .cm-s-erlang-dark .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } +.cm-s-erlang-dark .CodeMirror-guttermarker { color: white; } +.cm-s-erlang-dark .CodeMirror-guttermarker-subtle { color: #d0d0d0; } .cm-s-erlang-dark .CodeMirror-linenumber { color: #d0d0d0; } .cm-s-erlang-dark .CodeMirror-cursor { border-left: 1px solid white !important; } diff --git a/applications/admin/static/codemirror/theme/lesser-dark.css b/applications/admin/static/codemirror/theme/lesser-dark.css index c3255966..a7824747 100644 --- a/applications/admin/static/codemirror/theme/lesser-dark.css +++ b/applications/admin/static/codemirror/theme/lesser-dark.css @@ -5,10 +5,6 @@ Ported to CodeMirror by Peter Kroon .cm-s-lesser-dark { line-height: 1.3em; } -.cm-s-lesser-dark { - font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', 'Monaco', Courier, monospace !important; -} - .cm-s-lesser-dark.CodeMirror { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; } .cm-s-lesser-dark div.CodeMirror-selected {background: #45443B !important;} /* 33322B*/ .cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white !important; } @@ -17,6 +13,8 @@ Ported to CodeMirror by Peter Kroon .cm-s-lesser-dark.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/ .cm-s-lesser-dark .CodeMirror-gutters { background: #262626; border-right:1px solid #aaa; } +.cm-s-lesser-dark .CodeMirror-guttermarker { color: #599eff; } +.cm-s-lesser-dark .CodeMirror-guttermarker-subtle { color: #777; } .cm-s-lesser-dark .CodeMirror-linenumber { color: #777; } .cm-s-lesser-dark span.cm-keyword { color: #599eff; } diff --git a/applications/admin/static/codemirror/theme/mbo.css b/applications/admin/static/codemirror/theme/mbo.css index bb52e6d1..0ad6360b 100644 --- a/applications/admin/static/codemirror/theme/mbo.css +++ b/applications/admin/static/codemirror/theme/mbo.css @@ -1,8 +1,14 @@ -/* Based on mbonaci's Brackets mbo theme */ +/****************************************************************/ +/* Based on mbonaci's Brackets mbo theme */ +/* https://github.com/mbonaci/global/blob/master/Mbo.tmTheme */ +/* Create your own: http://tmtheme-editor.herokuapp.com */ +/****************************************************************/ -.cm-s-mbo.CodeMirror {background: #2c2c2c; color: #ffffe9;} +.cm-s-mbo.CodeMirror {background: #2c2c2c; color: #ffffec;} .cm-s-mbo div.CodeMirror-selected {background: #716C62 !important;} .cm-s-mbo .CodeMirror-gutters {background: #4e4e4e; border-right: 0px;} +.cm-s-mbo .CodeMirror-guttermarker { color: white; } +.cm-s-mbo .CodeMirror-guttermarker-subtle { color: grey; } .cm-s-mbo .CodeMirror-linenumber {color: #dadada;} .cm-s-mbo .CodeMirror-cursor {border-left: 1px solid #ffffec !important;} @@ -13,6 +19,7 @@ .cm-s-mbo span.cm-property, .cm-s-mbo span.cm-attribute {color: #9ddfe9;} .cm-s-mbo span.cm-keyword {color: #ffb928;} .cm-s-mbo span.cm-string {color: #ffcf6c;} +.cm-s-mbo span.cm-string.cm-property {color: #ffffec;} .cm-s-mbo span.cm-variable {color: #ffffec;} .cm-s-mbo span.cm-variable-2 {color: #00a8c6;} @@ -20,18 +27,9 @@ .cm-s-mbo span.cm-bracket {color: #fffffc; font-weight: bold;} .cm-s-mbo span.cm-tag {color: #9ddfe9;} .cm-s-mbo span.cm-link {color: #f54b07;} -.cm-s-mbo span.cm-error {background: #636363; color: #ffffec;} +.cm-s-mbo span.cm-error {border-bottom: #636363; color: #ffffec;} +.cm-s-mbo span.cm-qualifier {color: #ffffec;} .cm-s-mbo .CodeMirror-activeline-background {background: #494b41 !important;} -.cm-s-mbo .CodeMirror-matchingbracket { - text-decoration: underline; - color: #f5e107 !important; - } - -.cm-s-mbo .CodeMirror-matchingtag {background: #4e4e4e;} - -.cm-s-mbo span.cm-searching { - background-color: none; - background: none; - box-shadow: 0 0 0 1px #ffffec; -} +.cm-s-mbo .CodeMirror-matchingbracket {color: #222 !important;} +.cm-s-mbo .CodeMirror-matchingtag {background: rgba(255, 255, 255, .37);} diff --git a/applications/admin/static/codemirror/theme/mdn-like.css b/applications/admin/static/codemirror/theme/mdn-like.css index c12cb1f9..81b21772 100644 --- a/applications/admin/static/codemirror/theme/mdn-like.css +++ b/applications/admin/static/codemirror/theme/mdn-like.css @@ -1,13 +1,13 @@ /* MDN-LIKE Theme - Mozilla Ported to CodeMirror by Peter Kroon - Report bugs/issues here: https://github.com/marijnh/CodeMirror/issues + Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues GitHub: @peterkroon The mdn-like theme is inspired on the displayed code examples at: https://developer.mozilla.org/en-US/docs/Web/CSS/animation */ -.cm-s-mdn-like.CodeMirror { color: #999; font-family: monospace; background-color: #fff; } +.cm-s-mdn-like.CodeMirror { color: #999; background-color: #fff; } .cm-s-mdn-like .CodeMirror-selected { background: #cfc !important; } .cm-s-mdn-like .CodeMirror-gutters { background: #f8f8f8; border-left: 6px solid rgba(0,83,159,0.65); color: #333; } diff --git a/applications/admin/static/codemirror/theme/midnight.css b/applications/admin/static/codemirror/theme/midnight.css index 468d87da..4567d293 100644 --- a/applications/admin/static/codemirror/theme/midnight.css +++ b/applications/admin/static/codemirror/theme/midnight.css @@ -16,6 +16,8 @@ .cm-s-midnight div.CodeMirror-selected {background: #314D67 !important;} .cm-s-midnight .CodeMirror-gutters {background: #0F192A; border-right: 1px solid;} +.cm-s-midnight .CodeMirror-guttermarker { color: white; } +.cm-s-midnight .CodeMirror-guttermarker-subtle { color: #d0d0d0; } .cm-s-midnight .CodeMirror-linenumber {color: #D0D0D0;} .cm-s-midnight .CodeMirror-cursor { border-left: 1px solid #F8F8F0 !important; diff --git a/applications/admin/static/codemirror/theme/monokai.css b/applications/admin/static/codemirror/theme/monokai.css index 7ac601a1..548d2dff 100644 --- a/applications/admin/static/codemirror/theme/monokai.css +++ b/applications/admin/static/codemirror/theme/monokai.css @@ -3,6 +3,8 @@ .cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2;} .cm-s-monokai div.CodeMirror-selected {background: #49483E !important;} .cm-s-monokai .CodeMirror-gutters {background: #272822; border-right: 0px;} +.cm-s-monokai .CodeMirror-guttermarker { color: white; } +.cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; } .cm-s-monokai .CodeMirror-linenumber {color: #d0d0d0;} .cm-s-monokai .CodeMirror-cursor {border-left: 1px solid #f8f8f0 !important;} diff --git a/applications/admin/static/codemirror/theme/neo.css b/applications/admin/static/codemirror/theme/neo.css new file mode 100644 index 00000000..cecaaf28 --- /dev/null +++ b/applications/admin/static/codemirror/theme/neo.css @@ -0,0 +1,43 @@ +/* neo theme for codemirror */ + +/* Color scheme */ + +.cm-s-neo.CodeMirror { + background-color:#ffffff; + color:#2e383c; + line-height:1.4375; +} +.cm-s-neo .cm-comment {color:#75787b} +.cm-s-neo .cm-keyword, .cm-s-neo .cm-property {color:#1d75b3} +.cm-s-neo .cm-atom,.cm-s-neo .cm-number {color:#75438a} +.cm-s-neo .cm-node,.cm-s-neo .cm-tag {color:#9c3328} +.cm-s-neo .cm-string {color:#b35e14} +.cm-s-neo .cm-variable,.cm-s-neo .cm-qualifier {color:#047d65} + + +/* Editor styling */ + +.cm-s-neo pre { + padding:0; +} + +.cm-s-neo .CodeMirror-gutters { + border:none; + border-right:10px solid transparent; + background-color:transparent; +} + +.cm-s-neo .CodeMirror-linenumber { + padding:0; + color:#e0e2e5; +} + +.cm-s-neo .CodeMirror-guttermarker { color: #1d75b3; } +.cm-s-neo .CodeMirror-guttermarker-subtle { color: #e0e2e5; } + +.cm-s-neo div.CodeMirror-cursor { + width: auto; + border: 0; + background: rgba(155,157,162,0.37); + z-index: 1; +} diff --git a/applications/admin/static/codemirror/theme/night.css b/applications/admin/static/codemirror/theme/night.css index 016e55ee..a0bf8cfa 100644 --- a/applications/admin/static/codemirror/theme/night.css +++ b/applications/admin/static/codemirror/theme/night.css @@ -3,6 +3,8 @@ .cm-s-night.CodeMirror { background: #0a001f; color: #f8f8f8; } .cm-s-night div.CodeMirror-selected { background: #447 !important; } .cm-s-night .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } +.cm-s-night .CodeMirror-guttermarker { color: white; } +.cm-s-night .CodeMirror-guttermarker-subtle { color: #bbb; } .cm-s-night .CodeMirror-linenumber { color: #f8f8f8; } .cm-s-night .CodeMirror-cursor { border-left: 1px solid white !important; } diff --git a/applications/admin/static/codemirror/theme/paraiso-dark.css b/applications/admin/static/codemirror/theme/paraiso-dark.css index ddefc55d..53dcdf7a 100644 --- a/applications/admin/static/codemirror/theme/paraiso-dark.css +++ b/applications/admin/static/codemirror/theme/paraiso-dark.css @@ -11,6 +11,8 @@ .cm-s-paraiso-dark.CodeMirror {background: #2f1e2e; color: #b9b6b0;} .cm-s-paraiso-dark div.CodeMirror-selected {background: #41323f !important;} .cm-s-paraiso-dark .CodeMirror-gutters {background: #2f1e2e; border-right: 0px;} +.cm-s-paraiso-dark .CodeMirror-guttermarker { color: #ef6155; } +.cm-s-paraiso-dark .CodeMirror-guttermarker-subtle { color: #776e71; } .cm-s-paraiso-dark .CodeMirror-linenumber {color: #776e71;} .cm-s-paraiso-dark .CodeMirror-cursor {border-left: 1px solid #8d8687 !important;} diff --git a/applications/admin/static/codemirror/theme/paraiso-light.css b/applications/admin/static/codemirror/theme/paraiso-light.css index 8afb14be..07ca3259 100644 --- a/applications/admin/static/codemirror/theme/paraiso-light.css +++ b/applications/admin/static/codemirror/theme/paraiso-light.css @@ -11,6 +11,8 @@ .cm-s-paraiso-light.CodeMirror {background: #e7e9db; color: #41323f;} .cm-s-paraiso-light div.CodeMirror-selected {background: #b9b6b0 !important;} .cm-s-paraiso-light .CodeMirror-gutters {background: #e7e9db; border-right: 0px;} +.cm-s-paraiso-light .CodeMirror-guttermarker { color: black; } +.cm-s-paraiso-light .CodeMirror-guttermarker-subtle { color: #8d8687; } .cm-s-paraiso-light .CodeMirror-linenumber {color: #8d8687;} .cm-s-paraiso-light .CodeMirror-cursor {border-left: 1px solid #776e71 !important;} diff --git a/applications/admin/static/codemirror/theme/pastel-on-dark.css b/applications/admin/static/codemirror/theme/pastel-on-dark.css index df95699a..7992ac7d 100644 --- a/applications/admin/static/codemirror/theme/pastel-on-dark.css +++ b/applications/admin/static/codemirror/theme/pastel-on-dark.css @@ -11,7 +11,6 @@ background: #2c2827; color: #8F938F; line-height: 1.5; - font-family: consolas, Courier, monospace; font-size: 14px; } .cm-s-pastel-on-dark div.CodeMirror-selected { background: rgba(221,240,255,0.2) !important; } @@ -20,6 +19,8 @@ border-right: 0px; padding: 0 3px; } +.cm-s-pastel-on-dark .CodeMirror-guttermarker { color: white; } +.cm-s-pastel-on-dark .CodeMirror-guttermarker-subtle { color: #8F938F; } .cm-s-pastel-on-dark .CodeMirror-linenumber { color: #8F938F; } .cm-s-pastel-on-dark .CodeMirror-cursor { border-left: 1px solid #A7A7A7 !important; } .cm-s-pastel-on-dark span.cm-comment { color: #A6C6FF; } diff --git a/applications/admin/static/codemirror/theme/rubyblue.css b/applications/admin/static/codemirror/theme/rubyblue.css index b556139d..53498389 100644 --- a/applications/admin/static/codemirror/theme/rubyblue.css +++ b/applications/admin/static/codemirror/theme/rubyblue.css @@ -1,8 +1,8 @@ -.cm-s-rubyblue { font-family: Trebuchet, Verdana, sans-serif; } /* - customized editor font - */ - .cm-s-rubyblue.CodeMirror { background: #112435; color: white; } .cm-s-rubyblue div.CodeMirror-selected { background: #38566F !important; } .cm-s-rubyblue .CodeMirror-gutters { background: #1F4661; border-right: 7px solid #3E7087; } +.cm-s-rubyblue .CodeMirror-guttermarker { color: white; } +.cm-s-rubyblue .CodeMirror-guttermarker-subtle { color: #3E7087; } .cm-s-rubyblue .CodeMirror-linenumber { color: white; } .cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white !important; } diff --git a/applications/admin/static/codemirror/theme/solarized.css b/applications/admin/static/codemirror/theme/solarized.css index 9c2e9148..07ef4068 100644 --- a/applications/admin/static/codemirror/theme/solarized.css +++ b/applications/admin/static/codemirror/theme/solarized.css @@ -29,7 +29,6 @@ http://ethanschoonover.com/solarized/img/solarized-palette.png .cm-s-solarized { line-height: 1.45em; - font-family: Menlo,Monaco,"Andale Mono","lucida console","Courier New",monospace !important; color-profile: sRGB; rendering-intent: auto; } @@ -89,11 +88,6 @@ http://ethanschoonover.com/solarized/img/solarized-palette.png text-decoration-style: dotted; } .cm-s-solarized .cm-strong { color: #eee; } -.cm-s-solarized .cm-tab:before { - content: "➤"; /*visualize tab character*/ - color: #586e75; - position:absolute; -} .cm-s-solarized .cm-error, .cm-s-solarized .cm-invalidchar { color: #586e75; @@ -121,8 +115,6 @@ http://ethanschoonover.com/solarized/img/solarized-palette.png /* Gutter border and some shadow from it */ .cm-s-solarized .CodeMirror-gutters { - padding: 0 15px 0 10px; - box-shadow: 0 10px 20px black; border-right: 1px solid; } @@ -130,7 +122,7 @@ http://ethanschoonover.com/solarized/img/solarized-palette.png /* Dark */ .cm-s-solarized.cm-s-dark .CodeMirror-gutters { - background-color: #073642; + background-color: #002b36; border-color: #00232c; } @@ -140,23 +132,23 @@ http://ethanschoonover.com/solarized/img/solarized-palette.png /* Light */ .cm-s-solarized.cm-s-light .CodeMirror-gutters { - background-color: #eee8d5; + background-color: #fdf6e3; border-color: #eee8d5; } /* Common */ .cm-s-solarized .CodeMirror-linenumber { color: #586e75; + padding: 0 5px; } +.cm-s-solarized .CodeMirror-guttermarker-subtle { color: #586e75; } +.cm-s-solarized.cm-s-dark .CodeMirror-guttermarker { color: #ddd; } +.cm-s-solarized.cm-s-light .CodeMirror-guttermarker { color: #cb4b16; } .cm-s-solarized .CodeMirror-gutter .CodeMirror-gutter-text { color: #586e75; } -.cm-s-solarized .CodeMirror-lines { - padding-left: 5px; -} - .cm-s-solarized .CodeMirror-lines .CodeMirror-cursor { border-left: 1px solid #819090; } @@ -171,11 +163,3 @@ view-port .cm-s-solarized.cm-s-light .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0.10); } - -/* -View-port and gutter both get little noise background to give it a real feel. -*/ -.cm-s-solarized.CodeMirror, -.cm-s-solarized .CodeMirror-gutters { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAQAAAAHUWYVAABFFUlEQVQYGbzBCeDVU/74/6fj9HIcx/FRHx9JCFmzMyGRURhLZIkUsoeRfUjS2FNDtr6WkMhO9sm+S8maJfu+Jcsg+/o/c+Z4z/t97/vezy3z+z8ekGlnYICG/o7gdk+wmSHZ1z4pJItqapjoKXWahm8NmV6eOTbWUOp6/6a/XIg6GQqmenJ2lDHyvCFZ2cBDbmtHA043VFhHwXxClWmeYAdLhV00Bd85go8VmaFCkbVkzlQENzfBDZ5gtN7HwF0KDrTwJ0dypSOzpaKCMwQHKTIreYIxlmhXTzTWkVm+LTynZhiSBT3RZQ7aGfjGEd3qyXQ1FDymqbKxpspERQN2MiRjNZlFFQXfCNFm9nM1zpAsoYjmtRTc5ajwuaXc5xrWskT97RaKzAGe5ARHhVUsDbjKklziiX5WROcJwSNCNI+9w1Jwv4Zb2r7lCMZ4oq5C0EdTx+2GzNuKpJ+iFf38JEWkHJn9DNF7mmBDITrWEg0VWL3pHU20tSZnuqWu+R3BtYa8XxV1HO7GyD32UkOpL/yDloINFTmvtId+nmAjxRw40VMwVKiwrKLE4bK5UOVntYwhOcSSXKrJHKPJedocpGjVz/ZMIbnYUPB10/eKCrs5apqpgVmWzBYWpmtKHecJPjaUuEgRDDaU0oZghCJ6zNMQ5ZhDYx05r5v2muQdM0EILtXUsaKiQX9WMEUotagQzFbUNN6NUPC2nm5pxEWGCjMc3GdJHjSU2kORLK/JGSrkfGEIjncU/CYUnOipoYemwj8tST9NsJmB7TUVXtbUtXATJVZXBMvYeTXJfobgJUPmGMP/yFaWonaa6BcFO3nqcIqCozSZoZoSr1g4zJOzuyGnxTEX3lUEJ7WcZgme8ddaWvWJo2AJR9DZU3CUIbhCSG6ybSwN6qtJVnCU2svDTP2ZInOw2cBTrqtQahtNZn9NcJ4l2NaSmSkkP1noZWnVwkLmdUPOwLZEwy2Z3S3R+4rIG9hcbpPXHFVWcQdZkn2FOta3cKWQnNRC5g1LsJah4GCzSVsKnCOY5OAFRTBekyyryeyilhFKva75r4Mc0aWanGEaThcy31s439KKxTzJYY5WTHPU1FtIHjQU3Oip4xlNzj/lBw23dYZVliQa7WAXf4shetcQfatI+jWRDBPmyNeW6A1P5kdDgyYJlba0BIM8BZu1JfrFwItyjcAMR3K0BWOIrtMEXyhyrlVEx3ui5dUBjmB/Q3CXW85R4mBD0s7B+4q5tKUjOlb9qqmhi5AZ6GFIC5HXtOobdYGlVdMVbNJ8toNTFcHxnoL+muBagcctjWnbNMuR00uI7nQESwg5q2qqrKWIfrNUmeQocY6HuyxJV02wj36w00yhpmUFenv4p6fUkZYqLyuinx2RGOjhCXYyJF84oiU00YMOOhhquNdfbOB7gU88pY4xJO8LVdp6/q2voeB4R04vIdhSE40xZObx1HGGJ/ja0LBthFInKaLPPFzuCaYaoj8JjPME8yoyxo6zlBqkiUZYgq00OYMswbWO5NGmq+xhipxHLRW29ARjNKXO0wRnear8XSg4XFPLKEPUS1GqvyLwiuBUoa7zpZ0l5xxFwWmWZC1H5h5FwU8eQ7K+g8UcVY6TMQreVQT/8uQ8Z+ALIXnSEa2pYZQneE9RZbSBNYXfWYJzW/h/4j4Dp1tYVcFIC5019Vyi4ThPqSFCzjGWaHQTBU8q6vrVwgxP9Lkm840imWKpcLCjYTtrKuwvsKSnrvHCXGkSMk9p6lhckfRpIeis+N2PiszT+mFLspyGleUhDwcLrZqmyeylxwjBcKHEapqkmyangyLZRVOijwOtCY5SsG5zL0OwlCJ4y5KznF3EUNDDrinwiyLZRzOXtlBbK5ITHFGLp8Q0R6ab6mS7enI2cFrxOyHvOCFaT1HThS1krjCwqWeurCkk+willhCC+RSZnRXBiZaC5RXRIZYKp2lyfrHwiKPKR0JDzrdU2EFgpidawlFDR6FgXUMNa+g1FY3bUQh2cLCwosRdnuQTS/S+JVrGLeWIvtQUvONJxlqSQYYKpwoN2kaocLjdVsis4Mk80ESF2YpSkzwldjHkjFCUutI/r+EHDU8oCs6yzL3PhWiEooZdFMkymlas4AcI3KmoMMNSQ3tHzjGWCrcJJdYyZC7QFGwjRL9p+MrRkAGWzIaWCn9W0F3TsK01c2ZvQw0byvxuQU0r1lM0qJO7wW0kRIMdDTtXEdzi4VIh+EoIHm0mWtAtpCixlabgn83fKTI7anJe9ST7WIK1DMGpQmYeA58ImV6ezOGOzK2Kgq01pd60cKWiUi9Lievb/0vIDPHQ05Kzt4ddPckQBQtoaurjyHnek/nKzpQLrVgKPjIkh2v4uyezpv+Xoo7fPFXaGFp1vaLKxQ4uUpQQS5VuQs7BCq4xRJv7fwpVvvFEB3j+620haOuocqMhWd6TTPAEx+mdFNGHdranFe95WrWmIvlY4F1Dle2ECgc6cto7SryuqGGGha0tFQ5V53migUKmg6XKAo4qS3mik+0OZpAhOLeZKicacgaYcyx5hypYQE02ZA4xi/pNhOQxR4klNKyqacj+mpxnLTnnGSo85++3ZCZq6lrZkXlGEX3o+C9FieccJbZWVFjC0Yo1FZnJhoYMFoI1hEZ9r6hwg75HwzBNhbZCdJEfJwTPGzJvaKImw1yYX1HDAmpXR+ZJQ/SmgqMNVQb5vgamGwLtt7VwvP7Qk1xpiM5x5Cyv93E06MZmgs0Nya2azIKOYKCGBQQW97RmhKNKF02JZqHEJ4o58qp7X5EcZmc56trXEqzjCBZ1MFGR87Ql2tSTs6CGxS05PTzRQorkbw7aKoKXFDXsYW42VJih/q+FP2BdTzDTwVqOYB13liM50vG7wy28qagyuIXMeQI/Oqq8bcn5wJI50xH00CRntyfpL1T4hydYpoXgNiFzoIUTDZnLNRzh4TBHwbYGDvZkxmlyJloyr6tRihpeUG94GnKtIznREF0tzJG/OOr73JBcrSh1k6WuTprgLU+mnSGnv6Zge0NNz+kTDdH8nuAuTdJDCNb21LCiIuqlYbqGzT3RAoZofQfjFazkqeNWdYaGvYTM001EW2oKPvVk1ldUGSgUtHFwjKM1h9jnFcmy5lChoLNaQMGGDsYbKixlaMBmmsx1QjCfflwTfO/gckW0ruZ3jugKR3R5W9hGUWqCgxuFgsuaCHorotGKzGaeZB9DMsaTnKCpMtwTvOzhYk0rdrArKCqcaWmVk1+F372ur1YkKxgatI8Qfe1gIX9wE9FgS8ESmuABIXnRUbCapcKe+nO7slClSZFzpV/LkLncEb1qiO42fS3R855Su2mCLh62t1SYZZYVmKwIHjREF2uihTzB20JOkz7dkxzYQnK0UOU494wh+VWRc6Un2kpTaVgLDFEkJ/uhzRcI0YKGgpGWOlocBU/a4fKoJ/pEaNV6jip3+Es9VXY078rGnmAdf7t9ylPXS34RBSuYPs1UecZTU78WanhBCHpZ5sAoTz0LGZKjPf9TRypqWEiTvOFglL1fCEY3wY/++rbk7C8bWebA6p6om6PgOL2kp44TFJlVNBXae2rqqdZztOJpT87GQsE9jqCPIe9VReZuQ/CIgacsyZdCpIScSYqcZk8r+nsyCzhyfhOqHGOIvrLknC8wTpFcaYiGC/RU1NRbUeUpocQOnkRpGOrIOcNRx+1uA0UrzhSSt+VyS3SJpnFWkzNDqOFGIWcfR86DnmARTQ1HKIL33ExPiemeOhYSSjzlSUZZuE4TveoJLnBUOFof6KiysCbnAEcZgcUNTDOwkqWu3RWtmGpZwlHhJENdZ3miGz0lJlsKnjbwqSHQjpxnFDlTLLwqJPMZMjd7KrzkSG7VsxXBZE+F8YZkb01Oe00yyRK9psh5SYh29ySPKBo2ylNht7ZkZnsKenjKNJu9PNEyZpaCHv4Kt6RQsLvAVp7M9kIimmCUwGeWqLMmGuIotYMmWNpSahkhZw9FqZsVnKJhsjAHvtHMsTM9fCI06Dx/u3vfUXCqfsKRc4oFY2jMsoo/7DJDwZ1CsIKnJu+J9ldkpmiCxQx1rWjI+T9FwcWWzOuaYH0Hj7klNRVWEQpmaqosakiGNTFHdjS/qnUdmf0NJW5xsL0HhimCCZZSRzmSPTXJQ4aaztAwtZnoabebJ+htCaZ7Cm535ByoqXKbX1WRc4Eh2MkRXWzImVc96Cj4VdOKVxR84VdQsIUM8Psoou2byVHyZFuq7O8otbSQ2UAoeEWTudATLGSpZzVLlXVkPU2Jc+27lsw2jmg5T5VhbeE3BT083K9WsTTkFU/Osi0rC5lRlpwRHUiesNS0sOvmqGML1aRbPAxTJD9ZKtxuob+hhl8cwYGWpJ8nub7t5p6coYbMovZ1BTdaKn1jYD6h4GFDNFyT/Kqe1XCXphXHOKLZmuRSRdBPEfVUXQzJm5YGPGGJdvAEr7hHNdGZnuBvrpciGmopOLf5N0uVMy0FfYToJk90uUCbJupaVpO53UJXR2bVpoU00V2KOo4zMFrBd0Jtz2pa0clT5Q5L8IpQ177mWQejPMEJhuQjS10ref6HHjdEhy1P1EYR7GtO0uSsKJQYLiTnG1rVScj5lyazpqWGl5uBbRWl7m6ixGOOnEsMJR7z8J0n6KMnCdxhiNYQCoZ6CmYLnO8omC3MkW3bktlPmEt/VQQHejL3+dOE5FlPdK/Mq8hZxxJtLyRrepLThYKbLZxkSb5W52vYxNOaOxUF0yxMUPwBTYqCzy01XayYK0sJyWBLqX0MwU5CzoymRzV0EjjeUeLgDpTo6ij42ZAzvD01dHUUTPLU96MdLbBME8nFBn7zJCMtJcZokn8YoqU0FS5WFKyniHobguMcmW8N0XkWZjkyN3hqOMtS08r+/xTBwpZSZ3qiVRX8SzMHHjfUNFjgHEPmY9PL3ykEzxkSre/1ZD6z/NuznuB0RcE1TWTm9zRgfUWVJiG6yrzgmWPXC8EAR4Wxhlad0ZbgQyEz3pG5RVEwwDJH2mgKpjcTiCOzn1lfUWANFbZ2BA8balnEweJC9J0iuaeZoI+ippFCztEKVvckR2iice1JvhVytrQwUAZpgsubCPaU7xUe9vWnaOpaSBEspalykhC9bUlOMpT42ZHca6hyrqKmw/wMR8H5ZmdFoBVJb03O4UL0tSNnvIeRmkrLWqrs78gcrEn2tpcboh0UPOW3UUR9PMk4T4nnNKWmCjlrefhCwxRNztfmIQVdDElvS4m1/WuOujoZCs5XVOjtKPGokJzsYCtFYoWonSPT21DheU/wWhM19FcElwqNGOsp9Q8N/cwXaiND1MmeL1Q5XROtYYgGeFq1aTMsoMmcrKjQrOFQTQ1fmBYhmW6o8Jkjc7iDJRTBIo5kgJD5yMEYA3srCg7VFKwiVJkmRCc5ohGOKhsYMn/XBLdo5taZjlb9YAlGWRimqbCsoY7HFAXLa5I1HPRxMMsQDHFkWtRNniqT9UEeNjcE7RUlrCJ4R2CSJuqlKHWvJXjAUNcITYkenuBRB84TbeepcqTj3zZyFJzgYQdHnqfgI0ddUwS6GqWpsKWhjq9cV0vBAEMN2znq+EBfIWT+pClYw5xsTlJU6GeIBsjGmmANTzJZiIYpgrM0Oa8ZMjd7NP87jxhqGOhJlnQtjuQpB+8aEE00wZFznSJPyHxgH3HkPOsJFvYk8zqCHzTs1BYOa4J3PFU+UVRZxlHDM4YavlNUuMoRveiZA2d7grMNc2g+RbSCEKzmgYsUmWmazFJyoiOZ4KnyhKOGRzWJa0+moyV4TVHDzn51Awtqaphfk/lRQ08FX1iiqxTB/kLwd0VynKfEvI6cd4XMV5bMhZ7gZUWVzYQ6Nm2BYzxJbw3bGthEUUMfgbGeorae6DxHtJoZ6alhZ0+ytiVoK1R4z5PTrOECT/SugseEOlb1MMNR4VRNcJy+V1Hg9ONClSZFZjdHlc6W6FBLdJja2MC5hhpu0DBYEY1TFGwiFAxRRCsYkiM9JRb0JNMVkW6CZYT/2EiTGWmo8k+h4FhDNE7BvppoTSFnmCV5xZKzvcCdDo7VVPnIU+I+Rc68juApC90MwcFCsJ5hDqxgScYKreruyQwTqrzoqDCmhWi4IbhB0Yrt3RGa6GfDv52rKXWhh28dyZaWUvcZeMTBaZoSGyiCtRU5J8iviioHaErs7Jkj61syVzTTgOcUOQ8buFBTYWdL5g3T4qlpe0+wvD63heAXRfCCIed9RbCsp2CiI7raUOYOTU13N8PNHvpaGvayo4a3LLT1lDrVEPT2zLUlheB1R+ZTRfKWJ+dcocLJfi11vyJ51lLqJ0WD7tRwryezjiV5W28uJO9qykzX8JDe2lHl/9oyBwa2UMfOngpXCixvKdXTk3wrsKmiVYdZIqsoWEERjbcUNDuiaQomGoIbFdEHmsyWnuR+IeriKDVLnlawlyNHKwKlSU631PKep8J4Q+ayjkSLKYLhalNHlYvttb6fHm0p6OApsZ4l2VfdqZkjuysy6ysKLlckf1KUutCTs39bmCgEyyoasIWlVaMF7mgmWtBT8Kol5xpH9IGllo8cJdopcvZ2sImlDmMIbtDk3KIpeNiS08lQw11NFPTwVFlPP6pJ2gvRfI7gQUfmNAtf6Gs0wQxDsKGlVBdF8rCa3jzdwMaGHOsItrZk7hAyOzpK9VS06j5F49b0VNGOOfKs3lDToMsMBe9ZWtHFEgxTJLs7qrygKZjUnmCYoeAqeU6jqWuLJup4WghOdvCYJnrSkSzoyRkm5M2StQwVltPkfCAk58tET/CSg+8MUecmotMEnhBKfWBIZsg2ihruMJQaoIm+tkTLKEqspMh00w95gvFCQRtDwTT1gVDDSEVdlwqZfxoQRbK0g+tbiBZxzKlpnpypejdDwTaeOvorMk/IJE10h9CqRe28hhLbe0pMsdSwv4ZbhKivo2BjDWfL8UKJgeavwlwb5KlwhyE4u4XkGE2ytZCznKLCDZZq42VzT8HLCrpruFbIfOIINmh/qCdZ1ZBc65kLHR1Bkyf5zn6pN3SvGKIlFNGplhrO9QSXanLOMQTLCa0YJCRrCZm/CZmrLTm7WzCK4GJDiWUdFeYx1LCFg3NMd0XmCuF3Y5rITLDUsYS9zoHVzwnJoYpSTQoObyEzr4cFBNqYTopoaU/wkyLZ2lPhX/5Y95ulxGTV7KjhWrOZgl8MyUUafjYraNjNU1N3IWcjT5WzWqjwtoarHSUObGYO3GCJZpsBlnJGPd6ZYLyl1GdCA2625IwwJDP8GUKymbzuyPlZlvTUsaUh5zFDhRWFzPKKZLAlWdcQbObgF9tOqOsmB1dqcqYJmWstFbZRRI9poolmqiLnU0POvxScpah2iSL5UJNzgScY5+AuIbpO0YD3NCW+dLMszFSdFCWGqG6eVq2uYVNDdICGD6W7EPRWZEY5gpsE9rUkS3mijzzJnm6UpUFXG1hCUeVoS5WfNcFpblELL2qqrCvMvRfd45oalvKU2tiQ6ePJOVMRXase9iTtLJztPxJKLWpo2CRDcJwn2sWSLKIO1WQWNTCvpVUvOZhgSC40JD0dOctaSqzkCRbXsKlb11Oip6PCJ0IwSJM31j3akRxlP7Rwn6aGaUL0qiLnJkvB3xWZ2+Q1TfCwpQH3G0o92UzmX4o/oJNQMMSQc547wVHhdk+VCw01DFYEnTxzZKAm74QmeNNR1w6WzEhNK15VJzuCdxQ53dRUDws5KvwgBMOEgpcVNe0hZI6RXT1Jd0cyj5nsaEAHgVmGaJIlWdsc5Ui2ElrRR6jrRAttNMEAIWrTDFubkZaok7/AkzfIwfuWVq0jHzuCK4QabtLUMVPB3kJ0oyHTSVFlqMALilJf2Rf8k5aaHtMfayocLBS8L89oKoxpJvnAkDPa0qp5DAUTHKWmCcnthlou8iCKaFFLHWcINd1nyIwXqrSxMNmSs6KmoL2QrKuWtlQ5V0120xQ5vRyZS1rgFkWwhiOwiuQbR0OOVhQM9iS3tiXp4RawRPMp5tDletOOBL95MpM01dZTBM9pkn5qF010rIeHFcFZhmSGpYpTsI6nwhqe5C9ynhlpp5ophuRb6WcJFldkVnVEwwxVfrVkvnWUuNLCg5bgboFHPDlDPDmnK7hUrWiIbjadDclujlZcaokOFup4Ri1kacV6jmrrK1hN9bGwpKEBQ4Q6DvIUXOmo6U5LqQM6EPyiKNjVkPnJkDPNEaxhiFay5ExW1NXVUGqcpYYdPcGiCq7z/TSlbhL4pplWXKd7NZO5QQFrefhRQW/NHOsqcIglc4UhWklR8K0QzbAw08CBDnpbgqXdeD/QUsM4RZXDFBW6WJKe/mFPdH0LtBgiq57wFLzlyQzz82qYx5D5WJP5yVJDW01BfyHnS6HKO/reZqId1WGa4Hkh2kWodJ8i6KoIPlAj2hPt76CzXsVR6koPRzWTfKqIentatYpQw2me4AA3y1Kind3SwoOKZDcFXTwl9tWU6mfgRk9d71sKtlNwrjnYw5tC5n5LdKiGry3JKNlHEd3oaMCFHrazBPMp/uNJ+V7IudcSbeOIdjUEdwl0VHCOZo5t6YluEuaC9mQeMgSfOyKnYGFHcIeQ84yQWbuJYJpZw5CzglDH7gKnWqqM9ZTaXcN0TeYhR84eQtJT76JJ1lREe7WnnvsMmRc9FQ7SBBM9mV3lCUdmHk/S2RAMt0QjFNFqQpWjDPQ01DXWUdDBkXziKPjGEP3VP+zIWU2t7im41FOloyWzn/L6dkUy3VLDaZ6appgDLHPjJEsyvJngWEPUyVBiAaHCTEXwrLvSEbV1e1gKJniicWorC1MUrVjB3uDhJE/wgSOzk1DXpk0k73qCM8xw2UvD5kJmDUfOomqMpWCkJRlvKXGmoeBm18USjVIk04SClxTB6YrgLAPLWYK9HLUt5cmc0vYES8GnTeRc6skZbQkWdxRsIcyBRzx1DbTk9FbU0caTPOgJHhJKnOGIVhQqvKmo0llRw9sabrZkDtdg3PqaKi9oatjY8B+G371paMg6+mZFNNtQ04mWBq3rYLOmtWWQp8KJnpy9DdFensyjdqZ+yY40VJlH8wcdLzC8PZnvHMFUTZUrDTkLyQaGus5X5LzpYAf3i+e/ZlhqGqWhh6Ou6xTR9Z6oi5AZZtp7Mj2EEm8oSpxiYZCHU/1fbGdNNNRRoZMhmilEb2gqHOEJDtXkHK/JnG6IrvbPCwV3NhONVdS1thBMs1T4QOBcTWa2IzhMk2nW5Kyn9tXUtpv9RsG2msxk+ZsQzRQacJncpgke0+T8y5Fzj8BiGo7XlJjaTIlpQs7KFjpqGnKuoyEPeIKnFMkZHvopgh81ySxNFWvJWcKRs70j2FOT012IllEEO1n4pD1513Yg2ssQPOThOkvyrqHUdEXOSEsihmBbTbKX1kLBPWqWkLOqJbjB3GBIZmoa8qWl4CG/iZ7oiA72ZL7TJNeZUY7kFQftDcHHluBzRbCegzMtrRjVQpX2lgoPKKLJAkcbMl01XK2p7yhL8pCBbQ3BN2avJgKvttcrWDK3CiUOVxQ8ZP+pqXKyIxnmBymCg5vJjNfkPK4+c8cIfK8ocVt7kmfd/I5SR1hKvCzUtb+lhgc00ZaO6CyhIQP1Uv4yIZjload72PXX0OIJvnFU+0Zf6MhsJwTfW0r0UwQfW4LNLZl5HK261JCZ4qnBaAreVAS3WrjV0LBnNDUNNDToCEeFfwgcb4gOEqLRhirWkexrCEYKVV711DLYEE1XBEsp5tpTGjorkomKYF9FDXv7fR3BGwbettSxnyL53MBPjsxDZjMh+VUW9NRxq1DhVk+FSxQcaGjV9Pawv6eGByw5qzoy7xk4RsOShqjJwWKe/1pEEfzkobeD/dQJmpqedcyBTy2sr4nGNRH0c0SPWTLrqAc0OQcb/gemKgqucQT7ySWKCn2EUotoCvpZct7RO2sy/QW0IWcXd7pQRQyZVwT2USRO87uhjioTLKV2brpMUcMQRbKH/N2T+UlTpaMls6cmc6CCNy3JdYYSUzzJQ4oSD3oKLncULOiJvjBEC2oqnCJkJluCYy2ZQ5so9YYlZ1VLlQU1mXEW1jZERwj/MUSRc24TdexlqLKfQBtDTScJUV8FszXBEY5ktpD5Ur9hYB4Nb1iikw3JoYpkKX+RodRKFt53MMuRnKSpY31PwYaGaILh3wxJGz9TkTPEETxoCWZrgvOlmyMzxFEwVJE5xZKzvyJ4WxEc16Gd4Xe3Weq4XH2jKRikqOkGQ87hQnC7wBmGYLAnesX3M+S87eFATauuN+Qcrh7xIxXJbUIdMw3JGE3ylCWzrieaqCn4zhGM19TQ3z1oH1AX+pWEqIc7wNGAkULBo/ZxRaV9NNyh4Br3rCHZzbzmSfawBL0dNRwpW1kK9mxPXR9povcdrGSZK9c2k0xwFGzjuniCtRSZCZ6ccZ7gaktmgAOtKbG/JnOkJrjcQTdFMsxRQ2cLY3WTIrlCw1eWKn8R6pvt4GFDso3QoL4a3nLk3G6JrtME3dSenpx7PNFTmga0EaJTLQ061sEeQoWXhSo9LTXsaSjoJQRXeZLtDclbCrYzfzHHeaKjHCVOUkQHO3JeEepr56mhiyaYYKjjNU+Fed1wS5VlhWSqI/hYUdDOkaxiKehoyOnrCV5yBHtbWFqTHCCwtpDcYolesVR5yUzTZBb3RNMd0d6WP+SvhuBmRcGxnuQzT95IC285cr41cLGQ6aJJhmi4TMGempxeimBRQw1tFKV+8jd6KuzoSTqqDxzRtpZkurvKEHxlqXKRIjjfUNNXQsNOsRScoWFLT+YeRZVD3GRN0MdQcKqQjHDMrdGGVu3iYJpQx3WGUvfbmxwFfR20WBq0oYY7LMFhhgYtr8jpaEnaOzjawWWaTP8mMr0t/EPDPoqcnxTBI5o58L7uoWnMrpoqPwgVrlAUWE+V+TQl9rawoyP6QGAlQw2TPRX+YSkxyBC8Z6jhHkXBgQL7WII3DVFnRfCrBfxewv9D6xsyjys4VkhWb9pUU627JllV0YDNHMku/ldNMMXDEo4aFnAkk4U6frNEU4XgZUPmEKHUl44KrzmYamjAbh0JFvGnaTLPu1s9jPCwjFpYiN7z1DTOk/nc07CfDFzmCf7i+bfNHXhDtLeBXzTBT5rkMvWOIxpl4EMh2LGJBu2syDnAEx2naEhHDWMMzPZEhygyS1mS5RTJr5ZkoKbEUoYqr2kqdDUE8ztK7OaIntJkFrIECwv8LJTaVx5XJE86go8dFeZ3FN3rjabCAYpoYEeC9zzJVULBbmZhDyd7ko09ydpNZ3nm2Kee4FPPXHnYEF1nqOFEC08LUVcDvYXkJHW8gTaKCk9YGOeIJhqiE4ToPEepdp7IWFjdwnWaufGMwJJCMtUTTBBK9BGCOy2tGGrJTHIwyEOzp6aPzNMOtlZkDvcEWpP5SVNhfkvDxhmSazTJXYrM9U1E0xwFVwqZQwzJxw6+kGGGUj2FglGGmnb1/G51udRSMNlTw6GGnCcUwVcOpmsqTHa06o72sw1RL02p9z0VbnMLOaIX3QKaYKSCFQzBKEUNHTSc48k53RH9wxGMtpQa5KjjW0W0n6XCCCG4yxNNdhQ4R4l1Ff+2sSd6UFHiIEOyqqFgT01mEUMD+joy75jPhOA+oVVLm309FR4yVOlp4RhLiScNmSmaYF5Pw0STrOIoWMSR2UkRXOMp+M4SHW8o8Zoi6OZgjKOaFar8zZDzkWzvKOjkKBjmCXby8JahhjXULY4KlzgKLvAwxVGhvyd4zxB1d9T0piazmKLCVZY5sKiD0y2ZSYrkUEPUbIk+dlQ4SJHTR50k1DPaUWIdTZW9NJwnJMOECgd7ou/MnppMJ02O1VT4Wsh85MnZzcFTngpXGKo84qmwgKbCL/orR/SzJ2crA+t6Mp94KvxJUeIbT3CQu1uIdlQEOzlKfS3UMcrTiFmOuroocrZrT2AcmamOKg8YomeEKm/rlT2sociMaybaUlFhuqHCM2qIJ+rg4EcDFymiDSxzaHdPcpE62pD5kyM5SBMoA1PaUtfIthS85ig1VPiPPYXgYEMNk4Qq7TXBgo7oT57gPUdwgCHzhIVFPFU6OYJzHAX9m5oNrVjeE61miDrqQ4VSa1oiURTsKHC0IfjNwU2WzK6eqK8jWln4g15TVBnqmDteCJ501PGAocJhhqjZdtBEB6lnhLreFJKxmlKbeGrqLiSThVIbCdGzloasa6lpMQXHCME2boLpJgT7yWaemu6wBONbqGNVRS0PKIL7LckbjmQtR7K8I5qtqel+T/ChJTNIKLjdUMNIRyvOEko9YYl2cwQveBikCNawJKcLBbc7+JM92mysNvd/Fqp8a0k6CNEe7cnZrxlW0wQXaXjaktnRwNOGZKYiONwS7a1JVheq3WgJHlQUGKHKmp4KAxXR/ULURcNgoa4zhKSLpZR3kxRRb0NmD0OFn+UCS7CzI1nbP6+o4x47QZE5xRCt3ZagnYcvmpYQktXdk5YKXTzBC57kKEe0VVuiSYqapssMS3C9p2CKkHOg8B8Pa8p5atrIw3qezIWanMGa5HRDNF6RM9wcacl0N+Q8Z8hsIkSnaIIdHRUOEebAPy1zbCkhM062FCJtif7PU+UtoVXzWKqM1PxXO8cfdruhFQ/a6x3JKYagvVDhQEtNiyiiSQ7OsuRsZUku0CRNDs4Sog6KKjsZgk2bYJqijgsEenoKeniinRXBn/U3lgpPdyDZynQx8IiioMnCep5Ky8mjGs6Wty0l1hUQTcNWswS3WRp2kCNZwJG8omG8JphPUaFbC8lEfabwP7VtM9yoaNCAjpR41VNhrD9LkbN722v0CoZMByFzhaW+MyzRYEWFDQwN2M4/JiT76PuljT3VU/A36eaIThb+R9oZGOAJ9tewkgGvqOMNRWYjT/Cwu99Q8LqDE4TgbLWxJ1jaDDAERsFOFrobgjUsBScaguXU8kKm2RL19tRypSHnHNlHiIZqgufs4opgQdVdwxBNNFBR6kVFqb8ogimOzB6a6HTzrlDHEpYaxjiiA4TMQobkDg2vejjfwJGWmnbVFAw3H3hq2NyQfG7hz4aC+w3BbwbesG0swYayvpAs6++Ri1Vfzx93mFChvyN5xVHTS+0p9aqCAxyZ6ZacZyw5+7uuQkFPR9DDk9NOiE7X1PCYJVjVUqq7JlrHwWALF5nfHNGjApdpqgzx5OwilDhCiDYTgnc9waGW4BdLNNUQvOtpzDOWHDH8D7TR/A/85KljEQu3NREc4Pl/6B1Hhc8Umb5CsKMmGC9EPcxoT2amwHNCmeOEnOPbklnMkbOgIvO5UMOpQrS9UGVdt6iH/fURjhI/WOpaW9OKLYRod6HCUEdOX000wpDZQ6hwg6LgZfOqo1RfT/CrJzjekXOGhpc1VW71ZLbXyyp+93ILbC1kPtIEYx0FIx1VDrLoVzXRKRYWk809yYlC9ImcrinxtabKnzRJk3lAU1OLEN1j2zrYzr2myHRXJFf4h4QKT1qSTzTB5+ZNTzTRkAxX8FcLV2uS8eoQQ2aAkFzvCM72sJIcJET3WPjRk5wi32uSS9rfZajpWEvj9hW42F4o5NytSXYy8IKHay10VYdrcl4SkqscrXpMwyGOgtkajheSxdQqmpxP1L3t4R5PqasFnrQEjytq6qgp9Y09Qx9o4S1FzhUCn1kyHSzBWLemoSGvOqLNhZyBjmCaAUYpMgt4Ck7wBBMMwWKWgjsUwTaGVsxWC1mYoKiyqqeGKYqonSIRQ3KIkHO0pmAxTdBHkbOvfllfr+AA+7gnc50huVKYK393FOyg7rbPO/izI7hE4CnHHHnJ0ogNPRUGeUpsrZZTBJcrovUcJe51BPsr6GkJdhCCsZ6aTtMEb2pqWkqeVtDXE/QVggsU/Nl86d9RMF3DxvZTA58agu810RWawCiSzzXBeU3MMW9oyJUedvNEvQyNu1f10BSMddR1vaLCYpYa/mGocLSiYDcLbQz8aMn5iyF4xBNMs1P0QEOV7o5gaWGuzSeLue4tt3ro7y4Tgm4G/mopdZgl6q0o6KzJWE3mMksNr3r+a6CbT8g5wZNzT9O7fi/zpaOmnz3BRoqos+tv9zMbdpxsqDBOEewtJLt7cg5wtKKbvldpSzRRCD43VFheCI7yZLppggMVBS/KMAdHODJvOwq2NQSbKKKPLdFWQs7Fqo+mpl01JXYRgq8dnGLhTiFzqmWsUMdpllZdbKlyvSdYxhI9YghOtxR8LgSLWHK62mGGVoxzBE8LNWzqH9CUesQzFy5RQzTc56mhi6fgXEWwpKfE5Z7M05ZgZUPmo6auiv8YKzDYwWBLMErIbKHJvOwIrvEdhOBcQ9JdU1NHQ7CXn2XIDFBKU2WAgcX9UAUzDXWd5alwuyJ41Z9rjKLCL4aCp4WarhPm2rH+SaHUYE001JDZ2ZAzXPjdMpZWvC9wmqIB2lLhQ01D5jO06hghWMndbM7yRJMsoCj1vYbnFQVrW9jak3OlEJ3s/96+p33dEPRV5GxiqaGjIthUU6FFEZyqCa5qJrpBdzSw95IUnOPIrCUUjRZQFrbw5PR0R1qiYx3cb6nrWUMrBmmiBQxVHtTew5ICP/ip6g4hed/Akob/32wvBHsIOX83cI8hGeNeNPCIkPmXe8fPKx84OMSRM1MTdXSwjCZ4S30jVGhvqTRak/OVhgGazHuOCud5onEO1lJr6ecVyaOK6H7zqlBlIaHE0oroCgfvGJIdPcmfLNGLjpz7hZwZQpUbFME0A1cIJa7VNORkgfsMBatbKgwwJM9bSvQXeNOvbIjelg6WWvo5kvbKaJJNHexkKNHL9xRyFlH8Ti2riB5wVPhUk7nGkJnoCe428LR/wRGdYIlmWebCyxou1rCk4g/ShugBDX0V0ZQWkh0dOVsagkM0yV6OoLd5ye+pRlsCr0n+KiQrGuq5yJDzrTAXHtLUMduTDBVKrSm3eHL+6ijxhFDX9Z5gVU/wliHYTMiMFpKLNMEywu80wd3meoFmt6VbRMPenhrOc6DVe4pgXU8DnnHakLOIIrlF4FZPIw6R+zxBP0dyq6OOZ4Q5sLKCcz084ok+VsMMyQhNZmmBgX5xIXOEJTmi7VsGTvMTNdHHhpzdbE8Du2oKxgvBqQKdDDnTFOylCFaxR1syz2iqrOI/FEpNc3C6f11/7+ASS6l2inq2ciTrCCzgyemrCL5SVPjQkdPZUmGy2c9Sw9FtR1sS30RmsKPCS4rkIC/2U0MduwucYolGaPjKEyhzmiPYXagyWbYz8LWBDdzRimAXzxx4z8K9hpzlhLq+NiQ97HuKorMUfK/OVvC2JfiHUPCQI/q7J2gjK+tTDNxkCc4TMssqCs4TGtLVwQihyoAWgj9bosU80XGW6Ac9TJGziaUh5+hnFcHOnlaM1iRn29NaqGENTTTSUHCH2tWTeV0osUhH6psuVLjRUmGWhm6OZEshGeNowABHcJ2Bpy2ZszRcKkRXd2QuKVEeXnbfaEq825FguqfgfE2whlChSRMdron+LATTPQ2Z369t4B9C5gs/ylzv+CMmepIDPclFQl13W0rspPd1JOcbghGOEutqCv5qacURQl3dDKyvyJlqKXGPgcM9FfawJAMVmdcspcYKOZc4GjDYkFlK05olNMHyHn4zFNykyOxt99RkHlfwmiHo60l2EKI+mhreEKp080Tbug08BVPcgoqC5zWt+NLDTZ7oNSF51N1qie7Va3uCCwyZbkINf/NED6jzOsBdZjFN8oqG3wxVunqCSYYKf3EdhJyf9YWGf7tRU2oH3VHgPr1fe5J9hOgHd7xQ0y7qBwXr23aGErP0cm64JVjZwsOGqL+mhNgZmhJLW2oY4UhedsyBgzrCKrq7BmcpNVhR6jBPq64Vgi+kn6XE68pp8J5/+0wRHGOpsKenQn9DZntPzjRLZpDAdD2fnSgkG9tmIXnUwQ6WVighs7Yi2MxQ0N3CqYaCXkJ0oyOztMDJjmSSpcpvlrk0RMMOjmArQ04PRV1DO1FwhCVaUVPpKUM03JK5SxPsIWRu8/CGHi8UHChiqGFDTbSRJWeYUDDcH6vJWUxR4k1FXbMUwV6e4AJFXS8oMqsZKqzvYQ9DDQdZckY4aGsIhtlubbd2r3j4QBMoTamdPZk7O/Bf62lacZwneNjQoGcdVU7zJOd7ghsUHOkosagic6cnWc8+4gg285R6zZP5s1/LUbCKIznTwK36PkdwlOrl4U1LwfdCCa+IrvFkmgw1PCAUXKWo0sURXWcI2muKJlgyFzhynCY4RBOsqCjoI1R5zREco0n2Vt09BQtYSizgKNHfUmUrQ5UOCh51BFcLmY7umhYqXKQomOop8bUnWNNQcIiBcYaC6xzMNOS8JQQfeqKBmmglB+97ok/lfk3ygaHSyZaCRTzRxQo6GzLfa2jWBPepw+UmT7SQEJyiyRkhBLMVOfcoMjcK0eZChfUNzFAUzCsEN5vP/X1uP/n/aoMX+K+nw/Hjr/9xOo7j7Pju61tLcgvJpTWXNbfN5jLpi6VfCOviTktKlFusQixdEKWmEBUKNaIpjZRSSOXSgzaaKLdabrm1/9nZ+/f+vd/vz/v9+Xy+zZ7PRorYoZqyLrCwQdEAixxVOEXNNnjX2nUSRlkqGmWowk8lxR50JPy9Bo6qJXaXwNvREBvnThPEPrewryLhcAnj5WE15Fqi8W7R1sAuEu86S4ENikItFN4xkv9Af4nXSnUVcLiA9xzesFpivRRVeFKtsMRaKBhuSbjOELnAUtlSQUpXgdfB4Z1oSbnFEetbQ0IrAe+Y+pqnDcEJFj6S8LDZzZHwY4e3XONNlARraomNEt2bkvGsosA3ioyHm+6jCMbI59wqt4eeara28IzEmyPgoRaUOEDhTVdEJhmCoTWfC0p8aNkCp0oYqih2iqGi4yXeMkOsn4LdLLnmKfh/YogjNsPebeFGR4m9BJHLzB61XQ3BtpISfS2FugsK9FAtLWX1dCRcrCnUp44CNzuCowUZmxSRgYaE6Za0W2u/E7CVXCiI/UOR8aAm1+OSyE3mOUcwyc1zBBeoX1kiKy0Zfxck1Gsyulti11i83QTBF5Kg3pDQThFMVHiPSlK+0cSedng/VaS8bOZbtsBcTcZAR8JP5KeqQ1OYKAi20njdNNRpgnsU//K+JnaXJaGTomr7aYIphoRn9aeShJWKEq9LcozSF7QleEfDI5LYm5bgVkFkRwVDBCVu0DDIkGupo8TZBq+/pMQURYErJQmPKGKjNDkWOLx7Jd5QizdUweIaKrlP7SwJDhZvONjLkOsBBX9UpGxnydhXkfBLQ8IxgojQbLFnJf81JytSljclYYyEFyx0kVBvKWOFJmONpshGAcsduQY5giVNCV51eOdJYo/pLhbvM0uDHSevNKRcrKZIqnCtJeEsO95RoqcgGK4ocZcho1tTYtcZvH41pNQ7vA0WrhIfOSraIIntIAi+NXWCErdbkvrWwjRLrt0NKUdL6KSOscTOdMSOUtBHwL6OLA0vNSdynaWQEnCpIvKaIrJJEbvHkmuNhn6OjM8VkSGSqn1uYJCGHnq9I3aLhNME3t6GjIkO7xrNFumpyTNX/NrwX7CrIRiqqWijI9JO4d1iieykyfiposQIQ8YjjsjlBh6oHWbwRjgYJQn2NgSnNycmJAk3NiXhx44Sxykihxm8ybUwT1OVKySc7vi3OXVkdBJ4AyXBeksDXG0IhgtYY0lY5ahCD0ehborIk5aUWRJviMA7Xt5kyRjonrXENkm8yYqgs8VzgrJmClK20uMM3jRJ0FiQICQF9hdETlLQWRIb5ki6WDfWRPobvO6a4GP5mcOrNzDFELtTkONLh9dXE8xypEg7z8A9jkhrQ6Fhjlg/QVktJXxt4WXzT/03Q8IaQWSqIuEvloQ2mqC9Jfi7wRul4RX3pSPlzpoVlmCtI2jvKHCFhjcM3sN6lqF6HxnKelLjXWbwrpR4xzuCrTUZx2qq9oAh8p6ixCUGr78g8oyjRAtB5CZFwi80VerVpI0h+IeBxa6Zg6kWvpDHaioYYuEsRbDC3eOmC2JvGYLeioxGknL2UATNJN6hmtj1DlpLvDVmocYbrGCVJKOrg4X6DgddLA203BKMFngdJJFtFd7vJLm6KEpc5yjQrkk7M80SGe34X24nSex1Ra5Omgb71JKyg8SrU3i/kARKwWpH0kOGhKkObyfd0ZGjvyXlAkVZ4xRbYJ2irFMkFY1SwyWxr2oo4zlNiV+7zmaweFpT4kR3kaDAFW6xpSqzJay05FtYR4HmZhc9UxKbbfF2V8RG1MBmSaE+kmC6JnaRXK9gsiXhJHl/U0qM0WTcbyhwkYIvFGwjSbjfwhiJt8ZSQU+Bd5+marPMOkVkD0muxYLIfEuhh60x/J92itguihJSEMySVPQnTewnEm+620rTQEMsOfo4/kP/0ARvWjitlpSX7GxBgcMEsd3EEeYWvdytd+Saawi6aCIj1CkGb6Aj9rwhx16Cf3vAwFy5pyLhVonXzy51FDpdEblbkdJbUcEPDEFzQ8qNmhzzLTmmKWKbFCXeEuRabp6rxbvAtLF442QjQ+wEA9eL1xSR7Q0JXzlSHjJ4exq89yR0laScJ/FW6z4a73pFMEfDiRZvuvijIt86RaSFOl01riV2mD1UEvxGk/Geg5aWwGki1zgKPG9J2U8PEg8qYvMsZeytiTRXBMslCU8JSlxi8EabjwUldlDNLfzTUmCgxWsjqWCOHavYAqsknKFIO0yQ61VL5AVFxk6WhEaCAkdJgt9aSkzXlKNX2jEa79waYuc7gq0N3GDJGCBhoiTXUEPsdknCUE1CK0fwsiaylSF2uiDyO4XX3pFhNd7R4itFGc0k/ElBZwWvq+GC6szVeEoS/MZ+qylwpKNKv9Z469UOjqCjwlusicyTxG6VpNxcQ8IncoR4RhLbR+NdpGGmJWOcIzJGUuKPGpQg8rrG21dOMqQssJQ4RxH5jaUqnZuQ0F4Q+cjxLwPtpZbIAk3QTJHQWBE5S1BokoVtDd6lhqr9UpHSUxMcIYl9pojsb8h4SBOsMQcqvOWC2E8EVehqiJ1hrrAEbQxeK0NGZ0Gkq+guSRgniM23bIHVkqwx4hiHd7smaOyglyIyQuM978j4VS08J/A2G1KeMBRo4fBaSNhKUEZfQewVQ/C1I+MgfbEleEzCUw7mKXI0M3hd1EESVji8x5uQ41nxs1q4RMJCCXs7Iq9acpxn22oSDnQ/sJTxsCbHIYZiLyhY05TY0ZLIOQrGaSJDDN4t8pVaIrsqqFdEegtizc1iTew5Q4ayBDMUsQMkXocaYkc0hZua412siZ1rSXlR460zRJ5SlHGe5j801RLMlJTxtaOM3Q1pvxJ45zUlWFD7rsAbpfEm1JHxG0eh8w2R7QQVzBUw28FhFp5QZzq8t2rx2joqulYTWSuJdTYfWwqMFMcovFmSyJPNyLhE4E10pHzYjOC3huArRa571ZsGajQpQx38SBP5pyZB6lMU3khDnp0MBV51BE9o2E+TY5Ml2E8S7C0o6w1xvCZjf0HkVEHCzFoyNmqC+9wdcqN+Tp7jSDheE9ws8Y5V0NJCn2bk2tqSY4okdrEhx1iDN8cSudwepWmAGXKcJXK65H9to8jYQRH7SBF01ESUJdd0TayVInaWhLkOjlXE5irKGOnI6GSWGCJa482zBI9rCr0jyTVcEuzriC1vcr6mwFGSiqy5zMwxBH/TJHwjSPhL8+01kaaSUuMFKTcLEvaUePcrSmwn8DZrgikWb7CGPxkSjhQwrRk57tctmxLsb9sZvL9LSlyuSLlWkqOjwduo8b6Uv1DkmudIeFF2dHCgxVtk8dpIvHpBxhEOdhKk7OLIUSdJ+cSRY57B+0DgGUUlNfpthTfGkauzxrvTsUUaCVhlKeteTXCoJDCa2NOKhOmC4G1H8JBd4OBZReSRGkqcb/CO1PyLJTLB4j1q8JYaIutEjSLX8YKM+a6phdMsdLFUoV5RTm9JSkuDN8WcIon0NZMNZWh1q8C7SJEwV5HxrmnnTrf3KoJBlmCYI2ilSLlfEvlE4011NNgjgthzEua0oKK7JLE7HZHlEl60BLMVFewg4EWNt0ThrVNEVkkiTwpKXSWJzdRENgvKGq4IhjsiezgSFtsfCUq8qki5S1LRQeYQQ4nemmCkImWMw3tFUoUBZk4NOeZYEp4XRKTGa6wJjrWNHBVJR4m3FCnbuD6aak2WsMTh3SZImGCIPKNgsDpVwnsa70K31lCFJZYcwwSMFcQulGTsZuEaSdBXkPGZhu0FsdUO73RHjq8MPGGIfaGIbVTk6iuI3GFgucHrIQkmWSJdBd7BBu+uOryWAhY7+Lki9rK5wtEQzWwvtbqGhIMFwWRJsElsY4m9IIg9L6lCX0VklaPAYkfkZEGDnOWowlBJjtMUkcGK4Lg6EtoZInMUBVYLgn0UsdmCyCz7gIGHFfk+k1QwTh5We7A9x+IdJ6CvIkEagms0hR50eH9UnTQJ+2oiKyVlLFUE+8gBGu8MQ3CppUHesnjTHN4QB/UGPhCTHLFPHMFrCqa73gqObUJGa03wgbhHkrCfpEpzNLE7JDS25FMKhlhKKWKfCgqstLCPu1zBXy0J2ztwjtixBu8UTRn9LVtkmCN2iyFhtME70JHRQ1KVZXqKI/KNIKYMCYs1GUMEKbM1bKOI9LDXC7zbHS+bt+1MTWS9odA9DtrYtpbImQJ2VHh/lisEwaHqUk1kjKTAKknkBEXkbkdMGwq0dnhzLJF3NJH3JVwrqOB4Sca2hti75nmJN0WzxS6UxDYoEpxpa4htVlRjkYE7DZGzJVU72uC9IyhQL4i8YfGWSYLLNcHXloyz7QhNifmKSE9JgfGmuyLhc403Xm9vqcp6gXe3xuuv8F6VJNxkyTHEkHG2g0aKXL0MsXc1bGfgas2//dCONXiNLCX+5mB7eZIl1kHh7ajwpikyzlUUWOVOsjSQlsS+M0R+pPje/dzBXRZGO0rMtgQrLLG9VSu9n6CMXS3BhwYmSoIBhsjNBmZbgusE9BCPCP5triU4VhNbJfE+swSP27aayE8tuTpYYjtrYjMVGZdp2NpS1s6aBnKSHDsbKuplKbHM4a0wMFd/5/DmGyKrJSUaW4IBrqUhx0vyfzTBBLPIUcnZdrAkNsKR0sWRspumSns6Ch0v/qqIbBYUWKvPU/CFoyrDJGwSNFhbA/MlzKqjrO80hRbpKx0Jewsi/STftwGSlKc1JZyAzx05dhLEdnfQvhZOqiHWWEAHC7+30FuRcZUgaO5gpaIK+xsiHRUsqaPElTV40xQZQ107Q9BZE1nryDVGU9ZSQ47bmhBpLcYpUt7S+xuK/FiT8qKjwXYw5ypS2iuCv7q1gtgjhuBuB8LCFY5cUuCNtsQOFcT+4Ih9JX+k8Ea6v0iCIRZOtCT0Et00JW5UeC85Cg0ScK0k411HcG1zKtre3SeITBRk7WfwDhEvaYLTHP9le0m8By0JDwn4TlLW/aJOvGHxdjYUes+ScZigCkYQdNdEOhkiezgShqkx8ueKjI8lDfK2oNiOFvrZH1hS+tk7NV7nOmLHicGWEgubkXKdwdtZknCLJXaCpkrjZBtLZFsDP9CdxWsSr05Sxl6CMmoFbCOgryX40uDtamB7SVmXW4Ihlgpmq+00tBKUUa83WbjLUNkzDmY7cow1JDygyPGlhgGKYKz4vcV7QBNbJIgM11TUqZaMdwTeSguH6rOaw1JRKzaaGyxVm2EJ/uCIrVWUcZUkcp2grMsEjK+DMwS59jQk3Kd6SEq1d0S6uVmO4Bc1lDXTUcHjluCXEq+1OlBDj1pi9zgiXxnKuE0SqTXwhqbETW6RggMEnGl/q49UT2iCzgJvRwVXS2K/d6+ZkyUl7jawSVLit46EwxVljDZwoSQ20sDBihztHfk2yA8NVZghiXwrYHQdfKAOtzsayjhY9bY0yE2CWEeJ9xfzO423xhL5syS2TFJofO2pboHob0nY4GiAgRrvGQEDa/FWSsoaaYl0syRsEt3kWoH3B01shCXhTUWe9w3Bt44SC9QCh3eShQctwbaK2ApLroGCMlZrYqvlY3qYhM0aXpFkPOuoqJ3Dm6fxXrGwVF9gCWZagjPqznfkuMKQ8DPTQRO8ZqG1hPGKEm9IgpGW4DZDgTNriTxvFiq+Lz+0cKfp4wj6OCK9JSnzNSn9LFU7UhKZZMnYwcJ8s8yRsECScK4j5UOB95HFO0CzhY4xJxuCix0lDlEUeMdS6EZBkTsUkZ4K74dugyTXS7aNgL8aqjDfkCE0ZbwkCXpaWCKhl8P7VD5jxykivSyxyZrYERbe168LYu9ZYh86IkscgVLE7tWPKmJv11CgoyJltMEbrohtVAQfO4ImltiHEroYEs7RxAarVpY8AwXMcMReFOTYWe5iiLRQxJ5Q8DtJ8LQhWOhIeFESPGsILhbNDRljNbHzNRlTFbk2S3L0NOS6V1KFJYKUbSTcIIhM0wQ/s2TM0SRMNcQmSap3jCH4yhJZKSkwyRHpYYgsFeQ4U7xoCB7VVOExhXepo9ABBsYbvGWKXPME3lyH95YioZ0gssQRWWbI+FaSMkXijZXwgiTlYdPdkNLaETxlyDVIwqeaEus0aTcYcg0RVOkpR3CSJqIddK+90JCxzsDVloyrFd5ZAr4TBKfaWa6boEA7C7s6EpYaeFPjveooY72mjIccLHJ9HUwVlDhKkmutJDJBwnp1rvulJZggKDRfbXAkvC/4l3ozQOG9a8lxjx0i7nV4jSXc7vhe3OwIxjgSHjdEhhsif9YkPGlus3iLFDnWOFhtCZbJg0UbQcIaR67JjthoCyMEZRwhiXWyxO5QxI6w5NhT4U1WsJvDO60J34fW9hwzwlKij6ZAW9ne4L0s8C6XeBMEkd/LQy1VucBRot6QMlbivaBhoBgjqGiCJNhsqVp/S2SsG6DIONCR0dXhvWbJ+MRRZJkkuEjgDXJjFQW6SSL7GXK8Z2CZg7cVsbWGoKmEpzQ5elpiy8Ryg7dMkLLUEauzeO86CuwlSOlgYLojZWeJ9xM3S1PWfEfKl5ISLQ0MEKR8YOB2QfCxJBjrKPCN4f9MkaSsqoVXJBmP7EpFZ9UQfOoOFwSzBN4MQ8LsGrymlipcJQhmy0GaQjPqCHaXRwuCZwRbqK2Fg9wlClZqYicrIgMdZfxTQ0c7TBIbrChxmuzoKG8XRaSrIhhiyNFJkrC7oIAWMEOQa5aBekPCRknCo4IKPrYkvCDI8aYmY7WFtprgekcJZ3oLIqssCSMtFbQTJKwXYy3BY5oCh2iKPCpJOE+zRdpYgi6O2KmOAgvVCYaU4ySRek1sgyFhJ403QFHiVEmJHwtybO1gs8Hr5+BETQX3War0qZngYGgtVZtoqd6vFSk/UwdZElYqyjrF4HXUeFspIi9IGKf4j92pKGAdCYMVsbcV3kRF0N+R8LUd5PCsIGWoxDtBkCI0nKofdJQxT+LtZflvuc8Q3CjwWkq8KwUpHzkK/NmSsclCL0nseQdj5FRH5CNHSgtLiW80Of5HU9Hhlsga9bnBq3fEVltKfO5IaSTmGjjc4J0otcP7QsJUSQM8pEj5/wCuUuC2DWz8AAAAAElFTkSuQmCC"); -} diff --git a/applications/admin/static/codemirror/theme/the-matrix.css b/applications/admin/static/codemirror/theme/the-matrix.css index 0c3704a6..01474ca9 100644 --- a/applications/admin/static/codemirror/theme/the-matrix.css +++ b/applications/admin/static/codemirror/theme/the-matrix.css @@ -1,6 +1,8 @@ .cm-s-the-matrix.CodeMirror { background: #000000; color: #00FF00; } .cm-s-the-matrix div.CodeMirror-selected { background: #2D2D2D !important; } .cm-s-the-matrix .CodeMirror-gutters { background: #060; border-right: 2px solid #00FF00; } +.cm-s-the-matrix .CodeMirror-guttermarker { color: #0f0; } +.cm-s-the-matrix .CodeMirror-guttermarker-subtle { color: white; } .cm-s-the-matrix .CodeMirror-linenumber { color: #FFFFFF; } .cm-s-the-matrix .CodeMirror-cursor { border-left: 1px solid #00FF00 !important; } diff --git a/applications/admin/static/codemirror/theme/tomorrow-night-bright.css b/applications/admin/static/codemirror/theme/tomorrow-night-bright.css new file mode 100644 index 00000000..decb82d3 --- /dev/null +++ b/applications/admin/static/codemirror/theme/tomorrow-night-bright.css @@ -0,0 +1,35 @@ +/* + + Name: Tomorrow Night - Bright + Author: Chris Kempson + + Port done by Gerard Braad + +*/ + +.cm-s-tomorrow-night-bright.CodeMirror {background: #000000; color: #eaeaea;} +.cm-s-tomorrow-night-bright div.CodeMirror-selected {background: #424242 !important;} +.cm-s-tomorrow-night-bright .CodeMirror-gutters {background: #000000; border-right: 0px;} +.cm-s-tomorrow-night-bright .CodeMirror-guttermarker { color: #e78c45; } +.cm-s-tomorrow-night-bright .CodeMirror-guttermarker-subtle { color: #777; } +.cm-s-tomorrow-night-bright .CodeMirror-linenumber {color: #424242;} +.cm-s-tomorrow-night-bright .CodeMirror-cursor {border-left: 1px solid #6A6A6A !important;} + +.cm-s-tomorrow-night-bright span.cm-comment {color: #d27b53;} +.cm-s-tomorrow-night-bright span.cm-atom {color: #a16a94;} +.cm-s-tomorrow-night-bright span.cm-number {color: #a16a94;} + +.cm-s-tomorrow-night-bright span.cm-property, .cm-s-tomorrow-night-bright span.cm-attribute {color: #99cc99;} +.cm-s-tomorrow-night-bright span.cm-keyword {color: #d54e53;} +.cm-s-tomorrow-night-bright span.cm-string {color: #e7c547;} + +.cm-s-tomorrow-night-bright span.cm-variable {color: #b9ca4a;} +.cm-s-tomorrow-night-bright span.cm-variable-2 {color: #7aa6da;} +.cm-s-tomorrow-night-bright span.cm-def {color: #e78c45;} +.cm-s-tomorrow-night-bright span.cm-bracket {color: #eaeaea;} +.cm-s-tomorrow-night-bright span.cm-tag {color: #d54e53;} +.cm-s-tomorrow-night-bright span.cm-link {color: #a16a94;} +.cm-s-tomorrow-night-bright span.cm-error {background: #d54e53; color: #6A6A6A;} + +.cm-s-tomorrow-night-bright .CodeMirror-activeline-background {background: #2a2a2a !important;} +.cm-s-tomorrow-night-bright .CodeMirror-matchingbracket { text-decoration: underline; color: white !important;} diff --git a/applications/admin/static/codemirror/theme/tomorrow-night-eighties.css b/applications/admin/static/codemirror/theme/tomorrow-night-eighties.css index 85c2a4a7..84141354 100644 --- a/applications/admin/static/codemirror/theme/tomorrow-night-eighties.css +++ b/applications/admin/static/codemirror/theme/tomorrow-night-eighties.css @@ -11,6 +11,8 @@ .cm-s-tomorrow-night-eighties.CodeMirror {background: #000000; color: #CCCCCC;} .cm-s-tomorrow-night-eighties div.CodeMirror-selected {background: #2D2D2D !important;} .cm-s-tomorrow-night-eighties .CodeMirror-gutters {background: #000000; border-right: 0px;} +.cm-s-tomorrow-night-eighties .CodeMirror-guttermarker { color: #f2777a; } +.cm-s-tomorrow-night-eighties .CodeMirror-guttermarker-subtle { color: #777; } .cm-s-tomorrow-night-eighties .CodeMirror-linenumber {color: #515151;} .cm-s-tomorrow-night-eighties .CodeMirror-cursor {border-left: 1px solid #6A6A6A !important;} diff --git a/applications/admin/static/codemirror/theme/twilight.css b/applications/admin/static/codemirror/theme/twilight.css index 19d6abad..9ca50576 100644 --- a/applications/admin/static/codemirror/theme/twilight.css +++ b/applications/admin/static/codemirror/theme/twilight.css @@ -2,6 +2,8 @@ .cm-s-twilight .CodeMirror-selected { background: #323232 !important; } /**/ .cm-s-twilight .CodeMirror-gutters { background: #222; border-right: 1px solid #aaa; } +.cm-s-twilight .CodeMirror-guttermarker { color: white; } +.cm-s-twilight .CodeMirror-guttermarker-subtle { color: #aaa; } .cm-s-twilight .CodeMirror-linenumber { color: #aaa; } .cm-s-twilight .CodeMirror-cursor { border-left: 1px solid white !important; } diff --git a/applications/admin/static/codemirror/theme/vibrant-ink.css b/applications/admin/static/codemirror/theme/vibrant-ink.css index 0206225b..51772823 100644 --- a/applications/admin/static/codemirror/theme/vibrant-ink.css +++ b/applications/admin/static/codemirror/theme/vibrant-ink.css @@ -4,6 +4,8 @@ .cm-s-vibrant-ink .CodeMirror-selected { background: #35493c !important; } .cm-s-vibrant-ink .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } +.cm-s-vibrant-ink .CodeMirror-guttermarker { color: white; } +.cm-s-vibrant-ink .CodeMirror-guttermarker-subtle { color: #d0d0d0; } .cm-s-vibrant-ink .CodeMirror-linenumber { color: #d0d0d0; } .cm-s-vibrant-ink .CodeMirror-cursor { border-left: 1px solid white !important; } diff --git a/applications/admin/static/codemirror/theme/xq-dark.css b/applications/admin/static/codemirror/theme/xq-dark.css index 4a0b2138..116eccf2 100644 --- a/applications/admin/static/codemirror/theme/xq-dark.css +++ b/applications/admin/static/codemirror/theme/xq-dark.css @@ -23,6 +23,8 @@ THE SOFTWARE. .cm-s-xq-dark.CodeMirror { background: #0a001f; color: #f8f8f8; } .cm-s-xq-dark .CodeMirror-selected { background: #27007A !important; } .cm-s-xq-dark .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; } +.cm-s-xq-dark .CodeMirror-guttermarker { color: #FFBD40; } +.cm-s-xq-dark .CodeMirror-guttermarker-subtle { color: #f8f8f8; } .cm-s-xq-dark .CodeMirror-linenumber { color: #f8f8f8; } .cm-s-xq-dark .CodeMirror-cursor { border-left: 1px solid white !important; } diff --git a/applications/admin/static/codemirror/theme/zenburn.css b/applications/admin/static/codemirror/theme/zenburn.css new file mode 100644 index 00000000..f817198a --- /dev/null +++ b/applications/admin/static/codemirror/theme/zenburn.css @@ -0,0 +1,37 @@ +/** + * " + * Using Zenburn color palette from the Emacs Zenburn Theme + * https://github.com/bbatsov/zenburn-emacs/blob/master/zenburn-theme.el + * + * Also using parts of https://github.com/xavi/coderay-lighttable-theme + * " + * From: https://github.com/wisenomad/zenburn-lighttable-theme/blob/master/zenburn.css + */ + +.cm-s-zenburn .CodeMirror-gutters { background: #3f3f3f !important; } +.cm-s-zenburn .CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded { color: #999; } +.cm-s-zenburn .CodeMirror-cursor { border-left: 1px solid white !important; } +.cm-s-zenburn { background-color: #3f3f3f; color: #dcdccc; } +.cm-s-zenburn span.cm-builtin { color: #dcdccc; font-weight: bold; } +.cm-s-zenburn span.cm-comment { color: #7f9f7f; } +.cm-s-zenburn span.cm-keyword { color: #f0dfaf; font-weight: bold; } +.cm-s-zenburn span.cm-atom { color: #bfebbf; } +.cm-s-zenburn span.cm-def { color: #dcdccc; } +.cm-s-zenburn span.cm-variable { color: #dfaf8f; } +.cm-s-zenburn span.cm-variable-2 { color: #dcdccc; } +.cm-s-zenburn span.cm-string { color: #cc9393; } +.cm-s-zenburn span.cm-string-2 { color: #cc9393; } +.cm-s-zenburn span.cm-number { color: #dcdccc; } +.cm-s-zenburn span.cm-tag { color: #93e0e3; } +.cm-s-zenburn span.cm-property { color: #dfaf8f; } +.cm-s-zenburn span.cm-attribute { color: #dfaf8f; } +.cm-s-zenburn span.cm-qualifier { color: #7cb8bb; } +.cm-s-zenburn span.cm-meta { color: #f0dfaf; } +.cm-s-zenburn span.cm-header { color: #f0efd0; } +.cm-s-zenburn span.cm-operator { color: #f0efd0; } +.cm-s-zenburn span.CodeMirror-matchingbracket { box-sizing: border-box; background: transparent; border-bottom: 1px solid; } +.cm-s-zenburn span.CodeMirror-nonmatchingbracket { border-bottom: 1px solid; background: none; } +.cm-s-zenburn .CodeMirror-activeline { background: #000000; } +.cm-s-zenburn .CodeMirror-activeline-background { background: #000000; } +.cm-s-zenburn .CodeMirror-selected { background: #545454; } +.cm-s-zenburn .CodeMirror-focused .CodeMirror-selected { background: #4f4f4f; } diff --git a/applications/admin/static/js/jquery.js b/applications/admin/static/js/jquery.js index ab28a247..e6a051d0 100644 --- a/applications/admin/static/js/jquery.js +++ b/applications/admin/static/js/jquery.js @@ -1,4 +1,4 @@ -/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h; -if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("');storageContainer.close();storageOwner=storageContainer.w.frames[0].document;storage=storageOwner.createElement("div")}catch(e){storage=doc.createElement("div");storageOwner=doc.body}function withIEStorage(storeFunction){return function(){var args=Array.prototype.slice.call(arguments,0);args.unshift(storage);storageOwner.appendChild(storage);storage.addBehavior("#default#userData");storage.load(localStorageName);var result=storeFunction.apply(store,args);storageOwner.removeChild(storage);return result}}var forbiddenCharsRegex=new RegExp("[!\"#$%&'()*+,/\\\\:;<=>?@[\\]^`{|}~]","g");function ieKeyFix(key){return key.replace(forbiddenCharsRegex,"___")}store.set=withIEStorage(function(storage,key,val){key=ieKeyFix(key);if(val===undefined){return store.remove(key)}storage.setAttribute(key,store.serialize(val));storage.save(localStorageName);return val});store.get=withIEStorage(function(storage,key){key=ieKeyFix(key);return store.deserialize(storage.getAttribute(key))});store.remove=withIEStorage(function(storage,key){key=ieKeyFix(key);storage.removeAttribute(key);storage.save(localStorageName)});store.clear=withIEStorage(function(storage){var attributes=storage.XMLDocument.documentElement.attributes;storage.load(localStorageName);for(var i=0,attr;attr=attributes[i];i++){storage.removeAttribute(attr.name)}storage.save(localStorageName)});store.getAll=withIEStorage(function(storage){var attributes=storage.XMLDocument.documentElement.attributes;var ret={};for(var i=0,attr;attr=attributes[i];++i){var key=ieKeyFix(attr.name);ret[attr.name]=store.deserialize(storage.getAttribute(key))}return ret})}try{store.set(namespace,namespace);if(store.get(namespace)!=namespace){store.disabled=true}store.remove(namespace)}catch(e){store.disabled=true}store.enabled=!store.disabled;module.exports=store},{json:179}],219:[function(require,module,exports){module.exports=function(a,b){var fn=function(){};fn.prototype=b.prototype;a.prototype=new fn;a.prototype.constructor=a}},{}],208:[function(require,module,exports){module.exports=function isMeta(e){if(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)return true;var which=e.which,button=e.button;if(!which&&button!==undefined){return!button&1&&!button&2&&button&4}else if(which===2){return true}return false}},{}],209:[function(require,module,exports){exports.bind=function(el,type,fn,capture){if(el.addEventListener){el.addEventListener(type,fn,capture||false)}else{el.attachEvent("on"+type,fn)}return fn};exports.unbind=function(el,type,fn,capture){if(el.removeEventListener){el.removeEventListener(type,fn,capture||false)}else{el.detachEvent("on"+type,fn)}return fn}},{}],210:[function(require,module,exports){var canonical=require("canonical");var url=require("url");function pageDefaults(){return{path:canonicalPath(),referrer:document.referrer,search:location.search,title:document.title,url:canonicalUrl(location.search)}}function canonicalPath(){var canon=canonical();if(!canon)return window.location.pathname;var parsed=url.parse(canon);return parsed.pathname}function canonicalUrl(search){var canon=canonical();if(canon)return~canon.indexOf("?")?canon:canon+search;var url=window.location.href;var i=url.indexOf("#");return-1===i?url:url.slice(0,i)}module.exports=pageDefaults},{canonical:182,url:184}],211:[function(require,module,exports){"use strict";var objToString=Object.prototype.toString;var existy=function(val){return val!=null};var isArray=function(val){return objToString.call(val)==="[object Array]"};var isString=function(val){return typeof val==="string"||objToString.call(val)==="[object String]"};var isObject=function(val){return val!=null&&typeof val==="object"};var pick=function pick(props,object){if(!existy(object)||!isObject(object)){return{}}if(isString(props)){props=[props]}if(!isArray(props)){props=[]}var result={};for(var i=0;i",ret);return ret;function integration(name){return!!(~indexof(list,name)||"all"==name.toLowerCase()||~indexof(lower,name.toLowerCase()))}}},{debug:201,"component/indexof":116,defaults:97,"component/map":221,each:4,is:91}],221:[function(require,module,exports){var toFunction=require("to-function");module.exports=function(arr,fn){var ret=[];fn=toFunction(fn);for(var i=0;ithis.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m(" + +
+ +
+ +""" % dict(public_key=public_key)) + ) + ) + if not self.errors.captcha: + return XML(captcha).xml() + else: + captcha.append(DIV(self.errors['captcha'], _class='error')) + return XML(captcha).xml() + + +# this should only be used for captcha and perhaps not even for that def addrow(form, a, b, c, style, _id, position=-1): if style == "divs": form[0].insert(position, DIV(DIV(LABEL(a), _class='w2p_fl'), @@ -978,6 +1125,15 @@ def addrow(form, a, b, c, style, _id, position=-1): DIV(b, SPAN(c, _class='inline-help'), _class='controls'), _class='control-group', _id=_id)) + elif style == "bootstrap3_inline": + form[0].insert(position, DIV(LABEL(a, _class='control-label col-sm-3'), + DIV(b, SPAN(c, _class='help-block'), + _class='col-sm-9'), + _class='form-group', _id=_id)) + elif style == "bootstrap3_stacked": + form[0].insert(position, DIV(LABEL(a, _class='control-label'), + b, SPAN(c, _class='help-block'), + _class='form-group', _id=_id)) else: form[0].insert(position, TR(TD(LABEL(a), _class='w2p_fl'), TD(b, _class='w2p_fw'), @@ -993,6 +1149,7 @@ class Auth(object): reset_password_requires_verification=False, registration_requires_verification=False, registration_requires_approval=False, + bulk_register_enabled=False, login_after_registration=False, login_after_password_change=True, alternate_requires_registration=False, @@ -1016,7 +1173,7 @@ class Auth(object): allow_basic_login_only=False, on_failed_authentication=lambda x: redirect(x), formstyle=None, - label_separator=": ", + label_separator=None, logging_enabled = True, allow_delete_accounts=False, password_field='password', @@ -1026,6 +1183,7 @@ class Auth(object): table_permission_name='auth_permission', table_event_name='auth_event', table_cas_name='auth_cas', + table_token_name='auth_token', table_user=None, table_group=None, table_membership=None, @@ -1043,14 +1201,14 @@ class Auth(object): profile_fields=None, email_case_sensitive=True, username_case_sensitive=True, - update_fields = ['email'], + update_fields=['email'], ondelete="CASCADE", - client_side = True, + client_side=True, renew_session_onlogin=True, renew_session_onlogout=True, keep_session_onlogin=True, keep_session_onlogout=False, - wiki = Settings(), + wiki=Settings(), ) # ## these are messages that can be customized default_messages = dict( @@ -1092,9 +1250,10 @@ class Auth(object): retrieve_username_subject='Username retrieve', retrieve_password='Your password is: %(password)s', retrieve_password_subject='Password retrieve', - reset_password= - 'Click on the link %(link)s to reset your password', + reset_password='Click on the link %(link)s to reset your password', reset_password_subject='Password reset', + bulk_invite_subject='Invitation to join%(site)s', + bulk_invite_body='You have been invited to join %(site)s, click %(link)s to complete the process', invalid_reset_password='Invalid reset password', profile_updated='Profile updated', new_password='New password', @@ -1256,7 +1415,7 @@ class Auth(object): f=f, args=args, vars=vars, scheme=scheme) def here(self): - return URL(args=current.request.args,vars=current.request.get_vars) + return URL(args=current.request.args, vars=current.request.get_vars) def __init__(self, environment=None, db=None, mailer=True, hmac_key=None, controller='default', function='user', @@ -1308,6 +1467,7 @@ class Auth(object): settings.update(Auth.default_settings) settings.update( cas_domains=[request.env.http_host], + enable_tokens=False, cas_provider=cas_provider, cas_actions=dict(login='login', validate='validate', @@ -1321,9 +1481,8 @@ class Auth(object): login_url=url_login, logged_url=URL(controller, function, args='profile'), download_url=URL(controller, 'download'), - mailer=(mailer == True) and Mail() or mailer, - on_failed_authorization = - URL(controller, function, args='not_authorized'), + mailer=(mailer is True) and Mail() or mailer, + on_failed_authorization = URL(controller, function, args='not_authorized'), login_next = url_index, login_onvalidation = [], login_onaccept = [], @@ -1348,24 +1507,27 @@ class Auth(object): change_password_onvalidation = [], change_password_onaccept = [], retrieve_password_onvalidation = [], + request_reset_password_onvalidation = [], + request_reset_password_onaccept = [], reset_password_onvalidation = [], reset_password_onaccept = [], hmac_key = hmac_key, formstyle = current.response.formstyle, + label_separator = current.response.form_label_separator ) settings.lock_keys = True - # ## these are messages that can be customized messages = self.messages = Messages(current.T) messages.update(Auth.default_messages) - messages.update(ajax_failed_authentication=DIV(H4('NOT AUTHORIZED'), - 'Please ', - A('login', - _href=self.settings.login_url + - ('?_next=' + urllib.quote(current.request.env.http_web2py_component_location)) - if current.request.env.http_web2py_component_location else ''), - ' to view this content.', - _class='not-authorized alert alert-block')) + messages.update(ajax_failed_authentication= + DIV(H4('NOT AUTHORIZED'), + 'Please ', + A('login', + _href=self.settings.login_url + + ('?_next=' + urllib.quote(current.request.env.http_web2py_component_location)) + if current.request.env.http_web2py_component_location else ''), + ' to view this content.', + _class='not-authorized alert alert-block')) messages.lock_keys = True # for "remember me" option @@ -1382,10 +1544,16 @@ class Auth(object): next = current.request.vars._next if isinstance(next, (list, tuple)): next = next[0] + if next and self.settings.prevent_open_redirect_attacks: + # Prevent an attacker from adding an arbitrary url after the + # _next variable in the request. + items = next.split('/') + if '//' in next and items[2] != current.request.env.http_host: + next = None return next def _get_user_id(self): - "accessor for auth.user_id" + """accessor for auth.user_id""" return self.user and self.user.id or None user_id = property(_get_user_id, doc="user.id or None") @@ -1408,6 +1576,9 @@ class Auth(object): def table_cas(self): return self.db[self.settings.table_cas_name] + def table_token(self): + return self.db[self.settings.table_token_name] + def _HTTP(self, *a, **b): """ only used in lambda: self._HTTP(404) @@ -1435,7 +1606,8 @@ class Auth(object): 'retrieve_username', 'retrieve_password', 'reset_password', 'request_reset_password', 'change_password', 'profile', 'groups', - 'impersonate', 'not_authorized'): + 'impersonate', 'not_authorized', 'confirm_registration', + 'bulk_register','manage_tokens'): if len(request.args) >= 2 and args[0] == 'impersonate': return getattr(self, args[0])(request.args[1]) else: @@ -1538,10 +1710,10 @@ class Auth(object): self.bar[0][3].append((item['name'], False, item['href'])) def bootstrap3(): # Default web2py scaffolding - def rename(icon): return icon+' '+icon.replace('icon','glyphicon') + def rename(icon): return icon+' '+icon.replace('icon', 'glyphicon') self.bar = UL(LI(Anr(I(_class=rename('icon '+items[0]['icon'])), ' ' + items[0]['name'], - _href=items[0]['href'])),_class='dropdown-menu') + _href=items[0]['href'])), _class='dropdown-menu') del items[0] for item in items: self.bar.insert(-1, LI(Anr(I(_class=rename('icon '+item['icon'])), @@ -1550,13 +1722,13 @@ class Auth(object): self.bar.insert(-1, LI('', _class='divider')) if self.user_id: self.bar = LI(Anr(prefix, user_identifier, - _href='#',_class="dropdown-toggle", - data={'toggle':'dropdown'}), - self.bar,_class='dropdown') + _href='#', _class="dropdown-toggle", + data={'toggle': 'dropdown'}), + self.bar, _class='dropdown') else: self.bar = LI(Anr(T('Log In'), - _href='#',_class="dropdown-toggle", - data={'toggle':'dropdown'}), self.bar, + _href='#', _class="dropdown-toggle", + data={'toggle': 'dropdown'}), self.bar, _class='dropdown') def bare(): @@ -1707,7 +1879,7 @@ class Auth(object): """ current_record_label = current_record_label or current.T( - current_record.replace('_',' ').title()) + current_record.replace('_', ' ').title()) for table in tables: fieldnames = table.fields() if ('id' in fieldnames and @@ -1762,7 +1934,7 @@ class Auth(object): writable=False, readable=False, label=T('Modified By'), ondelete=ondelete)) - def define_tables(self, username=None, signature=None, + def define_tables(self, username=None, signature=None, enable_tokens=False, migrate=None, fake_migrate=None): """ To be called unless tables are defined manually @@ -1780,13 +1952,16 @@ class Auth(object): """ db = self.db - if migrate is None: migrate = db._migrate - if fake_migrate is None: fake_migrate = db._fake_migrate + if migrate is None: + migrate = db._migrate + if fake_migrate is None: + fake_migrate = db._fake_migrate settings = self.settings if username is None: username = settings.use_username else: settings.use_username = username + settings.enable_tokens = enable_tokens if not self.signature: self.define_signature() if signature == True: @@ -1888,8 +2063,7 @@ class Auth(object): settings.table_group_name, Field('role', length=512, default='', label=self.messages.label_role, - requires=IS_NOT_IN_DB( - db, '%s.role' % settings.table_group_name)), + requires=IS_NOT_IN_DB(db, '%s.role' % settings.table_group_name)), Field('description', 'text', label=self.messages.label_description), *extra_fields, @@ -1971,6 +2145,21 @@ class Auth(object): migrate=self.__get_migrate( settings.table_cas_name, migrate), fake_migrate=fake_migrate)) + if settings.enable_tokens: + extra_fields = settings.extra_fields.get( + settings.table_token_name, []) + signature_list + if not settings.table_token_name in db.tables: + db.define_table( + settings.table_token_name, + Field('user_id', reference_table_user, default=None, + label=self.messages.label_user_id), + Field('expires_on', 'datetime', default=datetime.datetime(2999,12,31)), + Field('token',writable=False,default=web2py_uuid(),unique=True), + *extra_fields, + **dict( + migrate=self.__get_migrate( + settings.table_token_name, migrate), + fake_migrate=fake_migrate)) if not db._lazy_tables: settings.table_user = db[settings.table_user_name] settings.table_group = db[settings.table_group_name] @@ -2109,8 +2298,7 @@ class Auth(object): basic_realm = unicode(basic_auth_realm) elif basic_auth_realm is True: basic_realm = u'' + current.request.application - http_401 = HTTP(401, u'Not Authorized', - **{'WWW-Authenticate': u'Basic realm="' + basic_realm + '"'}) + http_401 = HTTP(401, u'Not Authorized', **{'WWW-Authenticate': u'Basic realm="' + basic_realm + '"'}) if not basic or not basic[:6].lower() == 'basic ': if basic_auth_realm: raise http_401 @@ -2128,19 +2316,18 @@ class Auth(object): from gluon.settings import global_settings if global_settings.web2py_runtime_gae: user = Row(self.table_user()._filter_fields(user, id=True)) - delattr(user,'password') + delattr(user, 'password') else: user = Row(user) for key, value in user.items(): - if callable(value) or key=='password': - delattr(user,key) + if callable(value) or key == 'password': + delattr(user, key) if self.settings.renew_session_onlogin: current.session.renew(clear_session=not self.settings.keep_session_onlogin) - current.session.auth = Storage( - user = user, - last_visit=current.request.now, - expiration=self.settings.expiration, - hmac_key=web2py_uuid()) + current.session.auth = Storage(user=user, + last_visit=current.request.now, + expiration=self.settings.expiration, + hmac_key=web2py_uuid()) self.user = user self.update_groups() @@ -2171,10 +2358,9 @@ class Auth(object): else: # user not in database try other login methods for login_method in self.settings.login_methods: - if login_method != self and \ - login_method(username, password): - self.user = username - return username + if login_method != self and login_method(username, password): + self.user = user + return user return False def register_bare(self, **fields): @@ -2183,32 +2369,28 @@ class Auth(object): and a raw password. """ settings = self._get_login_settings() - if not fields.get(settings.passfield): - raise ValueError("register_bare: " + - "password not provided or invalid") - elif not fields.get(settings.userfield): + # users can register_bare even if no password is provided, + # in this case they will have to reset their password to login + if fields.get(settings.passfield): + fields[settings.passfield] = \ + settings.table_user[settings.passfield].validate(fields[settings.passfield])[0] + if not fields.get(settings.userfield): raise ValueError("register_bare: " + "userfield not provided or invalid") - fields[settings.passfield] = \ - settings.table_user[settings.passfield].validate( - fields[settings.passfield])[0] - user = self.get_or_create_user( - fields, login=False, get=False, - update_fields=self.settings.update_fields) + user = self.get_or_create_user(fields, login=False, get=False, + update_fields=self.settings.update_fields) if not user: # get or create did not create a user (it ignores duplicate records) return False return user - - def cas_login( - self, - next=DEFAULT, - onvalidation=DEFAULT, - onaccept=DEFAULT, - log=DEFAULT, - version=2, - ): + def cas_login(self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + version=2, + ): request = current.request response = current.response session = current.session @@ -2296,23 +2478,22 @@ class Auth(object): raise HTTP(200, message) def _reset_two_factor_auth(self, session): - '''When two-step authentication is enabled, this function is used to + """When two-step authentication is enabled, this function is used to clear the session after successfully completing second challenge or when the maximum number of tries allowed has expired. - ''' + """ session.auth_two_factor_user = None session.auth_two_factor = None session.auth_two_factor_enabled = False # Allow up to 4 attempts (the 1st one plus 3 more) session.auth_two_factor_tries_left = 3 - def login( - self, - next=DEFAULT, - onvalidation=DEFAULT, - onaccept=DEFAULT, - log=DEFAULT, - ): + def login(self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): """ Returns a login form """ @@ -2341,10 +2522,6 @@ class Auth(object): ### use session for federated login snext = self.get_vars_next() - if snext and self.settings.prevent_open_redirect_attacks: - items = snext.split('/') - if '//' in snext and items[2] != request.env.http_host: - snext = None if snext: session._auth_next = snext @@ -2355,6 +2532,8 @@ class Auth(object): if next is DEFAULT: # important for security next = settings.login_next + if callable(next): + next = next() user_next = snext if user_next: external = user_next.split('://') @@ -2415,7 +2594,7 @@ class Auth(object): if settings.remember_me_form: extra_fields = [ - Field('remember_me','boolean',default=False, + Field('remember_me', 'boolean', default=False, label = self.messages.label_remember_me)] else: extra_fields = [] @@ -2540,7 +2719,7 @@ class Auth(object): # auth.settings.two_factor_authentication_group if user and self.settings.two_factor_authentication_group: role = self.settings.two_factor_authentication_group - session.auth_two_factor_enabled = self.has_membership(user_id=user.id,role=role) + session.auth_two_factor_enabled = self.has_membership(user_id=user.id, role=role) # challenge if session.auth_two_factor_enabled: form = SQLFORM.factory( @@ -2664,13 +2843,12 @@ class Auth(object): if not next is None: redirect(next) - def register( - self, - next=DEFAULT, - onvalidation=DEFAULT, - onaccept=DEFAULT, - log=DEFAULT, - ): + def register(self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): """ Returns a registration form """ @@ -2719,7 +2897,7 @@ class Auth(object): if self.settings.register_verify_password: extra_fields = [ Field("password_two", "password", requires=IS_EQUAL_TO( - request.post_vars.get(passfield,None), + request.post_vars.get(passfield, None), error_message=self.messages.mismatched_password), label=current.T("Confirm Password"))] else: @@ -2763,7 +2941,7 @@ class Auth(object): link = self.url( self.settings.function, args=('verify_email', key), scheme=True) d = dict(form.vars) - d.update(dict(key=key, link=link,username=form.vars[username])) + d.update(dict(key=key, link=link, username=form.vars[username])) if not (self.settings.mailer and self.settings.mailer.send( to=form.vars.email, subject=self.messages.verify_email_subject, @@ -2804,12 +2982,11 @@ class Auth(object): return True return False - def verify_email( - self, - next=DEFAULT, - onaccept=DEFAULT, - log=DEFAULT, - ): + def verify_email(self, + next=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): """ Action used to verify the registration email """ @@ -2838,13 +3015,12 @@ class Auth(object): callback(onaccept, user) redirect(next) - def retrieve_username( - self, - next=DEFAULT, - onvalidation=DEFAULT, - onaccept=DEFAULT, - log=DEFAULT, - ): + def retrieve_username(self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): """ Returns a form to retrieve the user username (only if there is a username field) @@ -2895,9 +3071,8 @@ class Auth(object): redirect(self.url(args=request.args)) username = ', '.join(u.username for u in users) self.settings.mailer.send(to=form.vars.email, - subject=self.messages.retrieve_username_subject, - message=self.messages.retrieve_username - % dict(username=username)) + subject=self.messages.retrieve_username_subject, + message=self.messages.retrieve_username % dict(username=username)) session.flash = self.messages.email_sent for user in users: self.log_event(log, user) @@ -2922,13 +3097,12 @@ class Auth(object): password += random.choice(specials) return ''.join(random.sample(password, len(password))) - def reset_password_deprecated( - self, - next=DEFAULT, - onvalidation=DEFAULT, - onaccept=DEFAULT, - log=DEFAULT, - ): + def reset_password_deprecated(self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): """ Returns a form to reset the user password (deprecated) """ @@ -2981,9 +3155,8 @@ class Auth(object): user.update_record(**d) if self.settings.mailer and \ self.settings.mailer.send(to=form.vars.email, - subject=self.messages.retrieve_password_subject, - message=self.messages.retrieve_password - % dict(password=password)): + subject=self.messages.retrieve_password_subject, + message=self.messages.retrieve_password % dict(password=password)): session.flash = self.messages.email_sent else: session.flash = self.messages.unable_to_send_email @@ -2997,7 +3170,7 @@ class Auth(object): table_user.email.requires = old_requires return form - def reset_password( + def confirm_registration( self, next=DEFAULT, onvalidation=DEFAULT, @@ -3005,6 +3178,146 @@ class Auth(object): log=DEFAULT, ): """ + Returns a form to confirm user registration + """ + + table_user = self.table_user() + request = current.request + # response = current.response + session = current.session + + if next is DEFAULT: + next = self.get_vars_next() or self.settings.reset_password_next + + if self.settings.prevent_password_reset_attacks: + key = request.vars.key + if not key and len(request.args)>1: + key = request.args[-1] + if key: + session._reset_password_key = key + redirect(self.url(args='confirm_registration')) + else: + key = session._reset_password_key + else: + key = request.vars.key or getarg(-1) + try: + t0 = int(key.split('-')[0]) + if time.time() - t0 > 60 * 60 * 24: + raise Exception + user = table_user(reset_password_key=key) + if not user: + raise Exception + except Exception as e: + session.flash = self.messages.invalid_reset_password + redirect(self.url('login', vars=dict(test=e))) + redirect(next, client_side=self.settings.client_side) + passfield = self.settings.password_field + form = SQLFORM.factory( + Field('first_name', + label='First Name', + required=True), + Field('last_name', + label='Last Name', + required=True), + Field('new_password', 'password', + label=self.messages.new_password, + requires=self.table_user()[passfield].requires), + Field('new_password2', 'password', + label=self.messages.verify_password, + requires=[IS_EXPR( + 'value==%s' % repr(request.vars.new_password), + self.messages.mismatched_password)]), + submit_button='Confirm Registration', + hidden=dict(_next=next), + formstyle=self.settings.formstyle, + separator=self.settings.label_separator + ) + if form.process().accepted: + user.update_record( + **{passfield: str(form.vars.new_password), + 'first_name': str(form.vars.first_name), + 'last_name': str(form.vars.last_name), + 'registration_key': '', + 'reset_password_key': ''}) + session.flash = self.messages.password_changed + if self.settings.login_after_password_change: + self.login_user(user) + redirect(next, client_side=self.settings.client_side) + return form + + def email_registration(self, subject, body, user): + """ + Sends and email invitation to a user informing they have been registered with the application + """ + reset_password_key = str(int(time.time())) + '-' + web2py_uuid() + link = self.url(self.settings.function, + args=('confirm_registration',), vars={'key': reset_password_key}, + scheme=True) + d = dict(user) + d.update(dict(key=reset_password_key, link=link, site=current.request.env.http_host)) + if self.settings.mailer and self.settings.mailer.send( + to=user.email, + subject=subject % d, + message=body % d): + user.update_record(reset_password_key=reset_password_key) + return True + return False + + def bulk_register(self, max_emails=100): + """ + Creates a form for ther user to send invites to other users to join + """ + if not self.user: + redirect(self.settings.login_url) + if not self.setting.bulk_register_enabled: + return HTTP(404) + + form = SQLFORM.factory( + Field('subject','string',default=self.messages.bulk_invite_subject,requires=IS_NOT_EMPTY()), + Field('emails','text',requires=IS_NOT_EMPTY()), + Field('message','text',default=self.messages.bulk_invite_body,requires=IS_NOT_EMPTY()), + formstyle=self.settings.formstyle) + + if form.process().accepted: + emails = re.compile('[^\s\'"@<>,;:]+\@[^\s\'"@<>,;:]+').findall(form.vars.emails) + # send the invitations + emails_sent = [] + emails_fail = [] + emails_exist = [] + for email in emails[:max_emails]: + if self.table_user()(email=email): + emails_exist.append(email) + else: + user = self.register_bare(email=email) + if self.email_registration(form.vars.subject, form.vars.message, user): + emails_sent.append(email) + else: + emails_fail.append(email) + emails_fail += emails[max_emails:] + form = DIV(H4('Emails sent'),UL(*[A(x,_href='mailto:'+x) for x in emails_sent]), + H4('Emails failed'),UL(*[A(x,_href='mailto:'+x) for x in emails_fail]), + H4('Emails existing'),UL(*[A(x,_href='mailto:'+x) for x in emails_exist])) + return form + + def manage_tokens(self): + if not self.user: + redirect(self.settings.login_url) + table_token =self.table_token() + table_token.user_id.writable = False + table_token.user_id.default = self.user.id + table_token.token.writable = False + if current.request.args(1) == 'new': + table_token.token.readable = False + form = SQLFORM.grid(table_token, args=['manage_tokens']) + return form + + def reset_password(self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): + """ Returns a form to reset the user password """ @@ -3035,6 +3348,12 @@ class Auth(object): except Exception: session.flash = self.messages.invalid_reset_password redirect(next, client_side=self.settings.client_side) + + if onvalidation is DEFAULT: + onvalidation = self.settings.reset_password_onvalidation + if onaccept is DEFAULT: + onaccept = self.settings.reset_password_onaccept + passfield = self.settings.password_field form = SQLFORM.factory( Field('new_password', 'password', @@ -3050,7 +3369,7 @@ class Auth(object): formstyle=self.settings.formstyle, separator=self.settings.label_separator ) - if form.accepts(request, session, + if form.accepts(request, session, onvalidation=onvalidation, hideerror=self.settings.hideerror): user.update_record( **{passfield: str(form.vars.new_password), @@ -3059,16 +3378,16 @@ class Auth(object): session.flash = self.messages.password_changed if self.settings.login_after_password_change: self.login_user(user) + callback(onaccept, form) redirect(next, client_side=self.settings.client_side) return form - def request_reset_password( - self, - next=DEFAULT, - onvalidation=DEFAULT, - onaccept=DEFAULT, - log=DEFAULT, - ): + def request_reset_password(self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): """ Returns a form to reset the user password """ @@ -3085,14 +3404,14 @@ class Auth(object): response.flash = self.messages.function_disabled return '' if onvalidation is DEFAULT: - onvalidation = self.settings.reset_password_onvalidation + onvalidation = self.settings.request_reset_password_onvalidation if onaccept is DEFAULT: - onaccept = self.settings.reset_password_onaccept + onaccept = self.settings.request_reset_password_onaccept if log is DEFAULT: log = self.messages['reset_password_log'] userfield = self.settings.login_userfield or 'username' \ if 'username' in table_user.fields else 'email' - if userfield=='email': + if userfield == 'email': table_user.email.requires = [ IS_EMAIL(error_message=self.messages.invalid_email), IS_IN_DB(self.db, table_user.email, @@ -3160,25 +3479,23 @@ class Auth(object): return True return False - def retrieve_password( - self, - next=DEFAULT, - onvalidation=DEFAULT, - onaccept=DEFAULT, - log=DEFAULT, - ): + def retrieve_password(self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): if self.settings.reset_password_requires_verification: return self.request_reset_password(next, onvalidation, onaccept, log) else: return self.reset_password_deprecated(next, onvalidation, onaccept, log) - def change_password( - self, - next=DEFAULT, - onvalidation=DEFAULT, - onaccept=DEFAULT, - log=DEFAULT, - ): + def change_password(self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): """ Returns a form that lets the user change password """ @@ -3202,9 +3519,9 @@ class Auth(object): log = self.messages['change_password_log'] passfield = self.settings.password_field requires = table_user[passfield].requires - if not isinstance(requires,(list, tuple)): + if not isinstance(requires, (list, tuple)): requires = [requires] - requires = filter(lambda t:isinstance(t,CRYPT), requires) + requires = filter(lambda t: isinstance(t, CRYPT), requires) if requires: requires[0].min_length = 0 form = SQLFORM.factory( @@ -3228,7 +3545,7 @@ class Auth(object): onvalidation=onvalidation, hideerror=self.settings.hideerror): - current_user = s.select(limitby=(0,1), orderby_on_limitby=False).first() + current_user = s.select(limitby=(0, 1), orderby_on_limitby=False).first() if not form.vars['old_password'] == current_user[passfield]: form.errors['old_password'] = self.messages.invalid_password else: @@ -3244,13 +3561,12 @@ class Auth(object): redirect(next, client_side=self.settings.client_side) return form - def profile( - self, - next=DEFAULT, - onvalidation=DEFAULT, - onaccept=DEFAULT, - log=DEFAULT, - ): + def profile(self, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + ): """ Returns a form that lets the user change his/her profile """ @@ -3302,13 +3618,13 @@ class Auth(object): return form def run_login_onaccept(self): - onaccept = self.settings.login_onaccept - if onaccept: - form = Storage(dict(vars=self.user)) - if not isinstance(onaccept,(list, tuple)): - onaccept = [onaccept] - for callback in onaccept: - callback(form) + onaccept = self.settings.login_onaccept + if onaccept: + form = Storage(dict(vars=self.user)) + if not isinstance(onaccept, (list, tuple)): + onaccept = [onaccept] + for callback in onaccept: + callback(form) def is_impersonating(self): return self.is_logged_in() and 'impersonator' in current.session.auth @@ -3460,6 +3776,26 @@ class Auth(object): """ return self.requires(True, otherwise=otherwise) + def requires_login_or_token(self, otherwise=None): + if self.settings.enable_tokens == True: + user = None + request = current.request + token = request.env.http_web2py_user_token or request.vars._token + table_token = self.table_token() + table_user = self.table_user() + from gluon.settings import global_settings + if global_settings.web2py_runtime_gae: + row = table_token(token=token) + if row: + user = table_user(row.user_id) + else: + row = self.db(table_token.token==token)(table_user.id==table_token.user_id).select().first() + if row: + user = row[table_user._tablename] + if user: + self.login_user(user) + return self.requires(True, otherwise=otherwise) + def requires_membership(self, role=None, group_id=None, otherwise=None): """ Decorator that prevents access to action if not logged in or @@ -3511,7 +3847,7 @@ class Auth(object): self.db(self.table_group().id == group_id).delete() self.db(self.table_membership().group_id == group_id).delete() self.db(self.table_permission().group_id == group_id).delete() - self.update_groups() + if group_id in self.user_groups: del self.user_groups[group_id] self.log_event(self.messages.del_group_log, dict(group_id=group_id)) def id_group(self, role): @@ -3580,7 +3916,10 @@ class Auth(object): return record.id else: id = membership.insert(group_id=group_id, user_id=user_id) - self.update_groups() + if role: + self.user_groups[group_id] = role + else: + self.update_groups() self.log_event(self.messages['add_membership_log'], dict(user_id=user_id, group_id=group_id)) return id @@ -3600,17 +3939,16 @@ class Auth(object): ret = self.db(membership.user_id == user_id)(membership.group_id == group_id).delete() - self.update_groups() + if group_id in self.user_groups: del self.user_groups[group_id] return ret - def has_permission( - self, - name='any', - table_name='', - record_id=0, - user_id=None, - group_id=None, - ): + def has_permission(self, + name='any', + table_name='', + record_id=0, + user_id=None, + group_id=None, + ): """ Checks if user_id or current logged in user is member of a group that has 'name' permission on 'table_name' and 'record_id' @@ -3656,13 +3994,12 @@ class Auth(object): table_name=table_name, record_id=record_id)) return r - def add_permission( - self, - group_id, - name='any', - table_name='', - record_id=0, - ): + def add_permission(self, + group_id, + name='any', + table_name='', + record_id=0, + ): """ Gives group_id 'name' access to 'table_name' and 'record_id' """ @@ -3671,7 +4008,7 @@ class Auth(object): if group_id == 0: group_id = self.user_group() record = self.db(permission.group_id == group_id)(permission.name == name)(permission.table_name == str(table_name))( - permission.record_id == long(record_id)).select(limitby=(0,1), orderby_on_limitby=False).first() + permission.record_id == long(record_id)).select(limitby=(0, 1), orderby_on_limitby=False).first() if record: id = record.id else: @@ -3684,13 +4021,12 @@ class Auth(object): record_id=record_id)) return id - def del_permission( - self, - group_id, - name='any', - table_name='', - record_id=0, - ): + def del_permission(self, + group_id, + name='any', + table_name='', + record_id=0, + ): """ Revokes group_id 'name' access to 'table_name' and 'record_id' """ @@ -3850,7 +4186,8 @@ class Auth(object): force_render=False, groups=None): - if controller and function: resolve = False + if controller and function: + resolve = False if not hasattr(self, '_wiki'): self._wiki = Wiki(self, render=render, @@ -3873,7 +4210,7 @@ class Auth(object): if resolve: if slug: wiki = self._wiki.read(slug, force_render) - if isinstance(wiki, dict) and wiki.has_key('content'): + if isinstance(wiki, dict) and wiki.has_key('content'): # FIXME: .has_key() is deprecated # We don't want to return a dict object, just the wiki wiki = wiki['content'] else: @@ -3994,27 +4331,26 @@ class Crud(object): def tables(self): return TABLE(*[TR(A(name, _href=self.url(args=('select', name)))) - for name in self.db.tables]) + for name in self.db.tables]) @staticmethod def archive(form, archive_table=None, current_record='current_record'): return Auth.archive(form, archive_table=archive_table, current_record=current_record) - def update( - self, - table, - record, - next=DEFAULT, - onvalidation=DEFAULT, - onaccept=DEFAULT, - ondelete=DEFAULT, - log=DEFAULT, - message=DEFAULT, - deletable=DEFAULT, - formname=DEFAULT, - **attributes - ): + def update(self, + table, + record, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + ondelete=DEFAULT, + log=DEFAULT, + message=DEFAULT, + deletable=DEFAULT, + formname=DEFAULT, + **attributes + ): if not (isinstance(table, Table) or table in self.db.tables) \ or (isinstance(record, str) and not str(record).isdigit()): raise HTTP(404) @@ -4063,7 +4399,7 @@ class Crud(object): upload=self.settings.download_url, formstyle=self.settings.formstyle, separator=self.settings.label_separator, - **attributes # contains hidden + **attributes # contains hidden ) self.accepted = False self.deleted = False @@ -4113,17 +4449,16 @@ class Crud(object): raise HTTP(401, serializers.json(dict(errors=form.errors))) return form - def create( - self, - table, - next=DEFAULT, - onvalidation=DEFAULT, - onaccept=DEFAULT, - log=DEFAULT, - message=DEFAULT, - formname=DEFAULT, - **attributes - ): + def create(self, + table, + next=DEFAULT, + onvalidation=DEFAULT, + onaccept=DEFAULT, + log=DEFAULT, + message=DEFAULT, + formname=DEFAULT, + **attributes + ): if next is DEFAULT: next = self.settings.create_next @@ -4170,13 +4505,12 @@ class Crud(object): return table._filter_fields(form.record, id=True) return form - def delete( - self, - table, - record_id, - next=DEFAULT, - message=DEFAULT, - ): + def delete(self, + table, + record_id, + next=DEFAULT, + message=DEFAULT, + ): if not (isinstance(table, Table) or table in self.db.tables): raise HTTP(404) if not isinstance(table, Table): @@ -4225,16 +4559,15 @@ class Crud(object): limitby=limitby)) return rows - def select( - self, - table, - query=None, - fields=None, - orderby=None, - limitby=None, - headers=None, - **attr - ): + def select(self, + table, + query=None, + fields=None, + orderby=None, + limitby=None, + headers=None, + **attr + ): headers = headers or {} rows = self.rows(table, query, fields, orderby, limitby) if not rows: @@ -4317,7 +4650,7 @@ class Crud(object): """ table = tables[0] fields = args.get('fields', table.fields) - validate = args.get('validate',True) + validate = args.get('validate', True) request = current.request db = self.db if not (isinstance(table, Table) or table in db.tables): @@ -4370,8 +4703,7 @@ class Crud(object): if request.post_vars and (chkval or field.type == 'id'): if txtval and opval != '': if field.type[0:10] == 'reference ': - refsearch.append(self.get_query(field, - opval, txtval, refsearch=True)) + refsearch.append(self.get_query(field, opval, txtval, refsearch=True)) elif validate: value, error = field.validate(txtval) if not error: @@ -4465,8 +4797,7 @@ def universal_caller(f, *a, **b): # Fill the arg_dict with name and value for the submitted, positional values for pos_index, pos_val in enumerate(a[:c]): - arg_dict[n[pos_index] - ] = pos_val # n[pos_index] is the name of the argument + arg_dict[n[pos_index]] = pos_val # n[pos_index] is the name of the argument # There might be pos_args left, that are sent as named_values. Gather them as well. # If a argument already is populated with values we simply replaces them. @@ -4844,7 +5175,6 @@ class Service(object): -32603: ("Internal error", "Internal JSON-RPC error."), -32099: ("Server error", "Reserved for implementation-defined server-errors.")} - def serve_jsonrpc(self): def return_response(id, result): return serializers.json({'version': '1.1', @@ -4874,7 +5204,7 @@ class Service(object): if not method in methods: return return_error(id, 100, 'method "%s" does not exist' % method) try: - if isinstance(params,dict): + if isinstance(params, dict): s = methods[method](**params) else: s = methods[method](*params) @@ -4940,15 +5270,13 @@ class Service(object): return True - - request = current.request response = current.response if not data: response.headers['Content-Type'] = 'application/json; charset=utf-8' try: data = json_parser.loads(request.body.read()) - except ValueError: # decoding error in json lib + except ValueError: # decoding error in json lib return return_error(None, -32700) # Batch handling @@ -4956,9 +5284,9 @@ class Service(object): retlist = [] for c in data: retstr = self.serve_jsonrpc2(c, batch_element=True) - if retstr: # do not add empty responses + if retstr: # do not add empty responses retlist.append(retstr) - if len(retlist) == 0: # return nothing + if len(retlist) == 0: # return nothing return '' else: return "[" + ','.join(retlist) + "]" @@ -4974,7 +5302,7 @@ class Service(object): if not method in methods: return return_error(id, -32601, data='Method "%s" does not exist' % method) try: - if isinstance(params,dict): + if isinstance(params, dict): s = methods[method](**params) else: s = methods[method](*params) @@ -4994,7 +5322,6 @@ class Service(object): logger.warning('%s: %s\n%s' % (etype.__name__, eval, traceback.format_tb(etb))) return return_error(id, -32099, data=data) - def serve_xmlrpc(self): request = current.request response = current.response @@ -5380,7 +5707,7 @@ class Expose(object): if current.request.raw_args: self.args = [arg for arg in current.request.raw_args.split('/') if arg] else: - self.args = [arg for arg in current.request.args if args] + self.args = [arg for arg in current.request.args if arg] filename = os.path.join(base, *self.args) if not os.path.exists(filename): raise HTTP(404, "FILE NOT FOUND") @@ -5395,7 +5722,7 @@ class Expose(object): self.filenames = [f[len(path) - 1:] for f in sorted(glob.glob(path)) if not os.path.isdir(f) and not self.isprivate(f)] if 'README' in self.filenames: - readme = open(os.path.join(filename,'README')).read() + readme = open(os.path.join(filename, 'README')).read() self.paragraph = MARKMIN(readme) else: self.paragraph = None @@ -5452,7 +5779,8 @@ class Expose(object): class Wiki(object): everybody = 'everybody' rows_page = 25 - def markmin_base(self,body): + + def markmin_base(self, body): return MARKMIN(body, extra=self.settings.extra, url=True, environment=self.env, autolinks=lambda link: expand_one(link, {})).xml() @@ -5564,7 +5892,7 @@ class Wiki(object): table_definitions = [ ('wiki_page', { - 'args':[ + 'args': [ Field('slug', requires=[IS_SLUG(), IS_NOT_IN_DB(db, 'wiki_page.slug')], @@ -5589,20 +5917,20 @@ class Wiki(object): requires=IS_EMPTY_OR( IS_IN_SET(engines))), auth.signature], - 'vars':{'format':'%(title)s', 'migrate':migrate}}), + 'vars': {'format': '%(title)s', 'migrate': migrate}}), ('wiki_tag', { - 'args':[ + 'args': [ Field('name'), Field('wiki_page', 'reference wiki_page'), auth.signature], - 'vars':{'format':'%(title)s', 'migrate':migrate}}), + 'vars':{'format': '%(title)s', 'migrate': migrate}}), ('wiki_media', { - 'args':[ + 'args': [ Field('wiki_page', 'reference wiki_page'), Field('title', required=True), Field('filename', 'upload', required=True), auth.signature], - 'vars':{'format':'%(title)s', 'migrate':migrate}}), + 'vars': {'format': '%(title)s', 'migrate': migrate}}), ] # define only non-existent tables @@ -5621,7 +5949,7 @@ class Wiki(object): if self.settings.templates is None and not \ self.settings.manage_permissions: - self.settings.templates = db.wiki_page.tags.contains('template')&\ + self.settings.templates = db.wiki_page.tags.contains('template') & \ db.wiki_page.can_read.contains('everybody') def update_tags_insert(page, id, db=db): @@ -5631,7 +5959,7 @@ class Wiki(object): db.wiki_tag.insert(name=tag, wiki_page=id) def update_tags_update(dbset, page, db=db): - page = dbset.select(limitby=(0,1)).first() + page = dbset.select(limitby=(0, 1)).first() db(db.wiki_tag.wiki_page == page.id).delete() for tag in page.tags or []: tag = tag.strip().lower() @@ -5721,7 +6049,7 @@ class Wiki(object): elif not zero or not zero.startswith('_'): return self.read(zero) elif zero == '_edit': - return self.edit(request.args(1) or 'index',request.args(2) or 0) + return self.edit(request.args(1) or 'index', request.args(2) or 0) elif zero == '_editmedia': return self.editmedia(request.args(1) or 'index') elif zero == '_create': @@ -5792,7 +6120,7 @@ class Wiki(object): created_on=page.created_on, modified_on=page.modified_on) - def edit(self,slug,from_template=0): + def edit(self, slug, from_template=0): auth = self.auth db = auth.db page = db.wiki_page(slug=slug) @@ -5813,7 +6141,8 @@ class Wiki(object): db.wiki_page.body.default = \ '- Menu Item > @////index\n- - Submenu > http://web2py.com' else: - db.wiki_page.body.default = db(db.wiki_page.id==from_template).select(db.wiki_page.body)[0].body if int(from_template) > 0 else '## %s\n\npage content' % title_guess + db.wiki_page.body.default = db(db.wiki_page.id == from_template).select(db.wiki_page.body)[0].body \ + if int(from_template) > 0 else '## %s\n\npage content' % title_guess vars = current.request.post_vars if vars.body: vars.body = vars.body.replace('://%s' % self.host, '://HOSTNAME') @@ -5868,9 +6197,9 @@ class Wiki(object): } }) }) - """ % dict(url=URL(args=('_preview', slug)),link_media=('true' if page else 'false'), + """ % dict(url=URL(args=('_preview', slug)), link_media=('true' if page else 'false'), urlmedia=URL(extension='load', - args=('_editmedia',slug), + args=('_editmedia', slug), vars=dict(embedded=1))) return dict(content=TAG[''](form, SCRIPT(script))) @@ -5882,9 +6211,7 @@ class Wiki(object): return self.not_authorized(page) self.auth.db.wiki_media.id.represent = lambda id, row: \ id if not row.filename else \ - SPAN('@////%i/%s.%s' % - (id, IS_SLUG.urlify(row.title.split('.')[0]), - row.filename.split('.')[-1])) + SPAN('@////%i/%s.%s' % (id, IS_SLUG.urlify(row.title.split('.')[0]), row.filename.split('.')[-1])) self.auth.db.wiki_media.wiki_page.default = page.id self.auth.db.wiki_media.wiki_page.writable = False links = [] @@ -5895,16 +6222,16 @@ class Wiki(object): fragment = self.auth.db.wiki_media.id.represent csv = False create = False - links=[ + links= [ lambda row: A('copy into source', _href='#', _onclick=script % (fragment(row.id, row))) ] content = SQLFORM.grid( self.auth.db.wiki_media.wiki_page == page.id, orderby=self.auth.db.wiki_media.title, - links = links, - csv = csv, - create = create, + links=links, + csv=csv, + create=create, args=['_editmedia', slug], user_signature=False) return dict(content=content) @@ -5913,12 +6240,12 @@ class Wiki(object): if not self.can_edit(): return self.not_authorized() db = self.auth.db - slugs=db(db.wiki_page.id>0).select(db.wiki_page.id,db.wiki_page.slug) - options=[OPTION(row.slug,_value=row.id) for row in slugs] - options.insert(0, OPTION('',_value='')) + slugs = db(db.wiki_page.id > 0).select(db.wiki_page.id, db.wiki_page.slug) + options = [OPTION(row.slug, _value=row.id) for row in slugs] + options.insert(0, OPTION('', _value='')) fields = [Field("slug", default=current.request.args(1) or self.settings.force_prefix, - requires=(IS_SLUG(), IS_NOT_IN_DB(db,db.wiki_page.slug))),] + requires=(IS_SLUG(), IS_NOT_IN_DB(db, db.wiki_page.slug))),] if self.settings.templates: fields.append( Field("from_template", "reference wiki_page", @@ -5935,7 +6262,7 @@ class Wiki(object): if form.process().accepted: form.vars.from_template = 0 if not form.vars.from_template \ else form.vars.from_template - redirect(URL(args=('_edit', form.vars.slug,form.vars.from_template or 0))) # added param + redirect(URL(args=('_edit', form.vars.slug, form.vars.from_template or 0))) # added param return dict(content=form) def pages(self): @@ -5948,14 +6275,14 @@ class Wiki(object): wiki_table = self.auth.db.wiki_page content = SQLFORM.grid( wiki_table, - fields = [wiki_table.slug, - wiki_table.title, wiki_table.tags, - wiki_table.can_read, wiki_table.can_edit], + fields=[wiki_table.slug, + wiki_table.title, wiki_table.tags, + wiki_table.can_read, wiki_table.can_edit], links=[ lambda row: - A('edit', _href=URL(args=('_edit', row.slug)),_class='btn'), + A('edit', _href=URL(args=('_edit', row.slug)), _class='btn'), lambda row: - A('media', _href=URL(args=('_editmedia', row.slug)),_class='btn')], + A('media', _href=URL(args=('_editmedia', row.slug)), _class='btn')], details=False, editable=False, deletable=False, create=False, orderby=self.auth.db.wiki_page.title, args=['_pages'], @@ -5973,7 +6300,7 @@ class Wiki(object): return self.not_authorized(page) request.args = [media.filename] m = response.download(request, db) - current.session.forget() # get rid of the cookie + current.session.forget() # get rid of the cookie response.headers['Last-Modified'] = \ request.utcnow.strftime("%a, %d %b %Y %H:%M:%S GMT") if 'Content-Disposition' in response.headers: @@ -6077,10 +6404,10 @@ class Wiki(object): if self.settings.restrict_search and not self.manage(): query = query & (db.wiki_page.created_by == self.auth.user_id) pages = db(query).select(count, - *fields, **dict(orderby=orderby or ~count, - groupby=reduce(lambda a, b: a | b, fields), - distinct=True, - limitby=limitby)) + *fields, **dict(orderby=orderby or ~count, + groupby=reduce(lambda a, b: a | b, fields), + distinct=True, + limitby=limitby)) if request.extension in ('html', 'load'): if not pages: content.append(DIV(current.T("No results"), @@ -6139,6 +6466,7 @@ class Wiki(object): request.post_vars.render = None return render(request.post_vars) + class Config(object): def __init__( self, @@ -6154,7 +6482,7 @@ class Config(object): self.filename = filename def read(self): - if not( isinstance(current.session['settings_%s' % self.section], dict) ): + if not(isinstance(current.session['settings_%s' % self.section], dict)): settings = dict(self.config.items(self.section)) else: settings = current.session['settings_%s' % self.section] diff --git a/gluon/utils.py b/gluon/utils.py index e18edb8d..4df0cbc3 100644 --- a/gluon/utils.py +++ b/gluon/utils.py @@ -66,6 +66,7 @@ else: logger = logging.getLogger("web2py") + def AES_new(key, IV=None): """ Returns an AES cipher object and random IV if None specified """ if IV is None: @@ -88,6 +89,7 @@ def md5_hash(text): """ Generates a md5 hash with the given text """ return md5(text).hexdigest() + def simple_hash(text, key='', salt='', digest_alg='md5'): """ Generates hash with the given text using the specified diff --git a/gluon/validators.py b/gluon/validators.py index f98c6c0c..2afeedc4 100644 --- a/gluon/validators.py +++ b/gluon/validators.py @@ -22,7 +22,7 @@ import decimal import unicodedata from cStringIO import StringIO from gluon.utils import simple_hash, web2py_uuid, DIGEST_ALG_BY_SIZE -from gluon.dal.objects import FieldVirtual, FieldMethod +from pydal.objects import FieldVirtual, FieldMethod regex_isint = re.compile('^[+-]?\d+$') @@ -190,9 +190,9 @@ class IS_MATCH(Validator): if not expression.endswith('$'): expression = '(%s)$' % expression if is_unicode: - if not isinstance(expression,unicode): + if not isinstance(expression, unicode): expression = expression.decode('utf8') - self.regex = re.compile(expression,re.UNICODE) + self.regex = re.compile(expression, re.UNICODE) else: self.regex = re.compile(expression) self.error_message = error_message @@ -200,8 +200,11 @@ class IS_MATCH(Validator): self.is_unicode = is_unicode def __call__(self, value): - if self.is_unicode and not isinstance(value,unicode): - match = self.regex.search(str(value).decode('utf8')) + if self.is_unicode: + if isinstance(value,unicode): + match = self.regex.search(value) + else: + match = self.regex.search(str(value).decode('utf8')) else: match = self.regex.search(str(value)) if match is not None: @@ -348,6 +351,7 @@ class IS_LENGTH(Validator): return (value, translate(self.error_message) % dict(min=self.minsize, max=self.maxsize)) + class IS_JSON(Validator): """ Example: @@ -377,7 +381,7 @@ class IS_JSON(Validator): except JSONErrors: return (value, translate(self.error_message)) - def formatter(self,value): + def formatter(self, value): if value is None: return None if self.native_json: @@ -505,8 +509,9 @@ class IS_IN_DB(Validator): zero='', sort=False, _and=None, + left=None ): - from dal.objects import Table + from pydal.objects import Table if isinstance(field, Table): field = field._id @@ -521,7 +526,7 @@ class IS_IN_DB(Validator): if regex1.match(str(label)): label = '%%(%s)s' % str(label).split('.')[-1] ks = regex2.findall(label) - if not kfield in ks: + if kfield not in ks: ks += [kfield] fields = ks else: @@ -542,6 +547,7 @@ class IS_IN_DB(Validator): self.zero = zero self.sort = sort self._and = _and + self.left = left def set_self_id(self, id): if self._and: @@ -553,15 +559,16 @@ class IS_IN_DB(Validator): fields = [f for f in table] else: fields = [table[k] for k in self.fields] - ignore = (FieldVirtual,FieldMethod) - fields = filter(lambda f:not isinstance(f,ignore), fields) + ignore = (FieldVirtual, FieldMethod) + fields = filter(lambda f: not isinstance(f, ignore), fields) if self.dbset.db._dbname != 'gae': orderby = self.orderby or reduce(lambda a, b: a | b, fields) groupby = self.groupby distinct = self.distinct + left = self.left dd = dict(orderby=orderby, groupby=groupby, distinct=distinct, cache=self.cache, - cacheable=True) + cacheable=True, left=left) records = self.dbset(table).select(*fields, **dd) else: orderby = self.orderby or \ @@ -580,7 +587,7 @@ class IS_IN_DB(Validator): items = [(k, self.labels[i]) for (i, k) in enumerate(self.theset)] if self.sort: items.sort(options_sorter) - if zero and not self.zero is None and not self.multiple: + if zero and self.zero is not None and not self.multiple: items.insert(0, ('', self.zero)) return items @@ -600,14 +607,14 @@ class IS_IN_DB(Validator): not self.multiple[0] <= len(values) < self.multiple[1]: return (values, translate(self.error_message)) if self.theset: - if not [v for v in values if not v in self.theset]: + if not [v for v in values if v not in self.theset]: return (values, None) else: - from dal.adapters import GoogleDatastoreAdapter + from pydal.adapters import GoogleDatastoreAdapter def count(values, s=self.dbset, f=field): return s(f.belongs(map(int, values))).count() - if isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter): + if GoogleDatastoreAdapter is not None and isinstance(self.dbset.db._adapter, GoogleDatastoreAdapter): range_ids = range(0, len(values), 30) total = sum(count(values[i:i + 30]) for i in range_ids) if total == len(values): @@ -648,7 +655,7 @@ class IS_NOT_IN_DB(Validator): ignore_common_filters=False, ): - from dal.objects import Table + from pydal.objects import Table if isinstance(field, Table): field = field._id @@ -666,7 +673,7 @@ class IS_NOT_IN_DB(Validator): self.record_id = id def __call__(self, value): - if isinstance(value,unicode): + if isinstance(value, unicode): value = value.encode('utf8') else: value = str(value) @@ -693,7 +700,7 @@ class IS_NOT_IN_DB(Validator): def range_error_message(error_message, what_to_enter, minimum, maximum): - "build the error message for the number range validators" + """build the error message for the number range validators""" if error_message is None: error_message = 'Enter ' + what_to_enter if minimum is not None and maximum is not None: @@ -935,7 +942,7 @@ class IS_DECIMAL_IN_RANGE(Validator): def is_empty(value, empty_regex=None): - "test empty field" + """test empty field""" if isinstance(value, (str, unicode)): value = value.strip() if empty_regex is not None and empty_regex.match(value): @@ -1150,6 +1157,7 @@ class IS_EMAIL(Validator): return (value, None) return (value, translate(self.error_message)) + class IS_LIST_OF_EMAILS(object): """ Example: @@ -1163,7 +1171,8 @@ class IS_LIST_OF_EMAILS(object): ) """ split_emails = re.compile('[^,;\s]+') - def __init__(self, error_message = 'Invalid emails: %s'): + + def __init__(self, error_message='Invalid emails: %s'): self.error_message = error_message def __call__(self, value): @@ -1179,7 +1188,7 @@ class IS_LIST_OF_EMAILS(object): return (value, translate(self.error_message) % ', '.join(bad_emails)) - def formatter(self,value,row=None): + def formatter(self, value, row=None): return ', '.join(value or []) @@ -1331,7 +1340,7 @@ label_split_regex = re.compile(u'[\u002e\u3002\uff0e\uff61]') def escape_unicode(string): - ''' + """ Converts a unicode string into US-ASCII, using a simple conversion scheme. Each unicode character that does not have a US-ASCII equivalent is converted into a URL escaped form based on its hexadecimal value. @@ -1345,7 +1354,7 @@ def escape_unicode(string): string: the US-ASCII escaped form of the inputted string @author: Jonathan Benn - ''' + """ returnValue = StringIO() for character in string: @@ -1360,7 +1369,7 @@ def escape_unicode(string): def unicode_to_ascii_authority(authority): - ''' + """ Follows the steps in RFC 3490, Section 4 to convert a unicode authority string into its ASCII equivalent. For example, u'www.Alliancefran\xe7aise.nu' will be converted into @@ -1379,19 +1388,19 @@ def unicode_to_ascii_authority(authority): authority @author: Jonathan Benn - ''' - #RFC 3490, Section 4, Step 1 - #The encodings.idna Python module assumes that AllowUnassigned == True + """ + # RFC 3490, Section 4, Step 1 + # The encodings.idna Python module assumes that AllowUnassigned == True - #RFC 3490, Section 4, Step 2 + # RFC 3490, Section 4, Step 2 labels = label_split_regex.split(authority) - #RFC 3490, Section 4, Step 3 - #The encodings.idna Python module assumes that UseSTD3ASCIIRules == False + # RFC 3490, Section 4, Step 3 + # The encodings.idna Python module assumes that UseSTD3ASCIIRules == False - #RFC 3490, Section 4, Step 4 - #We use the ToASCII operation because we are about to put the authority - #into an IDN-unaware slot + # RFC 3490, Section 4, Step 4 + # We use the ToASCII operation because we are about to put the authority + # into an IDN-unaware slot asciiLabels = [] try: import encodings.idna @@ -1399,18 +1408,18 @@ def unicode_to_ascii_authority(authority): if label: asciiLabels.append(encodings.idna.ToASCII(label)) else: - #encodings.idna.ToASCII does not accept an empty string, but - #it is necessary for us to allow for empty labels so that we - #don't modify the URL + # encodings.idna.ToASCII does not accept an empty string, but + # it is necessary for us to allow for empty labels so that we + # don't modify the URL asciiLabels.append('') except: asciiLabels = [str(label) for label in labels] - #RFC 3490, Section 4, Step 5 + # RFC 3490, Section 4, Step 5 return str(reduce(lambda x, y: x + unichr(0x002E) + y, asciiLabels)) def unicode_to_ascii_url(url, prepend_scheme): - ''' + """ Converts the inputed unicode url into a US-ASCII equivalent. This function goes a little beyond RFC 3490, which is limited in scope to the domain name (authority) only. Here, the functionality is expanded to what was observed @@ -1440,23 +1449,23 @@ def unicode_to_ascii_url(url, prepend_scheme): string: a US-ASCII equivalent of the inputed url @author: Jonathan Benn - ''' - #convert the authority component of the URL into an ASCII punycode string, - #but encode the rest using the regular URI character encoding + """ + # convert the authority component of the URL into an ASCII punycode string, + # but encode the rest using the regular URI character encoding groups = url_split_regex.match(url).groups() - #If no authority was found + # If no authority was found if not groups[3]: - #Try appending a scheme to see if that fixes the problem + # Try appending a scheme to see if that fixes the problem scheme_to_prepend = prepend_scheme or 'http' groups = url_split_regex.match( unicode(scheme_to_prepend) + u'://' + url).groups() - #if we still can't find the authority + # if we still can't find the authority if not groups[3]: raise Exception('No authority component found, ' + 'could not decode unicode to US-ASCII') - #We're here if we found an authority, let's rebuild the URL + # We're here if we found an authority, let's rebuild the URL scheme = groups[1] authority = groups[3] path = groups[4] or '' @@ -1579,300 +1588,173 @@ class IS_GENERIC_URL(Validator): # else the URL is not valid return (value, translate(self.error_message)) -# Sources (obtained 2008-Nov-11): -# http://en.wikipedia.org/wiki/Top-level_domain -# http://www.iana.org/domains/root/db/ +# Sources (obtained 2015-Feb-24): +# http://data.iana.org/TLD/tlds-alpha-by-domain.txt +# see scripts/parse_top_level_domains.py for an easy update official_top_level_domains = [ - 'ac', - 'ad', - 'ae', - 'aero', - 'af', - 'ag', - 'ai', - 'al', - 'am', - 'an', - 'ao', - 'aq', - 'ar', - 'arpa', - 'as', - 'asia', - 'at', - 'au', - 'aw', - 'ax', - 'az', - 'ba', - 'bb', - 'bd', - 'be', - 'bf', - 'bg', - 'bh', - 'bi', - 'biz', - 'bj', - 'bl', - 'bm', - 'bn', - 'bo', - 'br', - 'bs', - 'bt', - 'bv', - 'bw', - 'by', - 'bz', - 'ca', - 'cat', - 'cc', - 'cd', - 'cf', - 'cg', - 'ch', - 'ci', - 'ck', - 'cl', - 'cm', - 'cn', - 'co', - 'com', - 'coop', - 'cr', - 'cu', - 'cv', - 'cx', - 'cy', + # a + 'abogado', 'ac', 'academy', 'accountants', 'active', 'actor', + 'ad', 'adult', 'ae', 'aero', 'af', 'ag', 'agency', 'ai', + 'airforce', 'al', 'allfinanz', 'alsace', 'am', 'amsterdam', 'an', + 'android', 'ao', 'apartments', 'aq', 'aquarelle', 'ar', 'archi', + 'army', 'arpa', 'as', 'asia', 'associates', 'at', 'attorney', + 'au', 'auction', 'audio', 'autos', 'aw', 'ax', 'axa', 'az', + # b + 'ba', 'band', 'bank', 'bar', 'barclaycard', 'barclays', + 'bargains', 'bayern', 'bb', 'bd', 'be', 'beer', 'berlin', 'best', + 'bf', 'bg', 'bh', 'bi', 'bid', 'bike', 'bingo', 'bio', 'biz', + 'bj', 'black', 'blackfriday', 'bloomberg', 'blue', 'bm', 'bmw', + 'bn', 'bnpparibas', 'bo', 'boo', 'boutique', 'br', 'brussels', + 'bs', 'bt', 'budapest', 'build', 'builders', 'business', 'buzz', + 'bv', 'bw', 'by', 'bz', 'bzh', + # c + 'ca', 'cab', 'cal', 'camera', 'camp', 'cancerresearch', 'canon', + 'capetown', 'capital', 'caravan', 'cards', 'care', 'career', + 'careers', 'cartier', 'casa', 'cash', 'casino', 'cat', + 'catering', 'cbn', 'cc', 'cd', 'center', 'ceo', 'cern', 'cf', + 'cg', 'ch', 'channel', 'chat', 'cheap', 'christmas', 'chrome', + 'church', 'ci', 'citic', 'city', 'ck', 'cl', 'claims', + 'cleaning', 'click', 'clinic', 'clothing', 'club', 'cm', 'cn', + 'co', 'coach', 'codes', 'coffee', 'college', 'cologne', 'com', + 'community', 'company', 'computer', 'condos', 'construction', + 'consulting', 'contractors', 'cooking', 'cool', 'coop', + 'country', 'cr', 'credit', 'creditcard', 'cricket', 'crs', + 'cruises', 'cu', 'cuisinella', 'cv', 'cw', 'cx', 'cy', 'cymru', 'cz', - 'de', - 'dj', - 'dk', - 'dm', - 'do', - 'dz', - 'ec', - 'edu', - 'ee', - 'eg', - 'eh', - 'er', - 'es', - 'et', - 'eu', - 'example', - 'fi', - 'fj', - 'fk', - 'fm', - 'fo', - 'fr', - 'ga', - 'gb', - 'gd', - 'ge', - 'gf', - 'gg', - 'gh', - 'gi', - 'gl', - 'gm', - 'gn', - 'gov', - 'gp', - 'gq', - 'gr', - 'gs', - 'gt', - 'gu', - 'gw', - 'gy', - 'hk', - 'hm', - 'hn', - 'hr', - 'ht', - 'hu', - 'id', - 'ie', - 'il', - 'im', - 'in', - 'info', - 'int', - 'invalid', - 'io', - 'iq', - 'ir', - 'is', - 'it', - 'je', - 'jm', - 'jo', - 'jobs', - 'jp', - 'ke', - 'kg', - 'kh', - 'ki', - 'km', - 'kn', - 'kp', - 'kr', - 'kw', - 'ky', - 'kz', - 'la', - 'lb', - 'lc', - 'li', - 'lk', - 'localhost', - 'lr', - 'ls', - 'lt', - 'lu', - 'lv', - 'ly', - 'ma', - 'mc', - 'md', - 'me', - 'mf', - 'mg', - 'mh', - 'mil', - 'mk', - 'ml', - 'mm', - 'mn', - 'mo', - 'mobi', - 'mp', - 'mq', - 'mr', - 'ms', - 'mt', - 'mu', - 'museum', - 'mv', - 'mw', - 'mx', - 'my', - 'mz', - 'na', - 'name', - 'nc', - 'ne', - 'net', - 'nf', - 'ng', - 'ni', - 'nl', - 'no', - 'np', - 'nr', - 'nu', - 'nz', - 'om', - 'org', - 'pa', - 'pe', - 'pf', - 'pg', - 'ph', - 'pk', - 'pl', - 'pm', - 'pn', - 'pr', - 'pro', - 'ps', - 'pt', - 'pw', - 'py', - 'qa', - 're', - 'ro', - 'rs', - 'ru', - 'rw', - 'sa', - 'sb', - 'sc', - 'sd', - 'se', - 'sg', - 'sh', - 'si', - 'sj', - 'sk', - 'sl', - 'sm', - 'sn', - 'so', - 'sr', - 'st', - 'su', - 'sv', - 'sy', - 'sz', - 'tc', - 'td', - 'tel', - 'test', - 'tf', - 'tg', - 'th', - 'tj', - 'tk', - 'tl', - 'tm', - 'tn', - 'to', - 'tp', - 'tr', - 'travel', - 'tt', - 'tv', - 'tw', + # d + 'dabur', 'dad', 'dance', 'dating', 'day', 'dclk', 'de', 'deals', + 'degree', 'delivery', 'democrat', 'dental', 'dentist', 'desi', + 'design', 'dev', 'diamonds', 'diet', 'digital', 'direct', + 'directory', 'discount', 'dj', 'dk', 'dm', 'dnp', 'do', 'docs', + 'domains', 'doosan', 'durban', 'dvag', 'dz', + # e + 'eat', 'ec', 'edu', 'education', 'ee', 'eg', 'email', 'emerck', + 'energy', 'engineer', 'engineering', 'enterprises', 'equipment', + 'er', 'es', 'esq', 'estate', 'et', 'eu', 'eurovision', 'eus', + 'events', 'everbank', 'exchange', 'expert', 'exposed', + # f + 'fail', 'fans', 'farm', 'fashion', 'feedback', 'fi', 'finance', + 'financial', 'firmdale', 'fish', 'fishing', 'fit', 'fitness', + 'fj', 'fk', 'flights', 'florist', 'flowers', 'flsmidth', 'fly', + 'fm', 'fo', 'foo', 'football', 'forsale', 'foundation', 'fr', + 'frl', 'frogans', 'fund', 'furniture', 'futbol', + # g + 'ga', 'gal', 'gallery', 'garden', 'gb', 'gbiz', 'gd', 'gdn', + 'ge', 'gent', 'gf', 'gg', 'ggee', 'gh', 'gi', 'gift', 'gifts', + 'gives', 'gl', 'glass', 'gle', 'global', 'globo', 'gm', 'gmail', + 'gmo', 'gmx', 'gn', 'goldpoint', 'goog', 'google', 'gop', 'gov', + 'gp', 'gq', 'gr', 'graphics', 'gratis', 'green', 'gripe', 'gs', + 'gt', 'gu', 'guide', 'guitars', 'guru', 'gw', 'gy', + # h + 'hamburg', 'hangout', 'haus', 'healthcare', 'help', 'here', + 'hermes', 'hiphop', 'hiv', 'hk', 'hm', 'hn', 'holdings', + 'holiday', 'homes', 'horse', 'host', 'hosting', 'house', 'how', + 'hr', 'ht', 'hu', + # i + 'ibm', 'id', 'ie', 'ifm', 'il', 'im', 'immo', 'immobilien', 'in', + 'industries', 'info', 'ing', 'ink', 'institute', 'insure', 'int', + 'international', 'investments', 'io', 'iq', 'ir', 'irish', 'is', + 'it', 'iwc', + # j + 'jcb', 'je', 'jetzt', 'jm', 'jo', 'jobs', 'joburg', 'jp', + 'juegos', + # k + 'kaufen', 'kddi', 'ke', 'kg', 'kh', 'ki', 'kim', 'kitchen', + 'kiwi', 'km', 'kn', 'koeln', 'kp', 'kr', 'krd', 'kred', 'kw', + 'ky', 'kyoto', 'kz', + # l + 'la', 'lacaixa', 'land', 'lat', 'latrobe', 'lawyer', 'lb', 'lc', + 'lds', 'lease', 'legal', 'lgbt', 'li', 'lidl', 'life', + 'lighting', 'limited', 'limo', 'link', 'lk', 'loans', + 'localhost', 'london', 'lotte', 'lotto', 'lr', 'ls', 'lt', + 'ltda', 'lu', 'luxe', 'luxury', 'lv', 'ly', + # m + 'ma', 'madrid', 'maison', 'management', 'mango', 'market', + 'marketing', 'marriott', 'mc', 'md', 'me', 'media', 'meet', + 'melbourne', 'meme', 'memorial', 'menu', 'mg', 'mh', 'miami', + 'mil', 'mini', 'mk', 'ml', 'mm', 'mn', 'mo', 'mobi', 'moda', + 'moe', 'monash', 'money', 'mormon', 'mortgage', 'moscow', + 'motorcycles', 'mov', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', + 'museum', 'mv', 'mw', 'mx', 'my', 'mz', + # n + 'na', 'nagoya', 'name', 'navy', 'nc', 'ne', 'net', 'network', + 'neustar', 'new', 'nexus', 'nf', 'ng', 'ngo', 'nhk', 'ni', + 'nico', 'ninja', 'nl', 'no', 'np', 'nr', 'nra', 'nrw', 'ntt', + 'nu', 'nyc', 'nz', + # o + 'okinawa', 'om', 'one', 'ong', 'onl', 'ooo', 'org', 'organic', + 'osaka', 'otsuka', 'ovh', + # p + 'pa', 'paris', 'partners', 'parts', 'party', 'pe', 'pf', 'pg', + 'ph', 'pharmacy', 'photo', 'photography', 'photos', 'physio', + 'pics', 'pictures', 'pink', 'pizza', 'pk', 'pl', 'place', + 'plumbing', 'pm', 'pn', 'pohl', 'poker', 'porn', 'post', 'pr', + 'praxi', 'press', 'pro', 'prod', 'productions', 'prof', + 'properties', 'property', 'ps', 'pt', 'pub', 'pw', 'py', + # q + 'qa', 'qpon', 'quebec', + # r + 're', 'realtor', 'recipes', 'red', 'rehab', 'reise', 'reisen', + 'reit', 'ren', 'rentals', 'repair', 'report', 'republican', + 'rest', 'restaurant', 'reviews', 'rich', 'rio', 'rip', 'ro', + 'rocks', 'rodeo', 'rs', 'rsvp', 'ru', 'ruhr', 'rw', 'ryukyu', + # s + 'sa', 'saarland', 'sale', 'samsung', 'sarl', 'saxo', 'sb', 'sc', + 'sca', 'scb', 'schmidt', 'school', 'schule', 'schwarz', + 'science', 'scot', 'sd', 'se', 'services', 'sew', 'sexy', 'sg', + 'sh', 'shiksha', 'shoes', 'shriram', 'si', 'singles', 'sj', 'sk', + 'sky', 'sl', 'sm', 'sn', 'so', 'social', 'software', 'sohu', + 'solar', 'solutions', 'soy', 'space', 'spiegel', 'sr', 'st', + 'style', 'su', 'supplies', 'supply', 'support', 'surf', + 'surgery', 'suzuki', 'sv', 'sx', 'sy', 'sydney', 'systems', 'sz', + # t + 'taipei', 'tatar', 'tattoo', 'tax', 'tc', 'td', 'technology', + 'tel', 'temasek', 'tennis', 'tf', 'tg', 'th', 'tienda', 'tips', + 'tires', 'tirol', 'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'today', + 'tokyo', 'tools', 'top', 'toshiba', 'town', 'toys', 'tp', 'tr', + 'trade', 'training', 'travel', 'trust', 'tt', 'tui', 'tv', 'tw', 'tz', - 'ua', - 'ug', - 'uk', - 'um', - 'us', - 'uy', - 'uz', - 'va', - 'vc', - 've', - 'vg', - 'vi', - 'vn', - 'vu', - 'wf', - 'ws', - 'xn--0zwm56d', - 'xn--11b5bs3a9aj6g', - 'xn--80akhbyknj4f', - 'xn--9t4b11yi5a', - 'xn--deba0ad', - 'xn--g6w251d', - 'xn--hgbk6aj7f53bba', - 'xn--hlcj6aya9esc7a', - 'xn--jxalpdlp', - 'xn--kgbechtv', - 'xn--p1ai', - 'xn--zckzah', - 'ye', - 'yt', - 'yu', - 'za', - 'zm', - 'zw', + # u + 'ua', 'ug', 'uk', 'university', 'uno', 'uol', 'us', 'uy', 'uz', + # v + 'va', 'vacations', 'vc', 've', 'vegas', 'ventures', + 'versicherung', 'vet', 'vg', 'vi', 'viajes', 'video', 'villas', + 'vision', 'vlaanderen', 'vn', 'vodka', 'vote', 'voting', 'voto', + 'voyage', 'vu', + # w + 'wales', 'wang', 'watch', 'webcam', 'website', 'wed', 'wedding', + 'wf', 'whoswho', 'wien', 'wiki', 'williamhill', 'wme', 'work', + 'works', 'world', 'ws', 'wtc', 'wtf', + # x + 'xn--1qqw23a', 'xn--3bst00m', 'xn--3ds443g', 'xn--3e0b707e', + 'xn--45brj9c', 'xn--45q11c', 'xn--4gbrim', 'xn--55qw42g', + 'xn--55qx5d', 'xn--6frz82g', 'xn--6qq986b3xl', 'xn--80adxhks', + 'xn--80ao21a', 'xn--80asehdb', 'xn--80aswg', 'xn--90a3ac', + 'xn--90ais', 'xn--b4w605ferd', 'xn--c1avg', 'xn--cg4bki', + 'xn--clchc0ea0b2g2a9gcd', 'xn--czr694b', 'xn--czrs0t', + 'xn--czru2d', 'xn--d1acj3b', 'xn--d1alf', 'xn--fiq228c5hs', + 'xn--fiq64b', 'xn--fiqs8s', 'xn--fiqz9s', 'xn--flw351e', + 'xn--fpcrj9c3d', 'xn--fzc2c9e2c', 'xn--gecrj9c', 'xn--h2brj9c', + 'xn--hxt814e', 'xn--i1b6b1a6a2e', 'xn--io0a7i', 'xn--j1amh', + 'xn--j6w193g', 'xn--kprw13d', 'xn--kpry57d', 'xn--kput3i', + 'xn--l1acc', 'xn--lgbbat1ad8j', 'xn--mgb9awbf', + 'xn--mgba3a4f16a', 'xn--mgbaam7a8h', 'xn--mgbab2bd', + 'xn--mgbayh7gpa', 'xn--mgbbh1a71e', 'xn--mgbc0a9azcg', + 'xn--mgberp4a5d4ar', 'xn--mgbx4cd0ab', 'xn--ngbc5azd', + 'xn--node', 'xn--nqv7f', 'xn--nqv7fs00ema', 'xn--o3cw4h', + 'xn--ogbpf8fl', 'xn--p1acf', 'xn--p1ai', 'xn--pgbs0dh', + 'xn--q9jyb4c', 'xn--qcka1pmc', 'xn--rhqv96g', 'xn--s9brj9c', + 'xn--ses554g', 'xn--unup4y', 'xn--vermgensberater-ctb', + 'xn--vermgensberatung-pwb', 'xn--vhquv', 'xn--wgbh1c', + 'xn--wgbl6a', 'xn--xhq521b', 'xn--xkc2al3hye2a', + 'xn--xkc2dl3a5ee0h', 'xn--yfro4i67o', 'xn--ygbi2ammx', + 'xn--zfr164b', 'xxx', 'xyz', + # y + 'yachts', 'yandex', 'ye', 'yodobashi', 'yoga', 'yokohama', + 'youtube', 'yt', + # z + 'za', 'zip', 'zm', 'zone', 'zuerich', 'zw' ] @@ -1936,6 +1818,7 @@ class IS_HTTP_URL(Validator): error_message='Enter a valid URL', allowed_schemes=None, prepend_scheme='http', + allowed_tlds=None ): self.error_message = error_message @@ -1943,6 +1826,10 @@ class IS_HTTP_URL(Validator): self.allowed_schemes = http_schemes else: self.allowed_schemes = allowed_schemes + if allowed_tlds is None: + self.allowed_tlds = official_top_level_domains + else: + self.allowed_tlds = allowed_tlds self.prepend_scheme = prepend_scheme for i in self.allowed_schemes: @@ -1986,7 +1873,7 @@ class IS_HTTP_URL(Validator): if domainMatch: # if the top-level domain really exists if domainMatch.group(5).lower()\ - in official_top_level_domains: + in self.allowed_tlds: # Then this HTTP URL is valid return (value, None) else: @@ -2111,13 +1998,18 @@ class IS_URL(Validator): mode='http', allowed_schemes=None, prepend_scheme='http', + allowed_tlds=None ): self.error_message = error_message self.mode = mode.lower() - if not self.mode in ['generic', 'http']: + if self.mode not in ['generic', 'http']: raise SyntaxError("invalid mode '%s' in IS_URL" % self.mode) self.allowed_schemes = allowed_schemes + if allowed_tlds is None: + self.allowed_tlds = official_top_level_domains + else: + self.allowed_tlds = allowed_tlds if self.allowed_schemes: if prepend_scheme not in self.allowed_schemes: @@ -2150,7 +2042,8 @@ class IS_URL(Validator): elif self.mode == 'http': subMethod = IS_HTTP_URL(error_message=self.error_message, allowed_schemes=self.allowed_schemes, - prepend_scheme=self.prepend_scheme) + prepend_scheme=self.prepend_scheme, + allowed_tlds=self.allowed_tlds) else: raise SyntaxError("invalid mode '%s' in IS_URL" % self.mode) @@ -2160,12 +2053,12 @@ class IS_URL(Validator): try: asciiValue = unicode_to_ascii_url(value, self.prepend_scheme) except Exception: - #If we are not able to convert the unicode url into a + # If we are not able to convert the unicode url into a # US-ASCII URL, then the URL is not valid return (value, translate(self.error_message)) methodResult = subMethod(asciiValue) - #if the validation of the US-ASCII version of the value failed + # if the validation of the US-ASCII version of the value failed if not methodResult[1] is None: # then return the original input value, not the US-ASCII version return (value, methodResult[1]) @@ -2233,7 +2126,7 @@ class IS_TIME(Validator): if not value.group('s') is None: s = int(value.group('s')) if value.group('d') == 'pm' and 0 < h < 12: - h = h + 12 + h += 12 if value.group('d') == 'am' and h == 12: h = 0 if not (h in range(24) and m in range(60) and s @@ -2247,18 +2140,23 @@ class IS_TIME(Validator): pass return (ivalue, translate(self.error_message)) + # A UTC class. class UTC(datetime.tzinfo): """UTC""" ZERO = datetime.timedelta(0) + def utcoffset(self, dt): return UTC.ZERO + def tzname(self, dt): return "UTC" + def dst(self, dt): return UTC.ZERO utc = UTC() + class IS_DATE(Validator): """ Examples: @@ -2272,7 +2170,7 @@ class IS_DATE(Validator): def __init__(self, format='%Y-%m-%d', error_message='Enter date as %(format)s', - timezone = None): + timezone=None): self.format = translate(format) self.error_message = str(error_message) self.timezone = timezone @@ -2507,23 +2405,23 @@ class IS_LIST_OF(Validator): ivalue = value if not isinstance(value, list): ivalue = [ivalue] - if not self.minimum is None and len(ivalue) < self.minimum: + ivalue = [i for i in ivalue if str(i).strip()] + if self.minimum is not None and len(ivalue) < self.minimum: return (ivalue, translate(self.error_message) % dict(min=self.minimum, max=self.maximum)) - if not self.maximum is None and len(ivalue) > self.maximum: + if self.maximum is not None and len(ivalue) > self.maximum: return (ivalue, translate(self.error_message) % dict(min=self.minimum, max=self.maximum)) new_value = [] other = self.other if self.other: - if not isinstance(other, (list,tuple)): + if not isinstance(other, (list, tuple)): other = [other] for item in ivalue: - if str(item).strip(): - v = item - for validator in other: - (v, e) = validator(v) - if e: - return (ivalue, e) - new_value.append(v) + v = item + for validator in other: + (v, e) = validator(v) + if e: + return (ivalue, e) + new_value.append(v) ivalue = new_value return (ivalue, None) @@ -2738,7 +2636,7 @@ class IS_EMPTY_OR(Validator): return self.other.formatter(value) return value -IS_NULL_OR = IS_EMPTY_OR # for backward compatibility +IS_NULL_OR = IS_EMPTY_OR # for backward compatibility class CLEANUP(Validator): @@ -2848,6 +2746,7 @@ class LazyCrypt(object): def __ne__(self, other): return not self.__eq__(other) + class CRYPT(object): """ Examples: @@ -2965,7 +2864,7 @@ otherset = frozenset( def calc_entropy(string): - " calculates a simple entropy for a given string " + """ calculates a simple entropy for a given string """ import math alphabet = 0 # alphabet size other = set() @@ -3487,6 +3386,7 @@ class IS_IPV4(Validator): return (value, None) return (value, translate(self.error_message)) + class IS_IPV6(Validator): """ Checks if field's value is an IP version 6 address. First attempts to @@ -3808,7 +3708,7 @@ class IS_IPADDRESS(Validator): from gluon.contrib import ipaddr as ipaddress try: - ip = ipaddress.ip_address(value) + ip = ipaddress.IPAddress(value) except ValueError, e: return (value, translate(self.error_message)) diff --git a/gluon/widget.py b/gluon/widget.py index b55f9a98..64720bba 100644 --- a/gluon/widget.py +++ b/gluon/widget.py @@ -143,7 +143,7 @@ class web2pyDialog(object): root.withdraw() self.root = Tkinter.Toplevel(root, bg=bg_color) - self.root.resizable(0,0) + self.root.resizable(0, 0) self.root.title(ProgramName) self.options = options @@ -151,7 +151,7 @@ class web2pyDialog(object): self.menu = Tkinter.Menu(self.root) servermenu = Tkinter.Menu(self.menu, tearoff=0) httplog = os.path.join(self.options.folder, 'httpserver.log') - iconphoto = os.path.join('extras','icons','web2py.gif') + iconphoto = os.path.join('extras', 'icons', 'web2py.gif') if os.path.exists(iconphoto): img = Tkinter.PhotoImage(file=iconphoto) self.root.tk.call('wm', 'iconphoto', self.root._w, img) @@ -206,7 +206,7 @@ class web2pyDialog(object): self.logoarea.grid(row=0, column=0, columnspan=4, sticky=sticky) self.logoarea.after(1000, self.update_canvas) - logo = os.path.join('extras','icons','splashlogo.gif') + logo = os.path.join('extras', 'icons', 'splashlogo.gif') if os.path.exists(logo): img = Tkinter.PhotoImage(file=logo) pnl = Tkinter.Label(self.logoarea, image=img, background=bg_color, bd=0) @@ -261,8 +261,8 @@ class web2pyDialog(object): Tkinter.Label(self.root, text='Server Port:', bg=bg_color, justify=Tkinter.RIGHT).grid(row=shift, - column=1, pady=10, - sticky=sticky) + column=1, pady=10, + sticky=sticky) self.port_number = Tkinter.Entry(self.root) self.port_number.insert(Tkinter.END, self.options.port) @@ -272,8 +272,8 @@ class web2pyDialog(object): Tkinter.Label(self.root, text='Choose Password:', bg=bg_color, justify=Tkinter.RIGHT).grid(row=shift + 1, - column=1, - sticky=sticky) + column=1, + sticky=sticky) self.password = Tkinter.Entry(self.root, show='*') self.password.bind('', lambda e: self.start()) @@ -328,7 +328,7 @@ class web2pyDialog(object): if os.path.exists( 'applications/%s/models/scheduler.py' % arq)] if start: - #the widget takes care of starting the scheduler + # the widget takes care of starting the scheduler if self.options.scheduler and self.options.with_scheduler: apps = [app.strip() for app in self.options.scheduler.split(',') @@ -336,7 +336,7 @@ class web2pyDialog(object): for app in apps: self.try_start_scheduler(app) - #reset the menu + # reset the menu self.schedmenu.delete(0, len(available_apps)) for arq in available_apps: if arq not in self.scheduler_processes: @@ -413,7 +413,7 @@ class web2pyDialog(object): def connect_pages(self): """ Connects pages """ - #reset the menu + # reset the menu available_apps = [arq for arq in os.listdir('applications/') if os.path.exists( 'applications/%s/__init__.py' % arq)] @@ -737,6 +737,13 @@ def console(): default=False, help='disable all output') + parser.add_option('-e', + '--errors_to_console', + action='store_true', + dest='print_errors', + default=False, + help='log all errors to console') + msg = ('set debug output level (0-100, 0 means all, 100 means none; ' 'default is 30)') parser.add_option('-D', @@ -932,19 +939,19 @@ def console(): if options.gae: if not os.path.exists('app.yaml'): name = raw_input("Your GAE app name: ") - content = open(os.path.join('examples','app.example.yaml'),'rb').read() - open('app.yaml','wb').write(content.replace("yourappname",name)) + content = open(os.path.join('examples', 'app.example.yaml'), 'rb').read() + open('app.yaml', 'wb').write(content.replace("yourappname", name)) else: print "app.yaml alreday exists in the web2py folder" if not os.path.exists('gaehandler.py'): - content = open(os.path.join('handlers','gaehandler.py'),'rb').read() - open('gaehandler.py','wb').write(content) + content = open(os.path.join('handlers', 'gaehandler.py'), 'rb').read() + open('gaehandler.py', 'wb').write(content) else: print "gaehandler.py alreday exists in the web2py folder" sys.exit(0) try: - options.ips = list(set( # no duplicates + options.ips = list(set( # no duplicates [addrinfo[4][0] for addrinfo in getipaddrinfo(socket.getfqdn()) if not is_loopback_ip_address(addrinfo=addrinfo)])) except socket.gaierror: @@ -1051,6 +1058,11 @@ def start_schedulers(options): print 'starting single-scheduler for "%s"...' % app_ run(app_, True, True, None, False, code) return + + # Work around OS X problem: http://bugs.python.org/issue9405 + import urllib + urllib.getproxies() + for app in apps: app_, code = get_code_for_scheduler(app, options) if not app_: @@ -1086,7 +1098,7 @@ def start(cron=True): print ProgramAuthor print ProgramVersion - from dal.adapters.base import DRIVERS + from pydal.drivers import DRIVERS if not options.nobanner: print 'Database drivers available: %s' % ', '.join(DRIVERS) @@ -1105,7 +1117,7 @@ def start(cron=True): if hasattr(options, key): setattr(options, key, getattr(options2, key)) - logfile0 = os.path.join('extras','examples','logging.example.conf') + logfile0 = os.path.join('extras', 'examples', 'logging.example.conf') if not os.path.exists('logging.conf') and os.path.exists(logfile0): import shutil sys.stdout.write("Copying logging.conf.example to logging.conf ... ") @@ -1176,7 +1188,7 @@ def start(cron=True): root = None - if not options.nogui and options.password=='': + if not options.nogui and options.password == '': try: import Tkinter havetk = True @@ -1256,6 +1268,7 @@ end tell # if the line was not found (under py2exe & when file was modified) import linecache py2exe_getline = linecache.getline + def getline(filename, lineno, *args, **kwargs): line = py2exe_getline(filename, lineno, *args, **kwargs) if not line: diff --git a/scripts/cleanhtml.py b/scripts/cleanhtml.py index e0df55b2..e4f71df0 100755 --- a/scripts/cleanhtml.py +++ b/scripts/cleanhtml.py @@ -57,8 +57,11 @@ def read_file(filename): finally: f.close() -file = sys.argv[1] -if file[-4:] == '.css': - print cleancss(read_file(file)) -if file[-5:] == '.html': - print cleanhtml(read_file(file)) +for file in sys.argv[1:]: + data = read_file(file) + open(file+'.bak2', 'w').write(data) + if file[-4:] == '.css': + data = cleancss(data) + if file[-5:] == '.html': + data = cleanhtml(data) + open(file, 'w').write(data) diff --git a/scripts/extract_sqlite_models.py b/scripts/extract_sqlite_models.py index b9638ede..d5fad50b 100644 --- a/scripts/extract_sqlite_models.py +++ b/scripts/extract_sqlite_models.py @@ -83,7 +83,7 @@ def sqlite(database_name): for line in sql_lines[1:-1]: if re.search('KEY', line) or re.search('PRIMARY', line) or re.search('"ID"', line) or line.startswith(')'): continue - hit = re.search(r'"(\S+)"\s+(\w+(\(\S+\))?),?( .*)?', line) + hit = re.search(r'\[(\S+)\]\s+(\w+(\(\S+\))?),?( .*)?', line) if hit is not None: name, d_type = hit.group(1), hit.group(2) d_type = re.sub(r'(\w+)\(.*', r'\1', d_type) @@ -94,7 +94,7 @@ def sqlite(database_name): else: field_type = 'reference %s.%s' % (fks[name][0], fks[name][1]) else: - field_type = data_type_map[d_type] + field_type = data_type_map[d_type.lower()] web2py_table_code += "\n Field('%s','%s')," % ( name, field_type) web2py_table_code = "legacy_db.define_table('%s',%s\n migrate=False)" % (table_name, web2py_table_code) diff --git a/scripts/parse_top_level_domains.py b/scripts/parse_top_level_domains.py new file mode 100644 index 00000000..06590afc --- /dev/null +++ b/scripts/parse_top_level_domains.py @@ -0,0 +1,41 @@ +#!/bin/env python +# -*- coding: utf-8 -*- +""" +Use to update official_top_level_domains in gluon/validators.py +""" + +import itertools +import operator +import urllib2 + +LIMIT = 70 +PREFIX = ' ' +TLDS_URL = 'http://data.iana.org/TLD/tlds-alpha-by-domain.txt' + +resp = urllib2.urlopen(TLDS_URL) +content = resp.read() + +valid_lines = [a.strip().lower() for a in content.split('\n') if a.strip() and a.strip()[0] != '#'] +valid_lines += ['localhost'] + +print 'Fetched TLDs are %s' % len(valid_lines) + +results = [list(g) for k, g in itertools.groupby(sorted(valid_lines), key=operator.itemgetter(0))] + +output = [] +line = "'%s', " + +for a in results: + output.append('%s# %s' % (PREFIX, a[-1][0])) + thisline = PREFIX + for c in a: + newline = thisline + line % c + if len(newline) > 70: + output.append(thisline[:-1]) + thisline = PREFIX + line % c + else: + thisline += line % c + if thisline: + output.append(thisline[:-1]) + +print '[\n' + '\n'.join(output)[:-1] + '\n]' diff --git a/scripts/sessions2trash.py b/scripts/sessions2trash.py index 10fe37b9..bbc2f408 100755 --- a/scripts/sessions2trash.py +++ b/scripts/sessions2trash.py @@ -24,7 +24,7 @@ Typical usage: # Delete session in a module (move to the modules folder) from sessions2trash import single_loop def delete_sessions(): - single_loop() + single_loop(auth.settings.expiration) """ @@ -42,7 +42,6 @@ import datetime import os import stat import time -import glob EXPIRATION_MINUTES = 60 SLEEP_MINUTES = 5 @@ -119,10 +118,20 @@ class SessionSetFiles(SessionSet): def __init__(self, expiration, force, verbose): SessionSet.__init__(self, expiration, force, verbose) + def cleanup_empty_folders(self, root_path): + for path, dirs, files in os.walk(root_path, topdown=False): + for d in dirs: + dd = os.path.join(path, d) + if not os.listdir(dd): + os.rmdir(dd) + def get(self): """Return list of SessionFile instances for existing sessions.""" root_path = os.path.join(current.request.folder, 'sessions') - return [SessionFile(os.path.join(path, x)) for path,dirs,files in os.walk(root_path) for x in files] + for path, dirs, files in os.walk(root_path, topdown=False): + for x in files: + yield SessionFile(os.path.join(path, x)) + self.cleanup_empty_folders(root_path) class SessionDb(object): @@ -163,9 +172,6 @@ class SessionFile(object): def delete(self): try: os.unlink(self.filename) - path = os.path.dirname(filename) - if not path.endswith('sessions') and len(os.listdir(path))==0: - os.rmdir(path) except: pass diff --git a/scripts/setup-web2py-cloudfoundry.sh b/scripts/setup-web2py-cloudfoundry.sh new file mode 100755 index 00000000..9a712624 --- /dev/null +++ b/scripts/setup-web2py-cloudfoundry.sh @@ -0,0 +1,6 @@ +read -p "Please choose a Web2Py Administrator password:" passwd +echo "web: python web2py.py -a '$passwd' -i 0.0.0.0 -p \$PORT" > Procfile +mkdir vendor +pip install --download vendor -r requirements.txt +read -p "Please choose a CF application name:" appname +cf push $appname -b https://github.com/cloudfoundry/buildpack-python.git diff --git a/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh b/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh index 6c6ab148..15075f8e 100644 --- a/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh +++ b/scripts/setup-web2py-nginx-uwsgi-ubuntu.sh @@ -84,7 +84,7 @@ server { ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA; - ssl_protocols SSLv3 TLSv1; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; keepalive_timeout 70; location / { #uwsgi_pass 127.0.0.1:9001; @@ -144,6 +144,7 @@ reload-on-as = 256 reload-on-rss = 192 uid = www-data gid = www-data +touch-reload = /home/www-data/web2py/routes.py cron = 0 0 -1 -1 -1 python /home/www-data/web2py/web2py.py -Q -S welcome -M -R scripts/sessions2trash.py -A -o no-orphans = true ' >/etc/uwsgi/web2py.ini diff --git a/scripts/setup-web2py-ubuntu.sh b/scripts/setup-web2py-ubuntu.sh index 4ce86474..01682d1a 100644 --- a/scripts/setup-web2py-ubuntu.sh +++ b/scripts/setup-web2py-ubuntu.sh @@ -1,7 +1,7 @@ echo "This script will: 1) install all modules need to run web2py on Ubuntu 14.04 2) install web2py in /home/www-data/ -3) create a self signed sll certificate +3) create a self signed ssl certificate 4) setup web2py with mod_wsgi 5) overwrite /etc/apache2/sites-available/default 6) restart apache. diff --git a/scripts/setup-web2py-ws2012r2.ps1 b/scripts/setup-web2py-ws2012r2.ps1 new file mode 100644 index 00000000..0a4df6a0 --- /dev/null +++ b/scripts/setup-web2py-ws2012r2.ps1 @@ -0,0 +1,169 @@ +"This script will work fine for a few cases 'by default':" +" - completely CLEAN WS2012R2 host" +" - python 2.7 installed in the default path" +" - wfasctgi installed on the default path" +"It'll install web2py under the default website " +" You can use it as a boilerplate to automate your deployments" +" but it still is released AS IT IS. " +"BIG FAT WARNING: It will install a bunch of dependecies +Inspect the source before executing it" +"" +"" +$ErrorActionPreference = 'stop' + +$REALLY_SURE = Read-Host "Do you want to start with web2py deployment? [y/N]" +if (!@('y', 'Y') -contains $REALLY_SURE) { + "Ok, Exiting without doing anything" + exit 1 +} +#setting root folder +$rootfolder = $pwd + +### utilities - start +function ask_a_question($question) { + $response = Read-Host "$question [Y/n]" + if (@('Y', 'y', '', $null) -contains $response) { + return $true + } else { + return $false + } +} + +function unzip_me { + #Load the assembly + [System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null + #Unzip the file + [System.IO.Compression.ZipFile]::ExtractToDirectory($pathToZip, $targetDir) +} + + +### utilities - end + +#install 4.5 that is needed for a bunch of things anyway +Install-WindowsFeature Net-Framework-45-Core + +#fetch web2py +$web2py_url = 'http://www.web2py.com/examples/static/web2py_src.zip' +$web2py_file = "$pwd\web2py_src.zip" +if (!(Test-Path $web2py_file)) { + (new-object net.webclient).DownloadFile($web2py_url, $web2py_file) +} +#Load the assembly +[System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null +#Unzip the file +[System.IO.Compression.ZipFile]::ExtractToDirectory($web2py_file, $pwd) + +#features installation (IIS, needed modules, python, chocolatey, etc) +$installfeatures = ask_a_question('Do you want to install needed features?') + +if ($installfeatures) { + Install-WindowsFeature Web-Server,Web-Default-Doc,Web-Static-Content,Web-Http-Redirect,Web-Http-Logging,Web-Request-Monitor,` + Web-Http-Tracing,Web-Stat-Compression,Web-Dyn-Compression,Web-Filtering,Web-Basic-Auth,Web-Windows-Auth,Web-AppInit,` + Web-CGI,Web-WebSockets,Web-Mgmt-Console,Web-Net-Ext45 +} + +$copy_web2py = ask_a_question("Copy web2py to the default website root?") +if ($copy_web2py) { + Import-Module WebAdministration + $available_websites = Get-Website + if ($available_websites[0] -eq $null) { + $default_one = $available_websites + } else { + $default_one = $available_websites[0] + } + $iis_root = [System.Environment]::ExpandEnvironmentVariables($default_one.PhysicalPath) + Copy-Item "$rootfolder\web2py\*" $iis_root -Recurse + $rootfolder = $iis_root + $acl = (Get-Item $rootfolder).GetAccessControl('Access') + $identity = "BUILTIN\IIS_IUSRS" + $fileSystemRights = "Modify" + $inheritanceFlags = "ContainerInherit, ObjectInherit" + $propagationFlags = "None" + $accessControlType = "Allow" + $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, $fileSystemRights, $inheritanceFlags, $propagationFlags, $accessControlType) + $acl.SetAccessRule($rule) + Set-Acl $rootfolder $acl +} + +$create_cert = ask_a_question("Do you want to create a self-signed SSL cert?") +if ($create_cert) { + $cert = New-SelfSignedCertificate -DnsName ("localtest.me","*.localtest.me") -CertStoreLocation cert:\LocalMachine\My + $rootStore = Get-Item cert:\LocalMachine\Root + $rootStore.Open("ReadWrite") + $rootStore.Add($cert) + $rootStore.Close(); + Import-Module WebAdministration + Set-Location IIS:\SslBindings + New-WebBinding -Name "Default Web Site" -IP "*" -Port 443 -Protocol https + $cert | New-Item 0.0.0.0!443 + Set-Location $pwd +} + +"checking for chocolatey" +if (Get-Command "choco.exe" -ErrorAction SilentlyContinue) +{ + "chocolatey found" +} else { + "installing chocolatey" + (new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1') | iex +} +"installing url-rewrite" +choco install UrlRewrite +$pythonexe = Read-Host 'Python.exe path [C:\Python27\python.exe]' +if (($pythonexe -eq '') -or ($pythonexe -eq $null)) { + $pythonexe = 'C:\Python27\python.exe' +} +if (!(Test-Path $pythonexe)) { + "ERROR: python executable not found" + $pythonwanted = ask_a_question("do you want to install it automatically?") + + if ($pythonwanted) { + choco install webpicmd + WebpiCmd.exe /Install /Products:WFastCgi_21_279 + $pythonexe = 'C:\Python27\python.exe' + } + else { + exit 1 + } + +} +$wfastcgipath = Read-Host 'wfastcgi.py path [C:\Python27\Scripts\wfastcgi.py]' +if (($wfastcgipath -eq '') -or ($wfastcgipath -eq $null)) { + $wfastcgipath = 'C:\Python27\Scripts\wfastcgi.py' +} + +if (-not (Test-Path $wfastcgipath)) { + "ERROR: wfastcgi.py not found" + + $wfastcgiwanted = ask_a_question("do you want to install it automatically?") + if ($wfastcgiwanted) { + choco install webpicmd + WebpiCmd.exe /Install /Products:WFastCgi_21_279 + } else { + exit 1 + } +} +$pythondir = Split-Path c:\python27\python.exe +#installing dependencies +$env:Path = $env:Path + ";$pythondir;$pythondir\Scripts" + +pip install pypiwin32 + +$PW = Read-Host 'Web2py Admin Password' + +$appcmdpath = "$env:windir\system32\inetsrv\appcmd.exe" + +& $appcmdpath set config /section:system.webServer/fastCGI "/+[fullPath='$pythonexe', arguments='$wfastcgipath']" +& $appcmdpath unlock config -section:system.webServer/handlers + +& cd $rootfolder +& $pythonexe -c "from gluon.main import save_password; save_password('$PW',443)" + +$webconfig_template = Join-Path $rootfolder "examples\web.config" +$destination = Join-Path $rootfolder "web.config" +$scriptprocessor = 'scriptProcessor="{0}|{1}"' -f $pythonexe, $wfastcgipath + +(Get-Content $webconfig_template) | Foreach-Object {$_ -replace 'scriptProcessor="SCRIPT_PROCESSOR"', $scriptprocessor} | where {$_ -ne ""} | Set-Content $destination +"" +"Installation finished. Web2py is available either on http://localhost/ or at https://localtest.me/" +"" diff --git a/scripts/tickets2slack.py b/scripts/tickets2slack.py new file mode 100755 index 00000000..692dce2b --- /dev/null +++ b/scripts/tickets2slack.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Post error tickets to slack on a 5 minute schedule. +# +# Proper use depends on having created a web-hook through Slack, and having set +# that value in your app's model as the value of global_settings.slack_hook. +# Details on creating web-hooks can be found at https://slack.com/integrations +# +# requires the Requests module for posting to slack, other requirements are +# standard or provided by web2py +# +# Usage (on Unices), replace myapp with the name of your application and run: +# nohup python web2py.py -S myapp -M -R scripts/tickets2slack.py & + +import sys +import os +import time +import pickle +import json + +try: + import requests +except ImportError as e: + print "missing module 'Requests', aborting." + sys.exit(1) + +from gluon import URL +from gluon.utils import md5_hash +from gluon.restricted import RestrictedError +from gluon.settings import global_settings + + +path = os.path.join(request.folder, 'errors') +sent_errors_file = os.path.join(path, 'slack_errors.pickle') +hashes = {} +if os.path.exists(sent_errors_file): + try: + with open(sent_errors_file, 'rb') as f: + hashes = pickle.load(f) + except Exception as _: + pass + +# ## CONFIGURE HERE +SLEEP_MINUTES = 5 +ALLOW_DUPLICATES = False +global_settings.slack_hook = global_settings.slack_hook or \ + 'https://hooks.slack.com/services/your_service' +# ## END CONFIGURATION + +while 1: + for file_name in os.listdir(path): + if file_name == 'slack_errors.pickle': + continue + + if not ALLOW_DUPLICATES: + key = md5_hash(file_name) + if key in hashes: + continue + hashes[key] = 1 + + error = RestrictedError() + + try: + error.load(request, request.application, file_name) + except Exception as _: + continue # not an exception file? + + url = URL(a='admin', f='ticket', args=[request.application, file], + scheme=True) + payload = json.dumps(dict(text="Error in %(app)s.\n%(url)s" % + dict(app=request.application, url=url))) + + requests.post(global_settings.slack_hook, data=dict(payload=payload)) + + with open(sent_errors_file, 'wb') as f: + pickle.dump(hashes, f) + time.sleep(SLEEP_MINUTES * 60) diff --git a/scripts/web2py-scheduler.conf b/scripts/web2py-scheduler.conf new file mode 100755 index 00000000..46eae509 --- /dev/null +++ b/scripts/web2py-scheduler.conf @@ -0,0 +1,21 @@ +description "web2py task scheduler" + +# INSTRUCTIONS: +# COPY THIS FILE IN: +# /etc/init/web2py-scheduler.con +# +# To start/stop the scheduler, use +# "sudo start web2py-scheduler" +# "sudo stop web2py-scheduler" +# "sudo status web2py-scheduler" +# +# YOU MAY HAVE TO EDIT PATH TO WEB2PY BELOW + +start on (local-filesystems and net-device-up IFACE=eth0) +stop on shutdown + +# Give up if restart occurs 8 times in 60 seconds. +respawn limit 8 60 + +exec sudo -u www-data python /home/www-data/web2py/web2py.py -K parking > /tmp/scheduler.out +respawn diff --git a/scripts/web2py.ubuntu.sh b/scripts/web2py.ubuntu.sh index d3dc19ea..e6b2662b 100755 --- a/scripts/web2py.ubuntu.sh +++ b/scripts/web2py.ubuntu.sh @@ -62,7 +62,7 @@ do_start() start-stop-daemon --stop --test --quiet --pidfile $PIDFILE \ && return 1 - start-stop-daemon --start --quiet --pidfile $PIDFILE \ + start-stop-daemon --start --quiet -m --pidfile $PIDFILE \ ${DAEMON_USER:+--chuid $DAEMON_USER} --chdir $DAEMON_DIR \ --background --exec $DAEMON -- $DAEMON_ARGS \ || return 2