From 0cb2241ea0dd73321165c1ff06e467b6ca7823f7 Mon Sep 17 00:00:00 2001 From: Dominic Bosch Date: Thu, 3 Apr 2014 17:41:51 +0200 Subject: [PATCH] Engine started in coffeescript --- coffee/components-manager.coffee | 64 +- coffee/dynamic-modules.coffee | 53 +- coffee/engine.coffee | 193 + coffee/webapi-eca.coffee | 21 +- documentation/techdoc/techdoc.tex | 7 +- js-coffee/components-manager.js | 280 +- js-coffee/config.js | 125 +- js-coffee/dynamic-modules.js | 97 +- js-coffee/engine.js | 411 +- js-coffee/event-poller.js | 17 +- js-coffee/persistence.js | 856 ++- js-coffee/webapi-eca.js | 128 +- js/engine.js | 327 +- testing/files/testObjects.json | 36 +- testing/test_components-manager.coffee | 64 +- testing/test_dynamic-modules.coffee | 41 + testing/test_engine.coffee | 84 + .../coffee/forge_action_invoker.coffee | 23 +- .../handlers/coffee/forge_event_poller.coffee | 2 +- webpages/handlers/coffee/forge_rule.coffee | 71 +- webpages/handlers/js/forge_action_invoker.js | 4 +- webpages/handlers/js/forge_event_poller.js | 4 +- webpages/handlers/js/forge_rule.js | 113 +- .../remote-scripts/forge_action_invoker.html | 2 +- .../handlers/remote-scripts/forge_event.html | 2 +- .../remote-scripts/forge_event_poller.html | 2 +- .../handlers/remote-scripts/forge_rule.html | 3 +- .../templates/forge_action_invoker.html | 4 +- .../templates/forge_event_poller.html | 4 +- webpages/handlers/templates/forge_rule.html | 4 +- .../handlers/templates/old_forge_rules.html | 130 - .../{ => js}/ace-src-min-noconflict/ace.js | 0 .../ace-src-min-noconflict/ext-chromevox.js | 0 .../ext-elastic_tabstops_lite.js | 0 .../ace-src-min-noconflict/ext-emmet.js | 0 .../ext-keybinding_menu.js | 0 .../ext-language_tools.js | 0 .../ace-src-min-noconflict/ext-modelist.js | 0 .../ace-src-min-noconflict/ext-old_ie.js | 0 .../ace-src-min-noconflict/ext-searchbox.js | 0 .../ext-settings_menu.js | 0 .../ace-src-min-noconflict/ext-spellcheck.js | 0 .../ace-src-min-noconflict/ext-split.js | 0 .../ext-static_highlight.js | 0 .../ace-src-min-noconflict/ext-statusbar.js | 0 .../ace-src-min-noconflict/ext-textarea.js | 0 .../ace-src-min-noconflict/ext-themelist.js | 0 .../ace-src-min-noconflict/ext-whitespace.js | 0 .../keybinding-emacs.js | 0 .../ace-src-min-noconflict/keybinding-vim.js | 0 .../ace-src-min-noconflict/mode-abap.js | 0 .../mode-actionscript.js | 0 .../ace-src-min-noconflict/mode-ada.js | 0 .../ace-src-min-noconflict/mode-asciidoc.js | 0 .../mode-assembly_x86.js | 0 .../ace-src-min-noconflict/mode-autohotkey.js | 0 .../ace-src-min-noconflict/mode-batchfile.js | 0 .../ace-src-min-noconflict/mode-c9search.js | 0 .../ace-src-min-noconflict/mode-c_cpp.js | 0 .../ace-src-min-noconflict/mode-clojure.js | 0 .../ace-src-min-noconflict/mode-cobol.js | 0 .../ace-src-min-noconflict/mode-coffee.js | 0 .../ace-src-min-noconflict/mode-coldfusion.js | 0 .../ace-src-min-noconflict/mode-csharp.js | 0 .../ace-src-min-noconflict/mode-css.js | 0 .../ace-src-min-noconflict/mode-curly.js | 0 .../{ => js}/ace-src-min-noconflict/mode-d.js | 0 .../ace-src-min-noconflict/mode-dart.js | 0 .../ace-src-min-noconflict/mode-diff.js | 0 .../ace-src-min-noconflict/mode-django.js | 0 .../ace-src-min-noconflict/mode-dot.js | 0 .../ace-src-min-noconflict/mode-ejs.js | 0 .../ace-src-min-noconflict/mode-erlang.js | 0 .../ace-src-min-noconflict/mode-forth.js | 0 .../ace-src-min-noconflict/mode-ftl.js | 0 .../ace-src-min-noconflict/mode-glsl.js | 0 .../ace-src-min-noconflict/mode-golang.js | 0 .../ace-src-min-noconflict/mode-groovy.js | 0 .../ace-src-min-noconflict/mode-haml.js | 0 .../ace-src-min-noconflict/mode-handlebars.js | 0 .../ace-src-min-noconflict/mode-haskell.js | 0 .../ace-src-min-noconflict/mode-haxe.js | 0 .../ace-src-min-noconflict/mode-html.js | 0 .../mode-html_completions.js | 0 .../ace-src-min-noconflict/mode-html_ruby.js | 0 .../ace-src-min-noconflict/mode-ini.js | 0 .../ace-src-min-noconflict/mode-jack.js | 0 .../ace-src-min-noconflict/mode-jade.js | 0 .../ace-src-min-noconflict/mode-java.js | 0 .../ace-src-min-noconflict/mode-javascript.js | 0 .../ace-src-min-noconflict/mode-json.js | 0 .../ace-src-min-noconflict/mode-jsoniq.js | 0 .../ace-src-min-noconflict/mode-jsp.js | 0 .../ace-src-min-noconflict/mode-jsx.js | 0 .../ace-src-min-noconflict/mode-julia.js | 0 .../ace-src-min-noconflict/mode-latex.js | 0 .../ace-src-min-noconflict/mode-less.js | 0 .../ace-src-min-noconflict/mode-liquid.js | 0 .../ace-src-min-noconflict/mode-lisp.js | 0 .../ace-src-min-noconflict/mode-livescript.js | 0 .../ace-src-min-noconflict/mode-logiql.js | 0 .../ace-src-min-noconflict/mode-lsl.js | 0 .../ace-src-min-noconflict/mode-lua.js | 0 .../ace-src-min-noconflict/mode-luapage.js | 0 .../ace-src-min-noconflict/mode-lucene.js | 0 .../ace-src-min-noconflict/mode-makefile.js | 0 .../ace-src-min-noconflict/mode-markdown.js | 0 .../ace-src-min-noconflict/mode-matlab.js | 0 .../ace-src-min-noconflict/mode-mushcode.js | 0 .../mode-mushcode_high_rules.js | 0 .../ace-src-min-noconflict/mode-mysql.js | 0 .../ace-src-min-noconflict/mode-nix.js | 0 .../ace-src-min-noconflict/mode-objectivec.js | 0 .../ace-src-min-noconflict/mode-ocaml.js | 0 .../ace-src-min-noconflict/mode-pascal.js | 0 .../ace-src-min-noconflict/mode-perl.js | 0 .../ace-src-min-noconflict/mode-pgsql.js | 0 .../ace-src-min-noconflict/mode-php.js | 0 .../ace-src-min-noconflict/mode-plain_text.js | 0 .../ace-src-min-noconflict/mode-powershell.js | 0 .../ace-src-min-noconflict/mode-prolog.js | 0 .../ace-src-min-noconflict/mode-properties.js | 0 .../ace-src-min-noconflict/mode-protobuf.js | 0 .../ace-src-min-noconflict/mode-python.js | 0 .../{ => js}/ace-src-min-noconflict/mode-r.js | 0 .../ace-src-min-noconflict/mode-rdoc.js | 0 .../ace-src-min-noconflict/mode-rhtml.js | 0 .../ace-src-min-noconflict/mode-ruby.js | 0 .../ace-src-min-noconflict/mode-rust.js | 0 .../ace-src-min-noconflict/mode-sass.js | 0 .../ace-src-min-noconflict/mode-scad.js | 0 .../ace-src-min-noconflict/mode-scala.js | 0 .../ace-src-min-noconflict/mode-scheme.js | 0 .../ace-src-min-noconflict/mode-scss.js | 0 .../ace-src-min-noconflict/mode-sh.js | 0 .../ace-src-min-noconflict/mode-sjs.js | 0 .../ace-src-min-noconflict/mode-snippets.js | 0 .../mode-soy_template.js | 0 .../ace-src-min-noconflict/mode-space.js | 0 .../ace-src-min-noconflict/mode-sql.js | 0 .../ace-src-min-noconflict/mode-stylus.js | 0 .../ace-src-min-noconflict/mode-svg.js | 0 .../ace-src-min-noconflict/mode-tcl.js | 0 .../ace-src-min-noconflict/mode-tex.js | 0 .../ace-src-min-noconflict/mode-text.js | 0 .../ace-src-min-noconflict/mode-textile.js | 0 .../ace-src-min-noconflict/mode-toml.js | 0 .../ace-src-min-noconflict/mode-twig.js | 0 .../ace-src-min-noconflict/mode-typescript.js | 0 .../ace-src-min-noconflict/mode-vbscript.js | 0 .../ace-src-min-noconflict/mode-velocity.js | 0 .../ace-src-min-noconflict/mode-verilog.js | 0 .../ace-src-min-noconflict/mode-vhdl.js | 0 .../ace-src-min-noconflict/mode-xml.js | 0 .../ace-src-min-noconflict/mode-xquery.js | 0 .../ace-src-min-noconflict/mode-yaml.js | 0 .../ace-src-min-noconflict/snippets/abap.js | 0 .../snippets/actionscript.js | 0 .../ace-src-min-noconflict/snippets/ada.js | 0 .../snippets/asciidoc.js | 0 .../snippets/assembly_x86.js | 0 .../snippets/autohotkey.js | 0 .../snippets/batchfile.js | 0 .../snippets/c9search.js | 0 .../ace-src-min-noconflict/snippets/c_cpp.js | 0 .../snippets/clojure.js | 0 .../ace-src-min-noconflict/snippets/cobol.js | 0 .../ace-src-min-noconflict/snippets/coffee.js | 0 .../snippets/coldfusion.js | 0 .../ace-src-min-noconflict/snippets/csharp.js | 0 .../ace-src-min-noconflict/snippets/css.js | 0 .../ace-src-min-noconflict/snippets/curly.js | 0 .../ace-src-min-noconflict/snippets/d.js | 0 .../ace-src-min-noconflict/snippets/dart.js | 0 .../ace-src-min-noconflict/snippets/diff.js | 0 .../ace-src-min-noconflict/snippets/django.js | 0 .../ace-src-min-noconflict/snippets/dot.js | 0 .../ace-src-min-noconflict/snippets/ejs.js | 0 .../ace-src-min-noconflict/snippets/erlang.js | 0 .../ace-src-min-noconflict/snippets/forth.js | 0 .../ace-src-min-noconflict/snippets/ftl.js | 0 .../ace-src-min-noconflict/snippets/glsl.js | 0 .../ace-src-min-noconflict/snippets/golang.js | 0 .../ace-src-min-noconflict/snippets/groovy.js | 0 .../ace-src-min-noconflict/snippets/haml.js | 0 .../snippets/handlebars.js | 0 .../snippets/haskell.js | 0 .../ace-src-min-noconflict/snippets/haxe.js | 0 .../ace-src-min-noconflict/snippets/html.js | 0 .../snippets/html_completions.js | 0 .../snippets/html_ruby.js | 0 .../ace-src-min-noconflict/snippets/ini.js | 0 .../ace-src-min-noconflict/snippets/jack.js | 0 .../ace-src-min-noconflict/snippets/jade.js | 0 .../ace-src-min-noconflict/snippets/java.js | 0 .../snippets/javascript.js | 0 .../ace-src-min-noconflict/snippets/json.js | 0 .../ace-src-min-noconflict/snippets/jsoniq.js | 0 .../ace-src-min-noconflict/snippets/jsp.js | 0 .../ace-src-min-noconflict/snippets/jsx.js | 0 .../ace-src-min-noconflict/snippets/julia.js | 0 .../ace-src-min-noconflict/snippets/latex.js | 0 .../ace-src-min-noconflict/snippets/less.js | 0 .../ace-src-min-noconflict/snippets/liquid.js | 0 .../ace-src-min-noconflict/snippets/lisp.js | 0 .../snippets/livescript.js | 0 .../ace-src-min-noconflict/snippets/logiql.js | 0 .../ace-src-min-noconflict/snippets/lsl.js | 0 .../ace-src-min-noconflict/snippets/lua.js | 0 .../snippets/luapage.js | 0 .../ace-src-min-noconflict/snippets/lucene.js | 0 .../snippets/makefile.js | 0 .../snippets/markdown.js | 0 .../ace-src-min-noconflict/snippets/matlab.js | 0 .../snippets/mushcode.js | 0 .../snippets/mushcode_high_rules.js | 0 .../ace-src-min-noconflict/snippets/mysql.js | 0 .../ace-src-min-noconflict/snippets/nix.js | 0 .../snippets/objectivec.js | 0 .../ace-src-min-noconflict/snippets/ocaml.js | 0 .../ace-src-min-noconflict/snippets/pascal.js | 0 .../ace-src-min-noconflict/snippets/perl.js | 0 .../ace-src-min-noconflict/snippets/pgsql.js | 0 .../ace-src-min-noconflict/snippets/php.js | 0 .../snippets/plain_text.js | 0 .../snippets/powershell.js | 0 .../ace-src-min-noconflict/snippets/prolog.js | 0 .../snippets/properties.js | 0 .../snippets/protobuf.js | 0 .../ace-src-min-noconflict/snippets/python.js | 0 .../ace-src-min-noconflict/snippets/r.js | 0 .../ace-src-min-noconflict/snippets/rdoc.js | 0 .../ace-src-min-noconflict/snippets/rhtml.js | 0 .../ace-src-min-noconflict/snippets/ruby.js | 0 .../ace-src-min-noconflict/snippets/rust.js | 0 .../ace-src-min-noconflict/snippets/sass.js | 0 .../ace-src-min-noconflict/snippets/scad.js | 0 .../ace-src-min-noconflict/snippets/scala.js | 0 .../ace-src-min-noconflict/snippets/scheme.js | 0 .../ace-src-min-noconflict/snippets/scss.js | 0 .../ace-src-min-noconflict/snippets/sh.js | 0 .../ace-src-min-noconflict/snippets/sjs.js | 0 .../snippets/snippets.js | 0 .../snippets/soy_template.js | 0 .../ace-src-min-noconflict/snippets/space.js | 0 .../ace-src-min-noconflict/snippets/sql.js | 0 .../ace-src-min-noconflict/snippets/stylus.js | 0 .../ace-src-min-noconflict/snippets/svg.js | 0 .../ace-src-min-noconflict/snippets/tcl.js | 0 .../ace-src-min-noconflict/snippets/tex.js | 0 .../ace-src-min-noconflict/snippets/text.js | 0 .../snippets/textile.js | 0 .../ace-src-min-noconflict/snippets/toml.js | 0 .../ace-src-min-noconflict/snippets/twig.js | 0 .../snippets/typescript.js | 0 .../snippets/vbscript.js | 0 .../snippets/velocity.js | 0 .../snippets/verilog.js | 0 .../ace-src-min-noconflict/snippets/vhdl.js | 0 .../ace-src-min-noconflict/snippets/xml.js | 0 .../ace-src-min-noconflict/snippets/xquery.js | 0 .../ace-src-min-noconflict/snippets/yaml.js | 0 .../ace-src-min-noconflict/theme-ambiance.js | 0 .../ace-src-min-noconflict/theme-chaos.js | 0 .../ace-src-min-noconflict/theme-chrome.js | 0 .../ace-src-min-noconflict/theme-clouds.js | 0 .../theme-clouds_midnight.js | 0 .../ace-src-min-noconflict/theme-cobalt.js | 0 .../theme-crimson_editor.js | 0 .../ace-src-min-noconflict/theme-dawn.js | 0 .../theme-dreamweaver.js | 0 .../ace-src-min-noconflict/theme-eclipse.js | 0 .../ace-src-min-noconflict/theme-github.js | 0 .../theme-idle_fingers.js | 0 .../ace-src-min-noconflict/theme-kr.js | 0 .../ace-src-min-noconflict/theme-merbivore.js | 0 .../theme-merbivore_soft.js | 0 .../theme-mono_industrial.js | 0 .../ace-src-min-noconflict/theme-monokai.js | 0 .../theme-pastel_on_dark.js | 0 .../theme-solarized_dark.js | 0 .../theme-solarized_light.js | 0 .../ace-src-min-noconflict/theme-terminal.js | 0 .../ace-src-min-noconflict/theme-textmate.js | 0 .../ace-src-min-noconflict/theme-tomorrow.js | 0 .../theme-tomorrow_night.js | 0 .../theme-tomorrow_night_blue.js | 0 .../theme-tomorrow_night_bright.js | 0 .../theme-tomorrow_night_eighties.js | 0 .../ace-src-min-noconflict/theme-twilight.js | 0 .../theme-vibrant_ink.js | 0 .../ace-src-min-noconflict/theme-xcode.js | 0 .../ace-src-min-noconflict/worker-coffee.js | 0 .../ace-src-min-noconflict/worker-css.js | 0 .../worker-javascript.js | 0 .../ace-src-min-noconflict/worker-json.js | 0 .../ace-src-min-noconflict/worker-lua.js | 0 .../ace-src-min-noconflict/worker-php.js | 0 .../ace-src-min-noconflict/worker-xquery.js | 0 webpages/public/lib/codemirror.css | 263 - webpages/public/lib/codemirror.js | 5944 ----------------- webpages/public/style.css | 7 + 302 files changed, 1679 insertions(+), 7707 deletions(-) create mode 100644 coffee/engine.coffee create mode 100644 testing/test_dynamic-modules.coffee create mode 100644 testing/test_engine.coffee delete mode 100644 webpages/handlers/templates/old_forge_rules.html rename webpages/public/{ => js}/ace-src-min-noconflict/ace.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-chromevox.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-elastic_tabstops_lite.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-emmet.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-keybinding_menu.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-language_tools.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-modelist.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-old_ie.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-searchbox.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-settings_menu.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-spellcheck.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-split.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-static_highlight.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-statusbar.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-textarea.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-themelist.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/ext-whitespace.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/keybinding-emacs.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/keybinding-vim.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-abap.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-actionscript.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-ada.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-asciidoc.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-assembly_x86.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-autohotkey.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-batchfile.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-c9search.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-c_cpp.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-clojure.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-cobol.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-coffee.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-coldfusion.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-csharp.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-css.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-curly.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-d.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-dart.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-diff.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-django.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-dot.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-ejs.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-erlang.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-forth.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-ftl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-glsl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-golang.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-groovy.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-haml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-handlebars.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-haskell.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-haxe.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-html.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-html_completions.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-html_ruby.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-ini.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-jack.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-jade.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-java.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-javascript.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-json.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-jsoniq.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-jsp.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-jsx.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-julia.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-latex.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-less.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-liquid.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-lisp.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-livescript.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-logiql.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-lsl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-lua.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-luapage.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-lucene.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-makefile.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-markdown.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-matlab.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-mushcode.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-mushcode_high_rules.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-mysql.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-nix.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-objectivec.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-ocaml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-pascal.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-perl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-pgsql.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-php.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-plain_text.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-powershell.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-prolog.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-properties.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-protobuf.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-python.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-r.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-rdoc.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-rhtml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-ruby.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-rust.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-sass.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-scad.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-scala.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-scheme.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-scss.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-sh.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-sjs.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-snippets.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-soy_template.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-space.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-sql.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-stylus.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-svg.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-tcl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-tex.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-text.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-textile.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-toml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-twig.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-typescript.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-vbscript.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-velocity.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-verilog.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-vhdl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-xml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-xquery.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/mode-yaml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/abap.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/actionscript.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/ada.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/asciidoc.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/assembly_x86.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/autohotkey.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/batchfile.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/c9search.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/c_cpp.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/clojure.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/cobol.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/coffee.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/coldfusion.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/csharp.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/css.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/curly.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/d.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/dart.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/diff.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/django.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/dot.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/ejs.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/erlang.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/forth.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/ftl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/glsl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/golang.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/groovy.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/haml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/handlebars.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/haskell.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/haxe.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/html.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/html_completions.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/html_ruby.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/ini.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/jack.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/jade.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/java.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/javascript.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/json.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/jsoniq.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/jsp.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/jsx.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/julia.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/latex.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/less.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/liquid.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/lisp.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/livescript.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/logiql.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/lsl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/lua.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/luapage.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/lucene.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/makefile.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/markdown.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/matlab.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/mushcode.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/mushcode_high_rules.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/mysql.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/nix.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/objectivec.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/ocaml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/pascal.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/perl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/pgsql.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/php.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/plain_text.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/powershell.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/prolog.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/properties.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/protobuf.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/python.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/r.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/rdoc.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/rhtml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/ruby.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/rust.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/sass.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/scad.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/scala.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/scheme.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/scss.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/sh.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/sjs.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/snippets.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/soy_template.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/space.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/sql.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/stylus.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/svg.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/tcl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/tex.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/text.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/textile.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/toml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/twig.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/typescript.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/vbscript.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/velocity.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/verilog.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/vhdl.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/xml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/xquery.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/snippets/yaml.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-ambiance.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-chaos.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-chrome.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-clouds.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-clouds_midnight.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-cobalt.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-crimson_editor.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-dawn.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-dreamweaver.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-eclipse.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-github.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-idle_fingers.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-kr.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-merbivore.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-merbivore_soft.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-mono_industrial.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-monokai.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-pastel_on_dark.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-solarized_dark.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-solarized_light.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-terminal.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-textmate.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-tomorrow.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-tomorrow_night.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-tomorrow_night_blue.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-tomorrow_night_bright.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-tomorrow_night_eighties.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-twilight.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-vibrant_ink.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/theme-xcode.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/worker-coffee.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/worker-css.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/worker-javascript.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/worker-json.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/worker-lua.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/worker-php.js (100%) rename webpages/public/{ => js}/ace-src-min-noconflict/worker-xquery.js (100%) delete mode 100644 webpages/public/lib/codemirror.css delete mode 100644 webpages/public/lib/codemirror.js diff --git a/coffee/components-manager.coffee b/coffee/components-manager.coffee index 627deaf..31640f8 100644 --- a/coffee/components-manager.coffee +++ b/coffee/components-manager.coffee @@ -13,16 +13,15 @@ Components Manager # - [Persistence](persistence.html) db = require './persistence' # - [Dynamic Modules](dynamic-modules.html) -dynmod = require './dynamic-modules' #TODO Rename to code-loader +dynmod = require './dynamic-modules' # - Node.js Modules: [fs](http://nodejs.org/api/fs.html), -# [vm](http://nodejs.org/api/vm.html) and -# [path](http://nodejs.org/api/path.html), +# [path](http://nodejs.org/api/path.html) and # [events](http://nodejs.org/api/events.html) fs = require 'fs' -vm = require 'vm' path = require 'path' events = require 'events' +eventEmitter = new events.EventEmitter() ### Module call @@ -33,29 +32,40 @@ Initializes the Components Manager and constructs a new Event Emitter. ### exports = module.exports = ( args ) => @log = args.logger - @ee = new events.EventEmitter() db args dynmod args module.exports ### -Add an event handler (eh) for a certain event (evt). -Current events are: +Add an event handler (eh) that listens for rules. -- init: as soon as an event handler is added, the init events are emitted for all existing rules. -- newRule: If a new rule is activated, the newRule event is emitted - -@public addListener ( *evt, eh* ) -@param {String} evt +@public addRuleListener ( *eh* ) @param {function} eh ### -exports.addListener = ( evt, eh ) => - @ee.addListener evt, eh - if evt is 'init' - db.getRules ( err, obj ) => - @ee.emit 'init', rule for id, rule of obj +exports.addRuleListener = ( eh ) => + eventEmitter.addListener 'rule', eh + + # Fetch all active rules per user + db.getAllActivatedRuleIdsPerUser ( err, objUsers ) -> + + # Go through all rules of each user + fGoThroughUsers = ( user, rules ) -> + + # Fetch the rules object for each rule in each user + fFetchRule = ( rule ) -> + db.getRule rule, ( err, oRule ) => + eventEmitter.emit 'rule', + event: 'init' + user: user + rule: JSON.parse oRule + + # Go through all rules for each user + fFetchRule rule for rule in rules + + # Go through each user + fGoThroughUsers user, rules for user, rules of objUsers ### Processes a user request coming through the request-handler. @@ -91,7 +101,7 @@ exports.processRequest = ( user, oReq, callback ) -> hasRequiredParams = ( arrParams, oPayload ) -> answ = code: 400 - message: "Your request didn't contain all necessary fields! id and params required" + message: "Your request didn't contain all necessary fields! Requires: #{ arrParams.join() }" return answ for param in arrParams when not oPayload[param] answ.code = 200 answ.message = 'All required properties found' @@ -125,6 +135,7 @@ getModuleParams = ( user, oPayload, dbMod, callback ) -> answ.message = oPayload callback answ + forgeModule = ( user, oPayload, dbMod, callback ) => answ = hasRequiredParams [ 'id', 'params', 'lang', 'data' ], oPayload if answ.code isnt 200 @@ -151,6 +162,11 @@ forgeModule = ( user, oPayload, dbMod, callback ) => callback answ commandFunctions = + get_public_key: ( user, oPayload, callback ) -> + callback + code: 200 + message: dynmod.getPublicKey() + get_event_pollers: ( user, oPayload, callback ) -> getModules user, oPayload, db.eventPollers, callback @@ -177,12 +193,12 @@ commandFunctions = # - event # - conditions # - actions - forge_rule: ( user, oPayload, callback ) => + forge_rule: ( user, oPayload, callback ) -> answ = hasRequiredParams [ 'id', 'event', 'conditions', 'actions' ], oPayload if answ.code isnt 200 callback answ else - db.getRule oPayload.id, ( err, oExisting ) => + db.getRule oPayload.id, ( err, oExisting ) -> if oExisting isnt null answ = code: 409 @@ -198,10 +214,14 @@ commandFunctions = db.linkRule rule.id, user.username db.activateRule rule.id, user.username if oPayload.event_params - db.eventPollers.storeUserParams ep.module, user.username, oPayload.event_params + epModId = rule.event.split( ' -> ' )[0] + db.eventPollers.storeUserParams epModId, user.username, oPayload.event_params arrParams = oPayload.action_params db.actionInvokers.storeUserParams id, user.username, JSON.stringify params for id, params of arrParams - @ee.emit 'newRule', strRule + eventEmitter.emit 'rule', + event: 'new' + user: user.username + rule: rule answ = code: 200 message: 'Rule stored and activated!' diff --git a/coffee/dynamic-modules.coffee b/coffee/dynamic-modules.coffee index 9d071fb..3ab7530 100644 --- a/coffee/dynamic-modules.coffee +++ b/coffee/dynamic-modules.coffee @@ -6,33 +6,17 @@ Dynamic Modules > with only a few allowed node.js modules. ### +# **Loads Modules:** + # - Node.js Modules: [vm](http://nodejs.org/api/vm.html) and # [events](http://nodejs.org/api/events.html) vm = require 'vm' needle = require 'needle' -# - External Modules: [coffee-script](http://coffeescript.org/) +# - External Modules: [coffee-script](http://coffeescript.org/), +# [cryptico](https://github.com/wwwtyro/cryptico) cs = require 'coffee-script' - - - -# cryptico = require 'my-cryptico' - -# conf = require path.join '..', 'js-coffee', 'config' -# conf opts - -# passPhrase = conf.getKeygenPassphrase() -# numBits = 1024 -# console.log passPhrase - -# oPrivateRSAkey = cryptico.generateRSAKey passPhrase, numBits -# strPublicKey = cryptico.publicKeyString oPrivateRSAkey -# plainText = "Matt, I need you to help me with my Starcraft strategy." -# oEncrypted = cryptico.encrypt plainText, strPublicKey - -# console.log oEncrypted.cipher -# oDecrypted = cryptico.decrypt oEncrypted.cipher, oPrivateRSAkey -# console.log oDecrypted.plaintext +cryptico = require 'my-cryptico' @@ -45,9 +29,27 @@ Initializes the dynamic module handler. ### exports = module.exports = ( args ) => @log = args.logger + # FIXME this can't come through the arguments + if not @strPublicKey and args[ 'keygen' ] + passPhrase = args[ 'keygen' ] + numBits = 1024 + @oPrivateRSAkey = cryptico.generateRSAKey passPhrase, numBits + @strPublicKey = cryptico.publicKeyString @oPrivateRSAkey + @log.info "Public Key generated: #{ @strPublicKey }" + + # plainText = "Matt, I need you to help me with my Starcraft strategy." + # oEncrypted = cryptico.encrypt plainText, strPublicKey + + # console.log oEncrypted.cipher + # oDecrypted = cryptico.decrypt oEncrypted.cipher, oPrivateRSAkey + # console.log oDecrypted.plaintext + module.exports +exports.getPublicKey = () => + @strPublicKey + ### Try to run a JS module from a string, together with the given parameters. If it is written in CoffeeScript we @@ -59,13 +61,12 @@ compile it first into JS. @param {Object} params @param {String} lang ### -exports.compileString = ( src, userId, moduleId, params, lang ) => +exports.compileString = ( src, userId, modId, params, lang ) => answ = code: 200 message: 'Successfully compiled' - # src = "'use strict;'\n" + src - if lang is '0' + if lang is 'CoffeeScript' try src = cs.compile src catch err @@ -74,7 +75,7 @@ exports.compileString = ( src, userId, moduleId, params, lang ) => err.location.first_line #FIXME not log but debug module is required to provide information to the user sandbox = - id: userId + '.' + moduleId + '.vm' + id: userId + '.' + modId + '.vm' params: params needle: needle log: console.log @@ -86,6 +87,8 @@ exports.compileString = ( src, userId, moduleId, params, lang ) => #kill the child each time? how to determine whether there's still a token in the module? try vm.runInNewContext src, sandbox, sandbox.id + # TODO We should investigate memory usage and garbage collection (global.gc())? + # Start Node with the flags —nouse_idle_notification and —expose_gc, and then when you want to run the GC, just call global.gc(). catch err answ.code = 400 answ.message = 'Loading Module failed: ' + err.message diff --git a/coffee/engine.coffee b/coffee/engine.coffee new file mode 100644 index 0000000..cd5b942 --- /dev/null +++ b/coffee/engine.coffee @@ -0,0 +1,193 @@ +### + +Engine +================== +> The heart of the WebAPI ECA System. The engine loads action invoker modules +> corresponding to active rules actions and invokes them if an appropriate event +> is retrieved. + +### + +# **Loads Modules:** + +# - [Persistence](persistence.html) +db = require './persistence' +# - [Dynamic Modules](dynamic-modules.html) +dynmod = require './dynamic-modules' + +listUserRules = {} +isRunning = false + +### +Module call +----------- +Initializes the Engine and starts polling the event queue for new events. + +@param {Object} args +### +exports = module.exports = ( args ) => + if not isRunning + isRunning = true + @log = args.logger + db args + dynmod args + pollQueue() + module.exports + + +### +Add an event handler (eh) that listens for rules. + +@public addRuleListener ( *eh* ) +@param {function} eh +### + +exports.internalEvent = ( evt ) => + if not listUserRules[evt.user] + listUserRules[evt.user] = + rules: {} + actions: {} + + oUser = listUserRules[evt.user] + oRule = evt.rule + if evt.event is 'new' or ( evt.event is 'init' and not oUser.rules[oRule.id] ) + oUser.rules[oRule.id] = oRule + updateActionModules oRule + +updateActionModules = ( oNewRule ) -> + + # Remove all action invoker modules that are not required anymore + fRemoveNotRequired = ( oUser ) -> + + # Check whether the action is still existing in a rule + fRequired = ( actionName ) -> + return true for nmRl, oRl of oUser.rules when actionName in oRl.actions + return false + + # Go thorugh all actions and check whether the action is still required + delete oUser.actions[action] for action of oUser.actions when not fRequired action + + fRemoveNotRequired oUser for name, oUser of listUserRules + + # Add action invoker modules that are not yet loaded + fAddRequired = ( userName, oUser ) -> + + # Check whether the action is existing in a rule and load if not + fCheckRules = ( oRule ) -> + + # Load the action invoker module if it was part of the updated rule or if it's new + fAddIfNewOrNotExisting = ( actionName ) -> + moduleName = (actionName.split ' -> ')[0] + if not oUser.actions[moduleName] or oRule.id is oNewRule.id + db.actionInvokers.getModule moduleName, ( err, obj ) -> + params = {} + res = dynmod.compileString obj.data, userName, moduleName, params, obj.lang + oUser.actions[moduleName] = res.module + + fAddIfNewOrNotExisting action for action in oRule.actions + + # Go thorugh all actions and check whether the action is still required + fCheckRules oRl for nmRl, oRl of oUser.rules + + fAddRequired userName, oUser for userName, oUser of listUserRules + +pollQueue = () -> + if isRunning + db.popEvent ( err, obj ) -> + if not err and obj + processEvent obj + setTimeout pollQueue, 50 #TODO adapt to load + +### +Checks whether all conditions of the rule are met by the event. + +@private validConditions ( *evt, rule* ) +@param {Object} evt +@param {Object} rule +### +validConditions = ( evt, rule ) -> + conds = rule.conditions + return false for prop of conds when not evt[prop] or evt[prop] isnt conds[prop] + return true + +### +Handles retrieved events. + +@private processEvent ( *evt* ) +@param {Object} evt +### +processEvent = ( evt ) -> + @log.info('EN | processing event: ' + evt.event + '(' + evt.eventid + ')'); + # var actions = checkEvent(evt); + # console.log('found actions to invoke:'); + # console.log(actions); + # for(var user in actions) { + # for(var module in actions[user]) { + # for(var i = 0; i < actions[user][module]['functions'].length; i++) { + # var act = { + # module: module, + # function: actions[user][module]['functions'][i] + # } + # invokeAction(evt, user, act); + +# */ +# function checkEvent(evt) { +# var actions = {}, tEvt; +# for(var user in listRules) { +# actions[user] = {}; +# for(var rule in listRules[user]) { +# //TODO this needs to get depth safe, not only data but eventually also +# // on one level above (eventid and other meta) +# tEvt = listRules[user][rule].event; +# if(tEvt.module + ' -> ' + tEvt.function === evt.event && validConditions(evt.payload, listRules[user][rule])) { +# log.info('EN', 'Rule "' + rule + '" fired'); +# var oAct = listRules[user][rule].actions; +# console.log (oAct); +# for(var module in oAct) { +# if(!actions[user][module]) { +# actions[user][module] = { +# functions: [] +# }; +# } +# for(var i = 0; i < oAct[module]['functions'].length; i++ ){ +# console.log ('processing action ' + i + ', ' + oAct[module]['functions'][i]); +# actions[user][module]['functions'].push(oAct[module]['functions'][i]); +# // if(actions[user].indexOf(arrAct[i]) === -1) actions[user].push(arrAct[i]); +# } +# } +# } +# } +# } +# return actions; +# } + +# /** +# * Invoke an action according to its type. +# * @param {Object} evt The event that invoked the action +# * @param {Object} action The action to be invoked +# */ +# function invokeAction( evt, user, action ) { +# console.log('invoking action'); +# var actionargs = {}; +# //FIXME internal events, such as loopback ha sno arrow +# //TODO this requires change. the module property will be the identifier +# // in the actions object (or shall we allow several times the same action?) +# console.log(action.module); +# console.log(listActionModules); +# var srvc = listActionModules[user][action.module]; +# console.log(srvc); +# if(srvc && srvc[action.function]) { +# //FIXME preprocessing not only on data +# //FIXME no preprocessing at all, why don't we just pass the whole event to the action?' +# // preprocessActionArguments(evt.payload, action.arguments, actionargs); +# try { +# if(srvc[action.function]) srvc[action.function](evt.payload); +# } catch(err) { +# log.error('EN', 'during action execution: ' + err); +# } +# } +# else log.info('EN', 'No api interface found for: ' + action.module); +# } + +exports.shutDown = () -> + isRunning = false \ No newline at end of file diff --git a/coffee/webapi-eca.coffee b/coffee/webapi-eca.coffee index 0b9dde6..9b178de 100644 --- a/coffee/webapi-eca.coffee +++ b/coffee/webapi-eca.coffee @@ -129,6 +129,9 @@ init = => # > Fetch the `http-port` argument args[ 'http-port' ] = parseInt argv.w || conf.getHttpPort() args[ 'db-port' ] = parseInt argv.d || conf.getDbPort() + + #FIXME this has to come from user input for security reasons: + args[ 'keygen' ] = conf.getKeygenPassphrase() @log.info 'RS | Initialzing DB' db args @@ -167,19 +170,9 @@ init = => # from engine and event poller @log.info 'RS | Initialzing module manager' cm args - cm.addListener 'init', ( evt ) -> - poller.send - event: 'init' - data: evt - cm.addListener 'newRule', ( evt ) -> - poller.send - event: 'newRule' - data: evt - cm.addListener 'init', ( evt ) -> - engine.internalEvent 'init', evt - cm.addListener 'newRule', ( evt ) -> - engine.internalEvent 'newRule', evt - + cm.addRuleListener engine.internalEvent + cm.addRuleListener ( evt ) -> poller.send evt + @log.info 'RS | Initialzing http listener' # The request handler passes certain requests to the module manager args[ 'request-service' ] = cm.processRequest @@ -195,7 +188,7 @@ Shuts down the server. shutDown = () => @log.warn 'RS | Received shut down command!' db?.shutDown() - engine?.shutDown() + engine.shutDown() # We need to call process.exit() since the express server in the http-listener # can't be stopped gracefully. Why would you stop this system anyways!?? process.exit() diff --git a/documentation/techdoc/techdoc.tex b/documentation/techdoc/techdoc.tex index 5e3740d..10acee6 100644 --- a/documentation/techdoc/techdoc.tex +++ b/documentation/techdoc/techdoc.tex @@ -112,7 +112,12 @@ and the engine fetches the same but modularized code from the npm repository via this also allows us to send privately stored modules and rules encrypted to the user, which will then see it decrypted after it arrived at the browser -\subsection{Request formats} +\subsection{Message Formats} +\subsubsection{Internal Events} +\subsubsubsection{Event Poller} +\subsubsubsection{Rule events} +\subsubsection{Client GUI} + \subsubsection{User commands} object that has a command as string and an optional payload as a stringified JSON diff --git a/js-coffee/components-manager.js b/js-coffee/components-manager.js index 61d2217..416fe2f 100644 --- a/js-coffee/components-manager.js +++ b/js-coffee/components-manager.js @@ -1,5 +1,4 @@ -// Generated by CoffeeScript 1.7.1 - +// Generated by CoffeeScript 1.6.3 /* Components Manager @@ -7,10 +6,12 @@ Components Manager > The components manager takes care of the dynamic JS modules and the rules. > Event Poller and Action Invoker modules are loaded as strings and stored in the database, > then compiled into node modules and rules and used in the engine and event poller. - */ +*/ + (function() { - var commandFunctions, db, dynmod, events, exports, forgeModule, fs, getModuleParams, getModules, hasRequiredParams, path, vm; + var commandFunctions, db, dynmod, eventEmitter, events, exports, forgeModule, fs, getModuleParams, getModules, hasRequiredParams, path, + _this = this; db = require('./persistence'); @@ -18,12 +19,11 @@ Components Manager fs = require('fs'); - vm = require('vm'); - path = require('path'); events = require('events'); + eventEmitter = new events.EventEmitter(); /* Module call @@ -31,48 +31,55 @@ Components Manager Initializes the Components Manager and constructs a new Event Emitter. @param {Object} args - */ + */ - exports = module.exports = (function(_this) { - return function(args) { - _this.log = args.logger; - _this.ee = new events.EventEmitter(); - db(args); - dynmod(args); - return module.exports; - }; - })(this); + exports = module.exports = function(args) { + _this.log = args.logger; + db(args); + dynmod(args); + return module.exports; + }; /* - Add an event handler (eh) for a certain event (evt). - Current events are: + Add an event handler (eh) that listens for rules. - - init: as soon as an event handler is added, the init events are emitted for all existing rules. - - newRule: If a new rule is activated, the newRule event is emitted - - @public addListener ( *evt, eh* ) - @param {String} evt + @public addRuleListener ( *eh* ) @param {function} eh - */ + */ - exports.addListener = (function(_this) { - return function(evt, eh) { - _this.ee.addListener(evt, eh); - if (evt === 'init') { - return db.getRules(function(err, obj) { - var id, rule, _results; - _results = []; - for (id in obj) { - rule = obj[id]; - _results.push(_this.ee.emit('init', rule)); - } - return _results; - }); + + exports.addRuleListener = function(eh) { + eventEmitter.addListener('rule', eh); + return db.getAllActivatedRuleIdsPerUser(function(err, objUsers) { + var fGoThroughUsers, rules, user, _results; + fGoThroughUsers = function(user, rules) { + var fFetchRule, rule, _i, _len, _results; + fFetchRule = function(rule) { + var _this = this; + return db.getRule(rule, function(err, oRule) { + return eventEmitter.emit('rule', { + event: 'init', + user: user, + rule: JSON.parse(oRule) + }); + }); + }; + _results = []; + for (_i = 0, _len = rules.length; _i < _len; _i++) { + rule = rules[_i]; + _results.push(fFetchRule(rule)); + } + return _results; + }; + _results = []; + for (user in objUsers) { + rules = objUsers[user]; + _results.push(fGoThroughUsers(user, rules)); } - }; - })(this); - + return _results; + }); + }; /* Processes a user request coming through the request-handler. @@ -87,7 +94,8 @@ Components Manager @param {Object} user @param {Object} oReq @param {function} callback - */ + */ + exports.processRequest = function(user, oReq, callback) { var dat, err; @@ -117,7 +125,7 @@ Components Manager var answ, param, _i, _len; answ = { code: 400, - message: "Your request didn't contain all necessary fields! id and params required" + message: "Your request didn't contain all necessary fields! Requires: " + (arrParams.join()) }; for (_i = 0, _len = arrParams.length; _i < _len; _i++) { param = arrParams[_i]; @@ -132,7 +140,8 @@ Components Manager getModules = function(user, oPayload, dbMod, callback) { return dbMod.getAvailableModuleIds(user.username, function(err, arrNames) { - var answReq, fGetFunctions, id, oRes, sem, _i, _len, _results; + var answReq, fGetFunctions, id, oRes, sem, _i, _len, _results, + _this = this; oRes = {}; answReq = function() { return callback({ @@ -144,18 +153,16 @@ Components Manager if (sem === 0) { return answReq(); } else { - fGetFunctions = (function(_this) { - return function(id) { - return dbMod.getModule(id, function(err, oModule) { - if (oModule) { - oRes[id] = JSON.parse(oModule.functions); - } - if (--sem === 0) { - return answReq(); - } - }); - }; - })(this); + fGetFunctions = function(id) { + return dbMod.getModule(id, function(err, oModule) { + if (oModule) { + oRes[id] = JSON.parse(oModule.functions); + } + if (--sem === 0) { + return answReq(); + } + }); + }; _results = []; for (_i = 0, _len = arrNames.length; _i < _len; _i++) { id = arrNames[_i]; @@ -179,45 +186,49 @@ Components Manager } }; - forgeModule = (function(_this) { - return function(user, oPayload, dbMod, callback) { - var answ; - answ = hasRequiredParams(['id', 'params', 'lang', 'data'], oPayload); - if (answ.code !== 200) { - return callback(answ); - } else { - return dbMod.getModule(oPayload.id, function(err, mod) { - var cm, funcs, id, name, src, _ref; - if (mod) { - answ.code = 409; - answ.message = 'Event Poller module name already existing: ' + oPayload.id; - } else { - src = oPayload.data; - cm = dynmod.compileString(src, user.username, oPayload.id, {}, oPayload.lang); - answ = cm.answ; - if (answ.code === 200) { - funcs = []; - _ref = cm.module; - for (name in _ref) { - id = _ref[name]; - funcs.push(name); - } - _this.log.info("CM | Storing new module with functions " + (funcs.join())); - answ.message = "Event Poller module successfully stored! Found following function(s): " + funcs; - oPayload.functions = JSON.stringify(funcs); - dbMod.storeModule(user.username, oPayload); - if (oPayload["public"] === 'true') { - dbMod.publish(oPayload.id); - } + forgeModule = function(user, oPayload, dbMod, callback) { + var answ; + answ = hasRequiredParams(['id', 'params', 'lang', 'data'], oPayload); + if (answ.code !== 200) { + return callback(answ); + } else { + return dbMod.getModule(oPayload.id, function(err, mod) { + var cm, funcs, id, name, src, _ref; + if (mod) { + answ.code = 409; + answ.message = 'Event Poller module name already existing: ' + oPayload.id; + } else { + src = oPayload.data; + cm = dynmod.compileString(src, user.username, oPayload.id, {}, oPayload.lang); + answ = cm.answ; + if (answ.code === 200) { + funcs = []; + _ref = cm.module; + for (name in _ref) { + id = _ref[name]; + funcs.push(name); + } + _this.log.info("CM | Storing new module with functions " + (funcs.join())); + answ.message = "Event Poller module successfully stored! Found following function(s): " + funcs; + oPayload.functions = JSON.stringify(funcs); + dbMod.storeModule(user.username, oPayload); + if (oPayload["public"] === 'true') { + dbMod.publish(oPayload.id); } } - return callback(answ); - }); - } - }; - })(this); + } + return callback(answ); + }); + } + }; commandFunctions = { + get_public_key: function(user, oPayload, callback) { + return callback({ + code: 200, + message: dynmod.getPublicKey() + }); + }, get_event_pollers: function(user, oPayload, callback) { return getModules(user, oPayload, db.eventPollers, callback); }, @@ -239,50 +250,53 @@ Components Manager get_rules: function(user, oPayload, callback) { return console.log('CM | Implement get_rules'); }, - forge_rule: (function(_this) { - return function(user, oPayload, callback) { - var answ; - answ = hasRequiredParams(['id', 'event', 'conditions', 'actions'], oPayload); - if (answ.code !== 200) { - return callback(answ); - } else { - return db.getRule(oPayload.id, function(err, oExisting) { - var arrParams, id, params, rule, strRule; - if (oExisting !== null) { - answ = { - code: 409, - message: 'Rule name already existing!' - }; - } else { - rule = { - id: oPayload.id, - event: oPayload.event, - conditions: oPayload.conditions, - actions: oPayload.actions - }; - strRule = JSON.stringify(rule); - db.storeRule(rule.id, strRule); - db.linkRule(rule.id, user.username); - db.activateRule(rule.id, user.username); - if (oPayload.event_params) { - db.eventPollers.storeUserParams(ep.module, user.username, oPayload.event_params); - } - arrParams = oPayload.action_params; - for (id in arrParams) { - params = arrParams[id]; - db.actionInvokers.storeUserParams(id, user.username, JSON.stringify(params)); - } - _this.ee.emit('newRule', strRule); - answ = { - code: 200, - message: 'Rule stored and activated!' - }; + forge_rule: function(user, oPayload, callback) { + var answ; + answ = hasRequiredParams(['id', 'event', 'conditions', 'actions'], oPayload); + if (answ.code !== 200) { + return callback(answ); + } else { + return db.getRule(oPayload.id, function(err, oExisting) { + var arrParams, epModId, id, params, rule, strRule; + if (oExisting !== null) { + answ = { + code: 409, + message: 'Rule name already existing!' + }; + } else { + rule = { + id: oPayload.id, + event: oPayload.event, + conditions: oPayload.conditions, + actions: oPayload.actions + }; + strRule = JSON.stringify(rule); + db.storeRule(rule.id, strRule); + db.linkRule(rule.id, user.username); + db.activateRule(rule.id, user.username); + if (oPayload.event_params) { + epModId = rule.event.split(' -> ')[0]; + db.eventPollers.storeUserParams(epModId, user.username, oPayload.event_params); } - return callback(answ); - }); - } - }; - })(this) + arrParams = oPayload.action_params; + for (id in arrParams) { + params = arrParams[id]; + db.actionInvokers.storeUserParams(id, user.username, JSON.stringify(params)); + } + eventEmitter.emit('rule', { + event: 'new', + user: user.username, + rule: rule + }); + answ = { + code: 200, + message: 'Rule stored and activated!' + }; + } + return callback(answ); + }); + } + } }; }).call(this); diff --git a/js-coffee/config.js b/js-coffee/config.js index e984a2e..4c48e7a 100644 --- a/js-coffee/config.js +++ b/js-coffee/config.js @@ -1,20 +1,20 @@ -// Generated by CoffeeScript 1.7.1 - +// Generated by CoffeeScript 1.6.3 /* Configuration ============= > Loads the configuration file and acts as an interface to it. - */ +*/ + (function() { - var exports, fetchProp, fs, loadConfigFile, path; + var exports, fetchProp, fs, loadConfigFile, path, + _this = this; fs = require('fs'); path = require('path'); - /* Module call ----------- @@ -24,23 +24,21 @@ Configuration be generated) and configPath for a custom configuration file path. @param {Object} args - */ + */ - exports = module.exports = (function(_this) { - return function(args) { - args = args != null ? args : {}; - if (args.nolog) { - _this.nolog = true; - } - if (args.configPath) { - loadConfigFile(args.configPath); - } else { - loadConfigFile(path.join('config', 'system.json')); - } - return module.exports; - }; - })(this); + exports = module.exports = function(args) { + args = args != null ? args : {}; + if (args.nolog) { + _this.nolog = true; + } + if (args.configPath) { + loadConfigFile(args.configPath); + } else { + loadConfigFile(path.join('config', 'system.json')); + } + return module.exports; + }; /* Tries to load a configuration file from the path relative to this module's parent folder. @@ -48,102 +46,97 @@ Configuration @private loadConfigFile @param {String} configPath - */ + */ - loadConfigFile = (function(_this) { - return function(configPath) { - var confProperties, e, prop, _i, _len; - _this.config = null; - confProperties = ['log', 'http-port', 'db-port']; - try { - _this.config = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', configPath))); - _this.isReady = true; - for (_i = 0, _len = confProperties.length; _i < _len; _i++) { - prop = confProperties[_i]; - if (!_this.config[prop]) { - _this.isReady = false; - } - } - if (!_this.isReady && !_this.nolog) { - return console.error("Missing property in config file, requires:\n" + (" - " + (confProperties.join("\n - ")))); - } - } catch (_error) { - e = _error; - _this.isReady = false; - if (!_this.nolog) { - return console.error("Failed loading config file: " + e.message); + + loadConfigFile = function(configPath) { + var confProperties, e, prop, _i, _len; + _this.config = null; + confProperties = ['log', 'http-port', 'db-port']; + try { + _this.config = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', configPath))); + _this.isReady = true; + for (_i = 0, _len = confProperties.length; _i < _len; _i++) { + prop = confProperties[_i]; + if (!_this.config[prop]) { + _this.isReady = false; } } - }; - })(this); - + if (!_this.isReady && !_this.nolog) { + return console.error("Missing property in config file, requires:\n" + (" - " + (confProperties.join("\n - ")))); + } + } catch (_error) { + e = _error; + _this.isReady = false; + if (!_this.nolog) { + return console.error("Failed loading config file: " + e.message); + } + } + }; /* Fetch a property from the configuration @private fetchProp( *prop* ) @param {String} prop - */ + */ - fetchProp = (function(_this) { - return function(prop) { - var _ref; - return (_ref = _this.config) != null ? _ref[prop] : void 0; - }; - })(this); + fetchProp = function(prop) { + var _ref; + return (_ref = _this.config) != null ? _ref[prop] : void 0; + }; /* ***Returns*** true if the config file is ready, else false @public isReady() - */ + */ - exports.isReady = (function(_this) { - return function() { - return _this.isReady; - }; - })(this); + exports.isReady = function() { + return _this.isReady; + }; /* ***Returns*** the HTTP port @public getHttpPort() - */ + */ + exports.getHttpPort = function() { return fetchProp('http-port'); }; - /* ***Returns*** the DB port* @public getDBPort() - */ + */ + exports.getDbPort = function() { return fetchProp('db-port'); }; - /* ***Returns*** the log conf object @public getLogConf() - */ + */ + exports.getLogConf = function() { return fetchProp('log'); }; - /* ***Returns*** the crypto key @public getCryptoKey() - */ + */ + exports.getKeygenPassphrase = function() { return fetchProp('keygen-passphrase'); diff --git a/js-coffee/dynamic-modules.js b/js-coffee/dynamic-modules.js index b2fbfbf..5192bd4 100644 --- a/js-coffee/dynamic-modules.js +++ b/js-coffee/dynamic-modules.js @@ -1,15 +1,16 @@ -// Generated by CoffeeScript 1.7.1 - +// Generated by CoffeeScript 1.6.3 /* Dynamic Modules =============== > Compiles CoffeeScript modules and loads JS modules in a VM, together > with only a few allowed node.js modules. - */ +*/ + (function() { - var cs, exports, needle, vm; + var cryptico, cs, exports, needle, vm, + _this = this; vm = require('vm'); @@ -17,6 +18,7 @@ Dynamic Modules cs = require('coffee-script'); + cryptico = require('my-cryptico'); /* Module call @@ -24,15 +26,25 @@ Dynamic Modules Initializes the dynamic module handler. @param {Object} args - */ + */ - exports = module.exports = (function(_this) { - return function(args) { - _this.log = args.logger; - return module.exports; - }; - })(this); + exports = module.exports = function(args) { + var numBits, passPhrase; + _this.log = args.logger; + if (!_this.strPublicKey && args['keygen']) { + passPhrase = args['keygen']; + numBits = 1024; + _this.oPrivateRSAkey = cryptico.generateRSAKey(passPhrase, numBits); + _this.strPublicKey = cryptico.publicKeyString(_this.oPrivateRSAkey); + _this.log.info("Public Key generated: " + _this.strPublicKey); + } + return module.exports; + }; + + exports.getPublicKey = function() { + return _this.strPublicKey; + }; /* Try to run a JS module from a string, together with the @@ -44,44 +56,43 @@ Dynamic Modules @param {String} id @param {Object} params @param {String} lang - */ + */ - exports.compileString = (function(_this) { - return function(src, userId, moduleId, params, lang) { - var answ, err, ret, sandbox; - answ = { - code: 200, - message: 'Successfully compiled' - }; - if (lang === '0') { - try { - src = cs.compile(src); - } catch (_error) { - err = _error; - answ.code = 400; - answ.message = 'Compilation of CoffeeScript failed at line ' + err.location.first_line; - } - } - sandbox = { - id: userId + '.' + moduleId + '.vm', - params: params, - needle: needle, - log: console.log, - exports: {} - }; + + exports.compileString = function(src, userId, modId, params, lang) { + var answ, err, ret, sandbox; + answ = { + code: 200, + message: 'Successfully compiled' + }; + if (lang === 'CoffeeScript') { try { - vm.runInNewContext(src, sandbox, sandbox.id); + src = cs.compile(src); } catch (_error) { err = _error; answ.code = 400; - answ.message = 'Loading Module failed: ' + err.message; + answ.message = 'Compilation of CoffeeScript failed at line ' + err.location.first_line; } - ret = { - answ: answ, - module: sandbox.exports - }; - return ret; + } + sandbox = { + id: userId + '.' + modId + '.vm', + params: params, + needle: needle, + log: console.log, + exports: {} }; - })(this); + try { + vm.runInNewContext(src, sandbox, sandbox.id); + } catch (_error) { + err = _error; + answ.code = 400; + answ.message = 'Loading Module failed: ' + err.message; + } + ret = { + answ: answ, + module: sandbox.exports + }; + return ret; + }; }).call(this); diff --git a/js-coffee/engine.js b/js-coffee/engine.js index 9365088..8bd0866 100644 --- a/js-coffee/engine.js +++ b/js-coffee/engine.js @@ -1,244 +1,183 @@ -'use strict'; +// Generated by CoffeeScript 1.6.3 +/* -var path = require('path'), - regex = /\$X\.[\w\.\[\]]*/g, // find properties of $X - listRules = {}, - listActionModules = {}, - isRunning = true, - dynmod = require('./dynamic-modules'), - db = require('./persistence'), log; - -exports = module.exports = function( args ) { - log = args.logger; - db( args); - dynmod(args); - pollQueue(); - return module.exports; -}; - -var updateActionModules = function() { - for ( var user in listRules ) { - if(!listActionModules[user]) listActionModules[user] = {}; - for ( var rule in listRules[user] ) { - var actions = listRules[user][rule].actions; - console.log(actions); - for ( var module in actions ){ - for ( var i = 0; i < actions[module]['functions'].length; i++ ){ - db.actionInvokers.getModule(module, function( err, objAM ){ - db.actionInvokers.getUserParams(module, user, function( err, objParams ) { - console.log (objAM); - - //FIXME am name is called 'actions'??? - // if(objParams) { //TODO we don't need them for all modules - var answ = dynmod.compileString(objAM.code, objAM.actions + "_" + user, objParams, objAM.lang); - console.log('answ'); - console.log(answ); - listActionModules[user][module] = answ.module; - console.log('loaded ' + user + ': ' + module); - console.log(listActionModules); - // } - }); - }); - } - } - } - } -}; - -exports.internalEvent = function( evt, data ) { - try { - // TODO do we need to determine init from newRule? - console.log (evt); - console.log (data); - var obj = JSON.parse( data ); - db.getRuleActivatedUsers(obj.id, function ( err, arrUsers ) { - console.log (arrUsers); - for(var i = 0; i < arrUsers.length; i++) { - if( !listRules[arrUsers[i]]) listRules[arrUsers[i]] = {}; - listRules[arrUsers[i]][obj.id] = obj; - updateActionModules(); - } - }); - } catch( err ) { - console.log( err ); - } -}; +Engine +================== +> The heart of the WebAPI ECA System. The engine loads action invoker modules +> corresponding to active rules actions and invokes them if an appropriate event +> is retrieved. +*/ -function pollQueue() { - if(isRunning) { - db.popEvent(function (err, obj) { - if(!err && obj) { - processEvent(obj); - } - setTimeout(pollQueue, 50); //TODO adapt to load - }); - } -} +(function() { + var db, dynmod, exports, isRunning, listUserRules, pollQueue, processEvent, updateActionModules, validConditions, + _this = this, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; -/** - * Handles correctly posted events - * @param {Object} evt The event object - */ -function processEvent(evt) { - log.info('EN', 'processing event: ' + evt.event + '(' + evt.eventid + ')'); - var actions = checkEvent(evt); - console.log('found actions to invoke:'); - console.log(actions); - for(var user in actions) { - for(var module in actions[user]) { - for(var i = 0; i < actions[user][module]['functions'].length; i++) { - var act = { - module: module, - function: actions[user][module]['functions'][i] - } - invokeAction(evt, user, act); - } - } - } -} + db = require('./persistence'); -/** - FIXME merge with processEvent + dynmod = require('./dynamic-modules'); - * Check an event against the rules repository and return the actions - * if the conditons are met. - * @param {Object} evt the event to check - */ -function checkEvent(evt) { - var actions = {}, tEvt; - for(var user in listRules) { - actions[user] = {}; - for(var rule in listRules[user]) { - //TODO this needs to get depth safe, not only data but eventually also - // on one level above (eventid and other meta) - tEvt = listRules[user][rule].event; - if(tEvt.module + ' -> ' + tEvt.function === evt.event && validConditions(evt.payload, listRules[user][rule])) { - log.info('EN', 'Rule "' + rule + '" fired'); - var oAct = listRules[user][rule].actions; - console.log (oAct); - for(var module in oAct) { - if(!actions[user][module]) { - actions[user][module] = { - functions: [] - }; - } - for(var i = 0; i < oAct[module]['functions'].length; i++ ){ - console.log ('processing action ' + i + ', ' + oAct[module]['functions'][i]); - actions[user][module]['functions'].push(oAct[module]['functions'][i]); - // if(actions[user].indexOf(arrAct[i]) === -1) actions[user].push(arrAct[i]); - } - } - } - } - } - return actions; -} + listUserRules = {}; -// { -// "event": "emailyak -> newMail", -// "payload": { -// "TextBody": "hello" -// } -// } - -// exports.sendMail = ( args ) -> -// url = 'https://api.emailyak.com/v1/ps1g59ndfcwg10w/json/send/email/' - -// data = -// FromAddress: 'tester@mscliveweb.simpleyak.com' -// ToAddress: 'dominic.bosch.db@gmail.com' -// TextBody: 'test' - -// needle.post url, JSON.stringify( data ), {json: true}, ( err, resp, body ) -> -// log err -// log body -/** - * Checks whether all conditions of the rule are met by the event. - * @param {Object} evt the event to check - * @param {Object} rule the rule with its conditions - */ -function validConditions(evt, rule) { - for(var property in rule.conditions){ - if(!evt[property] || evt[property] != rule.condition[property]) return false; - } - return true; -} - -/** - * Invoke an action according to its type. - * @param {Object} evt The event that invoked the action - * @param {Object} action The action to be invoked - */ -function invokeAction( evt, user, action ) { - console.log('invoking action'); - var actionargs = {}; - //FIXME internal events, such as loopback ha sno arrow - //TODO this requires change. the module property will be the identifier - // in the actions object (or shall we allow several times the same action?) - console.log(action.module); - console.log(listActionModules); - var srvc = listActionModules[user][action.module]; - console.log(srvc); - if(srvc && srvc[action.function]) { - //FIXME preprocessing not only on data - //FIXME no preprocessing at all, why don't we just pass the whole event to the action?' - // preprocessActionArguments(evt.payload, action.arguments, actionargs); - try { - if(srvc[action.function]) srvc[action.function](evt.payload); - } catch(err) { - log.error('EN', 'during action execution: ' + err); - } - } - else log.info('EN', 'No api interface found for: ' + action.module); -} - -// /** -// * Action properties may contain event properties which need to be resolved beforehand. -// * @param {Object} evt The event whose property values can be used in the rules action -// * @param {Object} act The rules action arguments -// * @param {Object} res The object to be used to enter the new properties -// */ -// function preprocessActionArguments(evt, act, res) { -// for(var prop in act) { -// /* -// * If the property is an object itself we go into recursion -// */ -// if(typeof act[prop] === 'object') { -// res[prop] = {}; -// preprocessActionArguments(evt, act[prop], res[prop]); -// } -// else { -// var txt = act[prop]; -// var arr = txt.match(regex); - -// * If rules action property holds event properties we resolve them and -// * replace the original action property - -// // console.log(evt); -// if(arr) { -// for(var i = 0; i < arr.length; i++) { -// /* -// * The first three characters are '$X.', followed by the property -// */ -// var actionProp = arr[i].substring(3).toLowerCase(); -// // console.log(actionProp); -// for(var eprop in evt) { -// // our rules language doesn't care about upper or lower case -// if(eprop.toLowerCase() === actionProp) { -// txt = txt.replace(arr[i], evt[eprop]); -// } -// } -// txt = txt.replace(arr[i], '[property not available]'); -// } -// } -// res[prop] = txt; -// } -// } -// } - -exports.shutDown = function() { - if(log) log.info('EN', 'Shutting down Poller and DB Link'); isRunning = false; - if(db) db.shutDown(); -}; + + /* + Module call + ----------- + Initializes the Engine and starts polling the event queue for new events. + + @param {Object} args + */ + + + exports = module.exports = function(args) { + if (!isRunning) { + isRunning = true; + _this.log = args.logger; + db(args); + dynmod(args); + pollQueue(); + return module.exports; + } + }; + + /* + Add an event handler (eh) that listens for rules. + + @public addRuleListener ( *eh* ) + @param {function} eh + */ + + + exports.internalEvent = function(evt) { + var oRule, oUser; + if (!listUserRules[evt.user]) { + listUserRules[evt.user] = { + rules: {}, + actions: {} + }; + } + oUser = listUserRules[evt.user]; + oRule = evt.rule; + if (evt.event === 'new' || (evt.event === 'init' && !oUser.rules[oRule.id])) { + oUser.rules[oRule.id] = oRule; + return updateActionModules(oRule); + } + }; + + updateActionModules = function(oNewRule) { + var fAddRequired, fRemoveNotRequired, name, oUser, userName, _results; + fRemoveNotRequired = function(oUser) { + var action, fRequired, _results; + fRequired = function(actionName) { + var nmRl, oRl, _ref; + _ref = oUser.rules; + for (nmRl in _ref) { + oRl = _ref[nmRl]; + if (__indexOf.call(oRl.actions, actionName) >= 0) { + return true; + } + } + return false; + }; + _results = []; + for (action in oUser.actions) { + if (!fRequired(action)) { + _results.push(delete oUser.actions[action]); + } + } + return _results; + }; + for (name in listUserRules) { + oUser = listUserRules[name]; + fRemoveNotRequired(oUser); + } + fAddRequired = function(userName, oUser) { + var fCheckRules, nmRl, oRl, _ref, _results; + fCheckRules = function(oRule) { + var action, fAddIfNewOrNotExisting, _i, _len, _ref, _results; + fAddIfNewOrNotExisting = function(actionName) { + var moduleName; + moduleName = (actionName.split(' -> '))[0]; + if (!oUser.actions[moduleName] || oRule.id === oNewRule.id) { + return db.actionInvokers.getModule(moduleName, function(err, obj) { + var params, res; + params = {}; + res = dynmod.compileString(obj.data, userName, moduleName, params, obj.lang); + return oUser.actions[moduleName] = res.module; + }); + } + }; + _ref = oRule.actions; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + action = _ref[_i]; + _results.push(fAddIfNewOrNotExisting(action)); + } + return _results; + }; + _ref = oUser.rules; + _results = []; + for (nmRl in _ref) { + oRl = _ref[nmRl]; + _results.push(fCheckRules(oRl)); + } + return _results; + }; + _results = []; + for (userName in listUserRules) { + oUser = listUserRules[userName]; + _results.push(fAddRequired(userName, oUser)); + } + return _results; + }; + + pollQueue = function() { + if (isRunning) { + return db.popEvent(function(err, obj) { + if (!err && obj) { + processEvent(obj); + } + return setTimeout(pollQueue, 50); + }); + } + }; + + /* + Checks whether all conditions of the rule are met by the event. + + @private validConditions ( *evt, rule* ) + @param {Object} evt + @param {Object} rule + */ + + + validConditions = function(evt, rule) { + var conds, prop; + conds = rule.conditions; + for (prop in conds) { + if (!evt[prop] || evt[prop] !== conds[prop]) { + return false; + } + } + return true; + }; + + /* + Handles retrieved events. + + @private processEvent ( *evt* ) + @param {Object} evt + */ + + + processEvent = function(evt) { + return this.log.info('EN | processing event: ' + evt.event + '(' + evt.eventid + ')'); + }; + + exports.shutDown = function() { + return isRunning = false; + }; + +}).call(this); diff --git a/js-coffee/event-poller.js b/js-coffee/event-poller.js index 2c997d2..12cd80f 100644 --- a/js-coffee/event-poller.js +++ b/js-coffee/event-poller.js @@ -15,6 +15,7 @@ var logger = require('./logging'), //TODO allow different polling intervals (a wrapper together with settimeout per to be polled could be an easy and solution) +// FIXME Eventually we don't even need to pass these arguments because they are anyways cached even over child_processes function init() { if(process.argv.length < 7){ @@ -33,7 +34,6 @@ function init() { var args = { logger: log }; (ml = require('./components-manager'))(args); (db = require('./persistence'))(args); - initAdminCommands(); initMessageActions(); pollLoop(); log.info('Event Poller instantiated'); @@ -106,31 +106,24 @@ function initMessageActions() { } }; - //TODO this goes into module_manager, this will receive notification about - // new loaded/stored event modules and fetch them from the db - listMessageActions['cmd'] = function(args) { - var func = listAdminCommands[args[1]]; - if(typeof(func) === 'function') func(args); - }; process.on('message', function( msg ) { - - console.log (JSON.parse(msg.data)); + console.log( 'EVENT POLLER GOT MESSAGE!'); + console.log( typeof msg); + console.log(msg); // var arrProps = obj .split('|'); // if(arrProps.length < 2) log.error('EP', 'too few parameter in message!'); // else { // var func = listMessageActions[arrProps[0]]; // if(func) func(arrProps); // } + console.log('EP internal event handled'); }); // very important so the process doesnt linger on when the paren process is killed process.on('disconnect', shutDown); } -function initAdminCommands() { - listAdminCommands['shutdown'] = shutDown -} function checkRemotes() { for(var prop in listPoll) { diff --git a/js-coffee/persistence.js b/js-coffee/persistence.js index 6ed7f23..0e141a1 100644 --- a/js-coffee/persistence.js +++ b/js-coffee/persistence.js @@ -1,5 +1,4 @@ -// Generated by CoffeeScript 1.7.1 - +// Generated by CoffeeScript 1.6.3 /* Persistence @@ -19,58 +18,55 @@ Persistence > 'action-invokers' and then stored in the db with the key 'action-invoker:' + ID > (e.g. action-invoker:probinder). > - */ +*/ + (function() { var IndexedModules, exports, getSetRecords, redis, replyHandler, + _this = this, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; redis = require('redis'); - /* Module call ----------- Initializes the DB connection with the given `db-port` property in the `args` object. @param {Object} args - */ + */ - exports = module.exports = (function(_this) { - return function(args) { - if (!_this.db) { - if (!args['db-port']) { - args['db-port'] = 6379; - } - _this.log = args.logger; - exports.eventPollers = new IndexedModules('event-poller', _this.log); - exports.actionInvokers = new IndexedModules('action-invoker', _this.log); - return exports.initPort(args['db-port']); + + exports = module.exports = function(args) { + if (!_this.db) { + if (!args['db-port']) { + args['db-port'] = 6379; } - }; - })(this); + _this.log = args.logger; + exports.eventPollers = new IndexedModules('event-poller', _this.log); + exports.actionInvokers = new IndexedModules('action-invoker', _this.log); + return exports.initPort(args['db-port']); + } + }; - exports.initPort = (function(_this) { - return function(port) { - var _ref; - _this.connRefused = false; - if ((_ref = _this.db) != null) { - _ref.quit(); + exports.initPort = function(port) { + var _ref; + _this.connRefused = false; + if ((_ref = _this.db) != null) { + _ref.quit(); + } + _this.db = redis.createClient(port, 'localhost', { + connect_timeout: 2000 + }); + _this.db.on('error', function(err) { + if (err.message.indexOf('ECONNREFUSED') > -1) { + _this.connRefused = true; + return _this.log.error(err, 'DB | Wrong port?'); } - _this.db = redis.createClient(port, 'localhost', { - connect_timeout: 2000 - }); - _this.db.on('error', function(err) { - if (err.message.indexOf('ECONNREFUSED') > -1) { - _this.connRefused = true; - return _this.log.error(err, 'DB | Wrong port?'); - } - }); - exports.eventPollers.setDB(_this.db); - return exports.actionInvokers.setDB(_this.db); - }; - })(this); - + }); + exports.eventPollers.setDB(_this.db); + return exports.actionInvokers.setDB(_this.db); + }; /* Checks whether the db is connected and passes either an error on failure after @@ -78,114 +74,104 @@ Persistence @public isConnected( *cb* ) @param {function} cb - */ + */ - exports.isConnected = (function(_this) { - return function(cb) { - var fCheckConnection, numAttempts; - if (!_this.db) { - return cb(new Error('DB | DB initialization did not occur or failed miserably!')); + + exports.isConnected = function(cb) { + var fCheckConnection, numAttempts; + if (!_this.db) { + return cb(new Error('DB | DB initialization did not occur or failed miserably!')); + } else { + if (_this.db.connected) { + return cb(); } else { - if (_this.db.connected) { - return cb(); - } else { - numAttempts = 0; - fCheckConnection = function() { - var _ref; - if (_this.connRefused) { - if ((_ref = _this.db) != null) { - _ref.quit(); - } - return cb(new Error('DB | Connection refused! Wrong port?')); - } else { - if (_this.db.connected) { - _this.log.info('DB | Successfully connected to DB!'); - return cb(); - } else if (numAttempts++ < 10) { - return setTimeout(fCheckConnection, 100); - } else { - return cb(new Error('DB | Connection to DB failed!')); - } + numAttempts = 0; + fCheckConnection = function() { + var _ref; + if (_this.connRefused) { + if ((_ref = _this.db) != null) { + _ref.quit(); } - }; - return setTimeout(fCheckConnection, 100); - } + return cb(new Error('DB | Connection refused! Wrong port?')); + } else { + if (_this.db.connected) { + _this.log.info('DB | Successfully connected to DB!'); + return cb(); + } else if (numAttempts++ < 10) { + return setTimeout(fCheckConnection, 100); + } else { + return cb(new Error('DB | Connection to DB failed!')); + } + } + }; + return setTimeout(fCheckConnection, 100); } - }; - })(this); - + } + }; /* Abstracts logging for simple action replies from the DB. @private replyHandler( *action* ) @param {String} action - */ + */ - replyHandler = (function(_this) { - return function(action) { - return function(err, reply) { - if (err) { - return _this.log.warn(err, "during '" + action + "'"); - } else { - return _this.log.info("DB | " + action + ": " + reply); - } - }; + + replyHandler = function(action) { + return function(err, reply) { + if (err) { + return _this.log.warn(err, "during '" + action + "'"); + } else { + return _this.log.info("DB | " + action + ": " + reply); + } }; - })(this); - + }; /* Push an event into the event queue. @public pushEvent( *oEvent* ) @param {Object} oEvent - */ + */ - exports.pushEvent = (function(_this) { - return function(oEvent) { - if (oEvent) { - _this.log.info("DB | Event pushed into the queue: '" + oEvent.eventid + "'"); - return _this.db.rpush('event_queue', JSON.stringify(oEvent)); - } else { - return _this.log.warn('DB | Why would you give me an empty event...'); - } - }; - })(this); + exports.pushEvent = function(oEvent) { + if (oEvent) { + _this.log.info("DB | Event pushed into the queue: '" + oEvent.eventid + "'"); + return _this.db.rpush('event_queue', JSON.stringify(oEvent)); + } else { + return _this.log.warn('DB | Why would you give me an empty event...'); + } + }; /* Pop an event from the event queue and pass it to cb(err, obj). @public popEvent( *cb* ) @param {function} cb - */ + */ - exports.popEvent = (function(_this) { - return function(cb) { - var makeObj; - makeObj = function(pcb) { - return function(err, obj) { - return pcb(err, JSON.parse(obj)); - }; + + exports.popEvent = function(cb) { + var makeObj; + makeObj = function(pcb) { + return function(err, obj) { + return pcb(err, JSON.parse(obj)); }; - return _this.db.lpop('event_queue', makeObj(cb)); }; - })(this); - + return _this.db.lpop('event_queue', makeObj(cb)); + }; /* Purge the event queue. @public purgeEventQueue() - */ + */ - exports.purgeEventQueue = (function(_this) { - return function() { - return _this.db.del('event_queue', replyHandler('purging event queue')); - }; - })(this); + exports.purgeEventQueue = function() { + return _this.db.del('event_queue', replyHandler('purging event queue')); + }; /* Fetches all linked data set keys from a linking set, fetches the single @@ -197,51 +183,50 @@ Persistence per set entry @param {function} cb the callback(err, obj) function that receives all the retrieved data or an error - */ + */ - getSetRecords = (function(_this) { - return function(set, fSingle, cb) { - _this.log.info("DB | Fetching set records: '" + set + "'"); - return _this.db.smembers(set, function(err, arrReply) { - var fCallback, objReplies, reply, semaphore, _i, _len, _results; - if (err) { - _this.log.warn(err, "DB | fetching '" + set + "'"); - return cb(err); - } else if (arrReply.length === 0) { - return cb(); - } else { - semaphore = arrReply.length; - objReplies = {}; - setTimeout(function() { - if (semaphore > 0) { - return cb(new Error("Timeout fetching '" + set + "'")); - } - }, 2000); - fCallback = function(prop) { - return function(err, data) { - --semaphore; - if (err) { - _this.log.warn(err, "DB | fetching single element: '" + prop + "'"); - } else if (!data) { - _this.log.warn(new Error("Empty key in DB: '" + prop + "'")); - } else { - objReplies[prop] = data; - } - if (semaphore === 0) { - return cb(null, objReplies); - } - }; - }; - _results = []; - for (_i = 0, _len = arrReply.length; _i < _len; _i++) { - reply = arrReply[_i]; - _results.push(fSingle(reply, fCallback(reply))); + + getSetRecords = function(set, fSingle, cb) { + _this.log.info("DB | Fetching set records: '" + set + "'"); + return _this.db.smembers(set, function(err, arrReply) { + var fCallback, objReplies, reply, semaphore, _i, _len, _results; + if (err) { + _this.log.warn(err, "DB | fetching '" + set + "'"); + return cb(err); + } else if (arrReply.length === 0) { + return cb(); + } else { + semaphore = arrReply.length; + objReplies = {}; + setTimeout(function() { + if (semaphore > 0) { + return cb(new Error("Timeout fetching '" + set + "'")); } - return _results; + }, 2000); + fCallback = function(prop) { + return function(err, data) { + --semaphore; + if (err) { + _this.log.warn(err, "DB | fetching single element: '" + prop + "'"); + } else if (!data) { + _this.log.warn(new Error("Empty key in DB: '" + prop + "'")); + } else { + objReplies[prop] = data; + } + if (semaphore === 0) { + return cb(null, objReplies); + } + }; + }; + _results = []; + for (_i = 0, _len = arrReply.length; _i < _len; _i++) { + reply = arrReply[_i]; + _results.push(fSingle(reply, fCallback(reply))); } - }); - }; - })(this); + return _results; + } + }); + }; IndexedModules = (function() { function IndexedModules(setname, log) { @@ -270,14 +255,14 @@ Persistence return this.log.info("DB | (IdxedMods) Registered new DB connection for '" + this.setname + "'"); }; - /* Stores a module and links it to the user. @private storeModule( *userId, oModule* ) @param {String} userId @param {object} oModule - */ + */ + IndexedModules.prototype.storeModule = function(userId, oModule) { this.log.info("DB | (IdxedMods) " + this.setname + ".storeModule( " + userId + ", oModule )"); @@ -334,24 +319,22 @@ Persistence }; IndexedModules.prototype.deleteModule = function(mId) { + var _this = this; this.log.info("DB | (IdxedMods) " + this.setname + ".deleteModule( " + mId + " )"); this.db.srem("" + this.setname + "s", mId, replyHandler("srem '" + mId + "' from " + this.setname + "s")); this.db.del("" + this.setname + ":" + mId, replyHandler("del of '" + this.setname + ":" + mId + "'")); this.unpublish(mId); - return this.db.smembers("" + this.setname + ":" + mId + ":users", (function(_this) { - return function(err, obj) { - var userId, _i, _len, _results; - _results = []; - for (_i = 0, _len = obj.length; _i < _len; _i++) { - userId = obj[_i]; - _results.push(_this.unlinkModule(mId, userId)); - } - return _results; - }; - })(this)); + return this.db.smembers("" + this.setname + ":" + mId + ":users", function(err, obj) { + var userId, _i, _len, _results; + _results = []; + for (_i = 0, _len = obj.length; _i < _len; _i++) { + userId = obj[_i]; + _results.push(_this.unlinkModule(mId, userId)); + } + return _results; + }); }; - /* Stores user params for a module. They are expected to be RSA encrypted with helps of the provided cryptico JS library and will only be decrypted right before the module is loaded! @@ -360,7 +343,8 @@ Persistence @param {String} mId @param {String} userId @param {object} encData - */ + */ + IndexedModules.prototype.storeUserParams = function(mId, userId, encData) { this.log.info("DB | (IdxedMods) " + this.setname + ".storeUserParams( " + mId + ", " + userId + ", encData )"); @@ -390,10 +374,9 @@ Persistence })(); - /* - *# Rules - */ + ## Rules + */ /* @@ -402,45 +385,39 @@ Persistence @public getRule( *ruleId, cb* ) @param {String} ruleId @param {function} cb - */ + */ - exports.getRule = (function(_this) { - return function(ruleId, cb) { - _this.log.info("DB | getRule: '" + ruleId + "'"); - return _this.db.get("rule:" + ruleId, cb); - }; - })(this); + exports.getRule = function(ruleId, cb) { + _this.log.info("DB | getRule: '" + ruleId + "'"); + return _this.db.get("rule:" + ruleId, cb); + }; /* Fetch all rules and pass them to cb(err, obj). @public getRules( *cb* ) @param {function} cb - */ + */ - exports.getRules = (function(_this) { - return function(cb) { - _this.log.info('DB | Fetching all Rules'); - return getSetRecords('rules', exports.getRule, cb); - }; - })(this); + exports.getRules = function(cb) { + _this.log.info('DB | Fetching all Rules'); + return getSetRecords('rules', exports.getRule, cb); + }; /* Fetch all rule IDs and hand it to cb(err, obj). @public getRuleIds( *cb* ) @param {function} cb - */ + */ - exports.getRuleIds = (function(_this) { - return function(cb) { - _this.log.info('DB | Fetching all Rule IDs'); - return _this.db.smembers('rules', cb); - }; - })(this); + exports.getRuleIds = function(cb) { + _this.log.info('DB | Fetching all Rule IDs'); + return _this.db.smembers('rules', cb); + }; /* Store a string representation of a rule in the DB. @@ -448,16 +425,14 @@ Persistence @public storeRule( *ruleId, data* ) @param {String} ruleId @param {String} data - */ + */ - exports.storeRule = (function(_this) { - return function(ruleId, data) { - _this.log.info("DB | storeRule: '" + ruleId + "'"); - _this.db.sadd('rules', "" + ruleId, replyHandler("storing rule key '" + ruleId + "'")); - return _this.db.set("rule:" + ruleId, data, replyHandler("storing rule '" + ruleId + "'")); - }; - })(this); + exports.storeRule = function(ruleId, data) { + _this.log.info("DB | storeRule: '" + ruleId + "'"); + _this.db.sadd('rules', "" + ruleId, replyHandler("storing rule key '" + ruleId + "'")); + return _this.db.set("rule:" + ruleId, data, replyHandler("storing rule '" + ruleId + "'")); + }; /* Delete a string representation of a rule. @@ -465,42 +440,40 @@ Persistence @public deleteRule( *ruleId, userId* ) @param {String} ruleId @param {String} userId - */ + */ - exports.deleteRule = (function(_this) { - return function(ruleId) { - _this.log.info("DB | deleteRule: '" + ruleId + "'"); - _this.db.srem("rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "'")); - _this.db.del("rule:" + ruleId, replyHandler("Deleting rule '" + ruleId + "'")); - _this.db.smembers("rule:" + ruleId + ":users", function(err, obj) { - var delLinkedUserRule, id, _i, _len, _results; - delLinkedUserRule = function(userId) { - return _this.db.srem("user:" + userId + ":rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "' in linked user '" + userId + "'")); - }; - _results = []; - for (_i = 0, _len = obj.length; _i < _len; _i++) { - id = obj[_i]; - _results.push(delLinkedUserRule(id)); - } - return _results; - }); - _this.db.del("rule:" + ruleId + ":users", replyHandler("Deleting rule '" + ruleId + "' users")); - _this.db.smembers("rule:" + ruleId + ":active-users", function(err, obj) { - var delActiveUserRule, id, _i, _len, _results; - delActiveUserRule = function(userId) { - return _this.db.srem("user:" + userId + ":active-rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "' in active user '" + userId + "'")); - }; - _results = []; - for (_i = 0, _len = obj.length; _i < _len; _i++) { - id = obj[_i]; - _results.push(delActiveUserRule(id)); - } - return _results; - }); - return _this.db.del("rule:" + ruleId + ":active-users", replyHandler("Deleting rule '" + ruleId + "' active users")); - }; - })(this); + exports.deleteRule = function(ruleId) { + _this.log.info("DB | deleteRule: '" + ruleId + "'"); + _this.db.srem("rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "'")); + _this.db.del("rule:" + ruleId, replyHandler("Deleting rule '" + ruleId + "'")); + _this.db.smembers("rule:" + ruleId + ":users", function(err, obj) { + var delLinkedUserRule, id, _i, _len, _results; + delLinkedUserRule = function(userId) { + return _this.db.srem("user:" + userId + ":rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "' in linked user '" + userId + "'")); + }; + _results = []; + for (_i = 0, _len = obj.length; _i < _len; _i++) { + id = obj[_i]; + _results.push(delLinkedUserRule(id)); + } + return _results; + }); + _this.db.del("rule:" + ruleId + ":users", replyHandler("Deleting rule '" + ruleId + "' users")); + _this.db.smembers("rule:" + ruleId + ":active-users", function(err, obj) { + var delActiveUserRule, id, _i, _len, _results; + delActiveUserRule = function(userId) { + return _this.db.srem("user:" + userId + ":active-rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "' in active user '" + userId + "'")); + }; + _results = []; + for (_i = 0, _len = obj.length; _i < _len; _i++) { + id = obj[_i]; + _results.push(delActiveUserRule(id)); + } + return _results; + }); + return _this.db.del("rule:" + ruleId + ":active-users", replyHandler("Deleting rule '" + ruleId + "' active users")); + }; /* Associate a rule to a user. @@ -508,16 +481,14 @@ Persistence @public linkRule( *ruleId, userId* ) @param {String} ruleId @param {String} userId - */ + */ - exports.linkRule = (function(_this) { - return function(ruleId, userId) { - _this.log.info("DB | linkRule: '" + ruleId + "' for user '" + userId + "'"); - _this.db.sadd("rule:" + ruleId + ":users", userId, replyHandler("storing user '" + userId + "' for rule key '" + ruleId + "'")); - return _this.db.sadd("user:" + userId + ":rules", ruleId, replyHandler("storing rule key '" + ruleId + "' for user '" + userId + "'")); - }; - })(this); + exports.linkRule = function(ruleId, userId) { + _this.log.info("DB | linkRule: '" + ruleId + "' for user '" + userId + "'"); + _this.db.sadd("rule:" + ruleId + ":users", userId, replyHandler("storing user '" + userId + "' for rule key '" + ruleId + "'")); + return _this.db.sadd("user:" + userId + ":rules", ruleId, replyHandler("storing rule key '" + ruleId + "' for user '" + userId + "'")); + }; /* Get rules linked to a user and hand it to cb(err, obj). @@ -525,15 +496,13 @@ Persistence @public getUserLinkRule( *userId, cb* ) @param {String} userId @param {function} cb - */ + */ - exports.getUserLinkedRules = (function(_this) { - return function(userId, cb) { - _this.log.info("DB | getUserLinkedRules: for user '" + userId + "'"); - return _this.db.smembers("user:" + userId + ":rules", cb); - }; - })(this); + exports.getUserLinkedRules = function(userId, cb) { + _this.log.info("DB | getUserLinkedRules: for user '" + userId + "'"); + return _this.db.smembers("user:" + userId + ":rules", cb); + }; /* Get users linked to a rule and hand it to cb(err, obj). @@ -541,15 +510,13 @@ Persistence @public getRuleLinkedUsers( *ruleId, cb* ) @param {String} ruleId @param {function} cb - */ + */ - exports.getRuleLinkedUsers = (function(_this) { - return function(ruleId, cb) { - _this.log.info("DB | getRuleLinkedUsers: for rule '" + ruleId + "'"); - return _this.db.smembers("rule:" + ruleId + ":users", cb); - }; - })(this); + exports.getRuleLinkedUsers = function(ruleId, cb) { + _this.log.info("DB | getRuleLinkedUsers: for rule '" + ruleId + "'"); + return _this.db.smembers("rule:" + ruleId + ":users", cb); + }; /* Delete an association of a rule to a user. @@ -557,16 +524,14 @@ Persistence @public unlinkRule( *ruleId, userId* ) @param {String} ruleId @param {String} userId - */ + */ - exports.unlinkRule = (function(_this) { - return function(ruleId, userId) { - _this.log.info("DB | unlinkRule: '" + ruleId + ":" + userId + "'"); - _this.db.srem("rule:" + ruleId + ":users", userId, replyHandler("removing user '" + userId + "' for rule key '" + ruleId + "'")); - return _this.db.srem("user:" + userId + ":rules", ruleId, replyHandler("removing rule key '" + ruleId + "' for user '" + userId + "'")); - }; - })(this); + exports.unlinkRule = function(ruleId, userId) { + _this.log.info("DB | unlinkRule: '" + ruleId + ":" + userId + "'"); + _this.db.srem("rule:" + ruleId + ":users", userId, replyHandler("removing user '" + userId + "' for rule key '" + ruleId + "'")); + return _this.db.srem("user:" + userId + ":rules", ruleId, replyHandler("removing rule key '" + ruleId + "' for user '" + userId + "'")); + }; /* Activate a rule. @@ -574,16 +539,14 @@ Persistence @public activateRule( *ruleId, userId* ) @param {String} ruleId @param {String} userId - */ + */ - exports.activateRule = (function(_this) { - return function(ruleId, userId) { - _this.log.info("DB | activateRule: '" + ruleId + "' for '" + userId + "'"); - _this.db.sadd("rule:" + ruleId + ":active-users", userId, replyHandler("storing activated user '" + userId + "' in rule '" + ruleId + "'")); - return _this.db.sadd("user:" + userId + ":active-rules", ruleId, replyHandler("storing activated rule '" + ruleId + "' in user '" + userId + "'")); - }; - })(this); + exports.activateRule = function(ruleId, userId) { + _this.log.info("DB | activateRule: '" + ruleId + "' for '" + userId + "'"); + _this.db.sadd("rule:" + ruleId + ":active-users", userId, replyHandler("storing activated user '" + userId + "' in rule '" + ruleId + "'")); + return _this.db.sadd("user:" + userId + ":active-rules", ruleId, replyHandler("storing activated rule '" + ruleId + "' in user '" + userId + "'")); + }; /* Get rules activated for a user and hand it to cb(err, obj). @@ -591,15 +554,13 @@ Persistence @public getUserLinkRule( *userId, cb* ) @param {String} userId @param {function} cb - */ + */ - exports.getUserActivatedRules = (function(_this) { - return function(userId, cb) { - _this.log.info("DB | getUserActivatedRules: for user '" + userId + "'"); - return _this.db.smembers("user:" + userId + ":active-rules", cb); - }; - })(this); + exports.getUserActivatedRules = function(userId, cb) { + _this.log.info("DB | getUserActivatedRules: for user '" + userId + "'"); + return _this.db.smembers("user:" + userId + ":active-rules", cb); + }; /* Get users activated for a rule and hand it to cb(err, obj). @@ -607,15 +568,13 @@ Persistence @public getRuleActivatedUsers ( *ruleId, cb* ) @param {String} ruleId @param {function} cb - */ + */ - exports.getRuleActivatedUsers = (function(_this) { - return function(ruleId, cb) { - _this.log.info("DB | getRuleActivatedUsers: for rule '" + ruleId + "'"); - return _this.db.smembers("rule:" + ruleId + ":active-users", cb); - }; - })(this); + exports.getRuleActivatedUsers = function(ruleId, cb) { + _this.log.info("DB | getRuleActivatedUsers: for rule '" + ruleId + "'"); + return _this.db.smembers("rule:" + ruleId + ":active-users", cb); + }; /* Deactivate a rule. @@ -623,59 +582,55 @@ Persistence @public deactivateRule( *ruleId, userId* ) @param {String} ruleId @param {String} userId - */ + */ - exports.deactivateRule = (function(_this) { - return function(ruleId, userId) { - _this.log.info("DB | deactivateRule: '" + ruleId + "' for '" + userId + "'"); - _this.db.srem("rule:" + ruleId + ":active-users", userId, replyHandler("removing activated user '" + userId + "' in rule '" + ruleId + "'")); - return _this.db.srem("user:" + userId + ":active-rules", ruleId, replyHandler("removing activated rule '" + ruleId + "' in user '" + userId + "'")); - }; - })(this); + exports.deactivateRule = function(ruleId, userId) { + _this.log.info("DB | deactivateRule: '" + ruleId + "' for '" + userId + "'"); + _this.db.srem("rule:" + ruleId + ":active-users", userId, replyHandler("removing activated user '" + userId + "' in rule '" + ruleId + "'")); + return _this.db.srem("user:" + userId + ":active-rules", ruleId, replyHandler("removing activated rule '" + ruleId + "' in user '" + userId + "'")); + }; /* Fetch all active ruleIds and pass them to cb(err, obj). @public getAllActivatedRuleIds( *cb* ) @param {function} cb - */ + */ - exports.getAllActivatedRuleIdsPerUser = (function(_this) { - return function(cb) { - _this.log.info("DB | Fetching all active rules"); - return _this.db.smembers('users', function(err, obj) { - var fFetchActiveUserRules, result, semaphore, user, _i, _len, _results; - result = {}; - if (obj.length === 0) { - return cb(null, result); - } else { - semaphore = obj.length; - fFetchActiveUserRules = function(userId) { - return _this.db.smembers("user:" + user + ":active-rules", function(err, obj) { - if (obj.length > 0) { - result[userId] = obj; - } - if (--semaphore === 0) { - return cb(null, result); - } - }); - }; - _results = []; - for (_i = 0, _len = obj.length; _i < _len; _i++) { - user = obj[_i]; - _results.push(fFetchActiveUserRules(user)); - } - return _results; + + exports.getAllActivatedRuleIdsPerUser = function(cb) { + _this.log.info("DB | Fetching all active rules"); + return _this.db.smembers('users', function(err, obj) { + var fFetchActiveUserRules, result, semaphore, user, _i, _len, _results; + result = {}; + if (obj.length === 0) { + return cb(null, result); + } else { + semaphore = obj.length; + fFetchActiveUserRules = function(userId) { + return _this.db.smembers("user:" + user + ":active-rules", function(err, obj) { + if (obj.length > 0) { + result[userId] = obj; + } + if (--semaphore === 0) { + return cb(null, result); + } + }); + }; + _results = []; + for (_i = 0, _len = obj.length; _i < _len; _i++) { + user = obj[_i]; + _results.push(fFetchActiveUserRules(user)); } - }); - }; - })(this); - + return _results; + } + }); + }; /* - *# Users - */ + ## Users + */ /* @@ -684,36 +639,32 @@ Persistence @public storeUser( *objUser* ) @param {Object} objUser - */ + */ - exports.storeUser = (function(_this) { - return function(objUser) { - _this.log.info("DB | storeUser: '" + objUser.username + "'"); - if (objUser && objUser.username && objUser.password) { - _this.db.sadd('users', objUser.username, replyHandler("storing user key '" + objUser.username + "'")); - objUser.password = objUser.password; - return _this.db.hmset("user:" + objUser.username, objUser, replyHandler("storing user properties '" + objUser.username + "'")); - } else { - return _this.log.warn(new Error('DB | username or password was missing')); - } - }; - })(this); + exports.storeUser = function(objUser) { + _this.log.info("DB | storeUser: '" + objUser.username + "'"); + if (objUser && objUser.username && objUser.password) { + _this.db.sadd('users', objUser.username, replyHandler("storing user key '" + objUser.username + "'")); + objUser.password = objUser.password; + return _this.db.hmset("user:" + objUser.username, objUser, replyHandler("storing user properties '" + objUser.username + "'")); + } else { + return _this.log.warn(new Error('DB | username or password was missing')); + } + }; /* Fetch all user IDs and pass them to cb(err, obj). @public getUserIds( *cb* ) @param {function} cb - */ + */ - exports.getUserIds = (function(_this) { - return function(cb) { - _this.log.info("DB | getUserIds"); - return _this.db.smembers("users", cb); - }; - })(this); + exports.getUserIds = function(cb) { + _this.log.info("DB | getUserIds"); + return _this.db.smembers("users", cb); + }; /* Fetch a user by id and pass it to cb(err, obj). @@ -721,70 +672,66 @@ Persistence @public getUser( *userId, cb* ) @param {String} userId @param {function} cb - */ + */ - exports.getUser = (function(_this) { - return function(userId, cb) { - _this.log.info("DB | getUser: '" + userId + "'"); - return _this.db.hgetall("user:" + userId, cb); - }; - })(this); + exports.getUser = function(userId, cb) { + _this.log.info("DB | getUser: '" + userId + "'"); + return _this.db.hgetall("user:" + userId, cb); + }; /* Deletes a user and all his associated linked and active rules. @public deleteUser( *userId* ) @param {String} userId - */ + */ - exports.deleteUser = (function(_this) { - return function(userId) { - _this.log.info("DB | deleteUser: '" + userId + "'"); - _this.db.srem("users", userId, replyHandler("Deleting user key '" + userId + "'")); - _this.db.del("user:" + userId, replyHandler("Deleting user '" + userId + "'")); - _this.db.smembers("user:" + userId + ":rules", function(err, obj) { - var delLinkedRuleUser, id, _i, _len, _results; - delLinkedRuleUser = function(ruleId) { - return _this.db.srem("rule:" + ruleId + ":users", userId, replyHandler("Deleting user key '" + userId + "' in linked rule '" + ruleId + "'")); - }; - _results = []; - for (_i = 0, _len = obj.length; _i < _len; _i++) { - id = obj[_i]; - _results.push(delLinkedRuleUser(id)); - } - return _results; - }); - _this.db.del("user:" + userId + ":rules", replyHandler("Deleting user '" + userId + "' rules")); - _this.db.smembers("user:" + userId + ":active-rules", function(err, obj) { - var delActivatedRuleUser, id, _i, _len, _results; - delActivatedRuleUser = function(ruleId) { - return _this.db.srem("rule:" + ruleId + ":active-users", userId, replyHandler("Deleting user key '" + userId + "' in active rule '" + ruleId + "'")); - }; - _results = []; - for (_i = 0, _len = obj.length; _i < _len; _i++) { - id = obj[_i]; - _results.push(delActivatedRuleUser(id)); - } - return _results; - }); - _this.db.del("user:" + userId + ":active-rules", replyHandler("Deleting user '" + userId + "' rules")); - _this.db.smembers("user:" + userId + ":roles", function(err, obj) { - var delRoleUser, id, _i, _len, _results; - delRoleUser = function(roleId) { - return _this.db.srem("role:" + roleId + ":users", userId, replyHandler("Deleting user key '" + userId + "' in role '" + roleId + "'")); - }; - _results = []; - for (_i = 0, _len = obj.length; _i < _len; _i++) { - id = obj[_i]; - _results.push(delRoleUser(id)); - } - return _results; - }); - return _this.db.del("user:" + userId + ":roles", replyHandler("Deleting user '" + userId + "' roles")); - }; - })(this); + exports.deleteUser = function(userId) { + _this.log.info("DB | deleteUser: '" + userId + "'"); + _this.db.srem("users", userId, replyHandler("Deleting user key '" + userId + "'")); + _this.db.del("user:" + userId, replyHandler("Deleting user '" + userId + "'")); + _this.db.smembers("user:" + userId + ":rules", function(err, obj) { + var delLinkedRuleUser, id, _i, _len, _results; + delLinkedRuleUser = function(ruleId) { + return _this.db.srem("rule:" + ruleId + ":users", userId, replyHandler("Deleting user key '" + userId + "' in linked rule '" + ruleId + "'")); + }; + _results = []; + for (_i = 0, _len = obj.length; _i < _len; _i++) { + id = obj[_i]; + _results.push(delLinkedRuleUser(id)); + } + return _results; + }); + _this.db.del("user:" + userId + ":rules", replyHandler("Deleting user '" + userId + "' rules")); + _this.db.smembers("user:" + userId + ":active-rules", function(err, obj) { + var delActivatedRuleUser, id, _i, _len, _results; + delActivatedRuleUser = function(ruleId) { + return _this.db.srem("rule:" + ruleId + ":active-users", userId, replyHandler("Deleting user key '" + userId + "' in active rule '" + ruleId + "'")); + }; + _results = []; + for (_i = 0, _len = obj.length; _i < _len; _i++) { + id = obj[_i]; + _results.push(delActivatedRuleUser(id)); + } + return _results; + }); + _this.db.del("user:" + userId + ":active-rules", replyHandler("Deleting user '" + userId + "' rules")); + _this.db.smembers("user:" + userId + ":roles", function(err, obj) { + var delRoleUser, id, _i, _len, _results; + delRoleUser = function(roleId) { + return _this.db.srem("role:" + roleId + ":users", userId, replyHandler("Deleting user key '" + userId + "' in role '" + roleId + "'")); + }; + _results = []; + for (_i = 0, _len = obj.length; _i < _len; _i++) { + id = obj[_i]; + _results.push(delRoleUser(id)); + } + return _results; + }); + return _this.db.del("user:" + userId + ":roles", replyHandler("Deleting user '" + userId + "' roles")); + }; /* Checks the credentials and on success returns the user object to the @@ -796,36 +743,34 @@ Persistence @param {String} userId @param {String} password @param {function} cb - */ + */ - exports.loginUser = (function(_this) { - return function(userId, password, cb) { - var fCheck; - _this.log.info("DB | User '" + userId + "' tries to log in"); - fCheck = function(pw) { - return function(err, obj) { - if (err) { - return cb(err, null); - } else if (obj && obj.password) { - if (pw === obj.password) { - _this.log.info("DB | User '" + obj.username + "' logged in!"); - return cb(null, obj); - } else { - return cb(new Error('Wrong credentials!'), null); - } + + exports.loginUser = function(userId, password, cb) { + var fCheck; + _this.log.info("DB | User '" + userId + "' tries to log in"); + fCheck = function(pw) { + return function(err, obj) { + if (err) { + return cb(err, null); + } else if (obj && obj.password) { + if (pw === obj.password) { + _this.log.info("DB | User '" + obj.username + "' logged in!"); + return cb(null, obj); } else { - return cb(new Error('User not found!'), null); + return cb(new Error('Wrong credentials!'), null); } - }; + } else { + return cb(new Error('User not found!'), null); + } }; - return _this.db.hgetall("user:" + userId, fCheck(password)); }; - })(this); - + return _this.db.hgetall("user:" + userId, fCheck(password)); + }; /* - *# User Roles - */ + ## User Roles + */ /* @@ -834,17 +779,15 @@ Persistence @public storeUserRole( *userId, role* ) @param {String} userId @param {String} role - */ + */ - exports.storeUserRole = (function(_this) { - return function(userId, role) { - _this.log.info("DB | storeUserRole: '" + userId + ":" + role + "'"); - _this.db.sadd('roles', role, replyHandler("adding role '" + role + "' to role index set")); - _this.db.sadd("user:" + userId + ":roles", role, replyHandler("adding role '" + role + "' to user '" + userId + "'")); - return _this.db.sadd("role:" + role + ":users", userId, replyHandler("adding user '" + userId + "' to role '" + role + "'")); - }; - })(this); + exports.storeUserRole = function(userId, role) { + _this.log.info("DB | storeUserRole: '" + userId + ":" + role + "'"); + _this.db.sadd('roles', role, replyHandler("adding role '" + role + "' to role index set")); + _this.db.sadd("user:" + userId + ":roles", role, replyHandler("adding role '" + role + "' to user '" + userId + "'")); + return _this.db.sadd("role:" + role + ":users", userId, replyHandler("adding user '" + userId + "' to role '" + role + "'")); + }; /* Fetch all roles of a user and pass them to cb(err, obj). @@ -852,15 +795,13 @@ Persistence @public getUserRoles( *userId* ) @param {String} userId @param {function} cb - */ + */ - exports.getUserRoles = (function(_this) { - return function(userId, cb) { - _this.log.info("DB | getUserRoles: '" + userId + "'"); - return _this.db.smembers("user:" + userId + ":roles", cb); - }; - })(this); + exports.getUserRoles = function(userId, cb) { + _this.log.info("DB | getUserRoles: '" + userId + "'"); + return _this.db.smembers("user:" + userId + ":roles", cb); + }; /* Fetch all users of a role and pass them to cb(err, obj). @@ -868,15 +809,13 @@ Persistence @public getUserRoles( *role* ) @param {String} role @param {function} cb - */ + */ - exports.getRoleUsers = (function(_this) { - return function(role, cb) { - _this.log.info("DB | getRoleUsers: '" + role + "'"); - return _this.db.smembers("role:" + role + ":users", cb); - }; - })(this); + exports.getRoleUsers = function(role, cb) { + _this.log.info("DB | getRoleUsers: '" + role + "'"); + return _this.db.smembers("role:" + role + ":users", cb); + }; /* Remove a role from a user. @@ -884,28 +823,25 @@ Persistence @public removeRoleFromUser( *role, userId* ) @param {String} role @param {String} userId - */ + */ - exports.removeUserRole = (function(_this) { - return function(userId, role) { - _this.log.info("DB | removeRoleFromUser: role '" + role + "', user '" + userId + "'"); - _this.db.srem("user:" + userId + ":roles", role, replyHandler("Removing role '" + role + "' from user '" + userId + "'")); - return _this.db.srem("role:" + role + ":users", userId, replyHandler("Removing user '" + userId + "' from role '" + role + "'")); - }; - })(this); + exports.removeUserRole = function(userId, role) { + _this.log.info("DB | removeRoleFromUser: role '" + role + "', user '" + userId + "'"); + _this.db.srem("user:" + userId + ":roles", role, replyHandler("Removing role '" + role + "' from user '" + userId + "'")); + return _this.db.srem("role:" + role + ":users", userId, replyHandler("Removing user '" + userId + "' from role '" + role + "'")); + }; /* Shuts down the db link. @public shutDown() - */ + */ - exports.shutDown = (function(_this) { - return function() { - var _ref; - return (_ref = _this.db) != null ? _ref.quit() : void 0; - }; - })(this); + + exports.shutDown = function() { + var _ref; + return (_ref = _this.db) != null ? _ref.quit() : void 0; + }; }).call(this); diff --git a/js-coffee/webapi-eca.js b/js-coffee/webapi-eca.js index 987c06c..5a89e07 100644 --- a/js-coffee/webapi-eca.js +++ b/js-coffee/webapi-eca.js @@ -1,5 +1,4 @@ -// Generated by CoffeeScript 1.7.1 - +// Generated by CoffeeScript 1.6.3 /* WebAPI-ECA Engine @@ -10,10 +9,12 @@ WebAPI-ECA Engine > node webapi-eca [opt] > > See below in the optimist CLI preparation for allowed optional parameters `[opt]`. - */ +*/ + (function() { - var argv, cm, conf, cp, db, engine, fs, http, init, logconf, logger, nameEP, opt, optimist, path, procCmds, shutDown, usage; + var argv, cm, conf, cp, db, engine, fs, http, init, logconf, logger, nameEP, opt, optimist, path, procCmds, shutDown, usage, + _this = this; logger = require('./logging'); @@ -39,10 +40,10 @@ WebAPI-ECA Engine procCmds = {}; - /* Let's prepare the optimist CLI optional arguments `[opt]`: - */ + */ + usage = 'This runs your webapi-based ECA engine'; @@ -129,91 +130,72 @@ WebAPI-ECA Engine this.log.info('RS | STARTING SERVER'); - /* This function is invoked right after the module is loaded and starts the server. @private init() - */ + */ - init = (function(_this) { - return function() { - var args; - args = { - logger: _this.log, - logconf: logconf - }; - args['http-port'] = parseInt(argv.w || conf.getHttpPort()); - args['db-port'] = parseInt(argv.d || conf.getDbPort()); - _this.log.info('RS | Initialzing DB'); - db(args); - return db.isConnected(function(err) { - var cliArgs, poller; - if (err) { - _this.log.error('RS | No DB connection, shutting down system!'); - return shutDown(); - } else { - _this.log.info('RS | Initialzing engine'); - engine(args); - _this.log.info('RS | Forking a child process for the event poller'); - cliArgs = [args.logconf['mode'], args.logconf['io-level'], args.logconf['file-level'], args.logconf['file-path'], args.logconf['nolog']]; - poller = cp.fork(path.resolve(__dirname, nameEP), cliArgs); - _this.log.info('RS | Initialzing module manager'); - cm(args); - cm.addListener('init', function(evt) { - return poller.send({ - event: 'init', - data: evt - }); - }); - cm.addListener('newRule', function(evt) { - return poller.send({ - event: 'newRule', - data: evt - }); - }); - cm.addListener('init', function(evt) { - return engine.internalEvent('init', evt); - }); - cm.addListener('newRule', function(evt) { - return engine.internalEvent('newRule', evt); - }); - _this.log.info('RS | Initialzing http listener'); - args['request-service'] = cm.processRequest; - args['shutdown-function'] = shutDown; - return http(args); - } - }); + + init = function() { + var args; + args = { + logger: _this.log, + logconf: logconf }; - })(this); - + args['http-port'] = parseInt(argv.w || conf.getHttpPort()); + args['db-port'] = parseInt(argv.d || conf.getDbPort()); + args['keygen'] = conf.getKeygenPassphrase(); + _this.log.info('RS | Initialzing DB'); + db(args); + return db.isConnected(function(err) { + var cliArgs, poller; + if (err) { + _this.log.error('RS | No DB connection, shutting down system!'); + return shutDown(); + } else { + _this.log.info('RS | Initialzing engine'); + engine(args); + _this.log.info('RS | Forking a child process for the event poller'); + cliArgs = [args.logconf['mode'], args.logconf['io-level'], args.logconf['file-level'], args.logconf['file-path'], args.logconf['nolog']]; + poller = cp.fork(path.resolve(__dirname, nameEP), cliArgs); + _this.log.info('RS | Initialzing module manager'); + cm(args); + cm.addRuleListener(engine.internalEvent); + cm.addRuleListener(function(evt) { + return poller.send(evt); + }); + _this.log.info('RS | Initialzing http listener'); + args['request-service'] = cm.processRequest; + args['shutdown-function'] = shutDown; + return http(args); + } + }); + }; /* Shuts down the server. @private shutDown() - */ + */ - shutDown = (function(_this) { - return function() { - _this.log.warn('RS | Received shut down command!'); - if (db != null) { - db.shutDown(); - } - if (engine != null) { - engine.shutDown(); - } - return process.exit(); - }; - })(this); + shutDown = function() { + _this.log.warn('RS | Received shut down command!'); + if (db != null) { + db.shutDown(); + } + engine.shutDown(); + return process.exit(); + }; /* - *# Process Commands + ## Process Commands When the server is run as a child process, this function handles messages from the parent process (e.g. the testing suite) - */ + */ + process.on('message', function(cmd) { return typeof procCmds[cmd] === "function" ? procCmds[cmd]() : void 0; diff --git a/js/engine.js b/js/engine.js index 2191b33..c9b88ad 100644 --- a/js/engine.js +++ b/js/engine.js @@ -1,156 +1,166 @@ 'use strict'; var path = require('path'), - cp = require('child_process'), - log = require('./logging'), - qEvents = new (require('./queue')).Queue(), //TODO export queue into redis regex = /\$X\.[\w\.\[\]]*/g, // find properties of $X listRules = {}, listActionModules = {}, isRunning = true, - ml, poller, db; + dynmod = require('./dynamic-modules'), + db = require('./persistence'), log; -exports = module.exports = function(args) { - args = args || {}; - log(args); - ml = require('./module_loader')(args); - poller = cp.fork(path.resolve(__dirname, 'eventpoller'), [log.getLogType()]); - poller.on('message', function(evt) { - exports.pushEvent(evt); - }); - //start to poll the event queue +exports = module.exports = function( args ) { + log = args.logger; + db( args); + dynmod(args); pollQueue(); return module.exports; }; -/* - * Initialize the rules engine which initializes the module loader. - * @param {Object} db_link the link to the db, see [db\_interface](db_interface.html) - * @param {String} db_port the db port - * @param {String} crypto_key the key to be used for encryption on the db, max legnth 256 - */ -exports.addDBLinkAndLoadActionsAndRules = function(db_link) { - //TODO only load rules on beginning, if rules require certain actions, load them in order to allow fast firing - // if rules are set inactive, remove the action module from the memory - db = db_link; - if(ml && db) db.actionInvokers.getModules(function(err, obj) { - if(err) log.error('EN', 'retrieving Action Modules from DB!'); - else { - if(!obj) { - log.print('EN', 'No Action Modules found in DB!'); - } else { - var m; - for(var el in obj) { - log.print('EN', 'Loading Action Module from DB: ' + el); - try { - m = ml.requireFromString(obj[el], el); - // db.getActionModuleAuth(el, function(mod) { - // return function(err, obj) { - // if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj)); - // }; - // }(m)); - //FIXME !!! - listActionModules[el] = m; - } catch(e) { - e.addInfo = 'error in action module "' + el + '"'; - log.error('EN', e); - } +var updateActionModules = function() { + for ( var user in listRules ) { + if(!listActionModules[user]) listActionModules[user] = {}; + for ( var rule in listRules[user] ) { + var actions = listRules[user][rule].actions; + console.log(actions); + for ( var module in actions ){ + for ( var i = 0; i < actions[module]['functions'].length; i++ ){ + db.actionInvokers.getModule(module, function( err, objAM ){ + db.actionInvokers.getUserParams(module, user, function( err, objParams ) { + console.log (objAM); + + //FIXME am name is called 'actions'??? + // if(objParams) { //TODO we don't need them for all modules + var answ = dynmod.compileString(objAM.code, objAM.actions + "_" + user, objParams, objAM.lang); + console.log('answ'); + console.log(answ); + listActionModules[user][module] = answ.module; + console.log('loaded ' + user + ': ' + module); + console.log(listActionModules); + // } + }); + }); } } } - if(db) db.getRules(function(err, obj) { - for(var el in obj) exports.addRule(JSON.parse(obj[el])); - }); - }); - else log.severe('EN', new Error('Module Loader or DB not defined!')); -}; - - -/** - * Insert an action module into the list of available interfaces. - * @param {Object} objModule the action module object - */ -//TODO action modules should be loaded once a user activates a rule with the respective -// action, if the user deletes the rule it has to be garrbage collected from the engine's list -exports.loadActionModule = function(name, objModule) { - log.print('EN', 'Action module "' + name + '" loaded'); - listActionModules[name] = objModule; -}; - -/** - * Add a rule into the working memory - * @param {Object} objRule the rule object - */ -exports.addRule = function(objRule) { - //TODO validate rule - log.print('EN', 'Loading Rule'); - log.print('EN', 'Loading Rule: ' + objRule.id); - if(listRules[objRule.id]) log.print('EN', 'Replacing rule: ' + objRule.id); - listRules[objRule.id] = objRule; - - // Notify poller about eventual candidate - try { - poller.send('event|'+objRule.event); - } catch (err) { - log.print('EN', 'Unable to inform poller about new active rule!'); } }; +exports.internalEvent = function( evt ) { + try { + // TODO do we need to determine init from newRule? + console.log (evt); + // console.log (data); + // var obj = JSON.parse( data ); + // db.getRuleActivatedUsers(obj.id, function ( err, arrUsers ) { + // console.log (arrUsers); + // for(var i = 0; i < arrUsers.length; i++) { + // if( !listRules[arrUsers[i]]) listRules[arrUsers[i]] = {}; + // listRules[arrUsers[i]][obj.id] = obj; + // updateActionModules(); + // } + // }); + } catch( err ) { + console.log( err ); + } + console.log('internal event handled'); +}; + + function pollQueue() { if(isRunning) { - var evt = qEvents.dequeue(); - if(evt) { - processEvent(evt); - } - setTimeout(pollQueue, 50); //TODO adapt to load + db.popEvent(function (err, obj) { + if(!err && obj) { + processEvent(obj); + } + setTimeout(pollQueue, 50); //TODO adapt to load + }); } } -/** - * Stores correctly posted events in the queue - * @param {Object} evt The event object - */ -exports.pushEvent = function(evt) { - qEvents.enqueue(evt); -}; - /** * Handles correctly posted events * @param {Object} evt The event object */ function processEvent(evt) { - log.print('EN', 'processing event: ' + evt.event + '(' + evt.eventid + ')'); + log.info('EN', 'processing event: ' + evt.event + '(' + evt.eventid + ')'); var actions = checkEvent(evt); - for(var i = 0; i < actions.length; i++) { - invokeAction(evt, actions[i]); + console.log('found actions to invoke:'); + console.log(actions); + for(var user in actions) { + for(var module in actions[user]) { + for(var i = 0; i < actions[user][module]['functions'].length; i++) { + var act = { + module: module, + function: actions[user][module]['functions'][i] + } + invokeAction(evt, user, act); + } + } } } /** + FIXME merge with processEvent + * Check an event against the rules repository and return the actions * if the conditons are met. * @param {Object} evt the event to check */ function checkEvent(evt) { - var actions = []; - for(var rn in listRules) { + var actions = {}, tEvt; + for(var user in listRules) { + actions[user] = {}; + for(var rule in listRules[user]) { //TODO this needs to get depth safe, not only data but eventually also // on one level above (eventid and other meta) - if(listRules[rn].event === evt.event && validConditions(evt.payload, listRules[rn])) { - log.print('EN', 'Rule "' + rn + '" fired'); - actions = actions.concat(listRules[rn].actions); + tEvt = listRules[user][rule].event; + if(tEvt.module + ' -> ' + tEvt.function === evt.event && validConditions(evt.payload, listRules[user][rule])) { + log.info('EN', 'Rule "' + rule + '" fired'); + var oAct = listRules[user][rule].actions; + console.log (oAct); + for(var module in oAct) { + if(!actions[user][module]) { + actions[user][module] = { + functions: [] + }; + } + for(var i = 0; i < oAct[module]['functions'].length; i++ ){ + console.log ('processing action ' + i + ', ' + oAct[module]['functions'][i]); + actions[user][module]['functions'].push(oAct[module]['functions'][i]); + // if(actions[user].indexOf(arrAct[i]) === -1) actions[user].push(arrAct[i]); + } + } + } } } return actions; } +// { +// "event": "emailyak -> newMail", +// "payload": { +// "TextBody": "hello" +// } +// } + +// exports.sendMail = ( args ) -> +// url = 'https://api.emailyak.com/v1/ps1g59ndfcwg10w/json/send/email/' + +// data = +// FromAddress: 'tester@mscliveweb.simpleyak.com' +// ToAddress: 'dominic.bosch.db@gmail.com' +// TextBody: 'test' + +// needle.post url, JSON.stringify( data ), {json: true}, ( err, resp, body ) -> +// log err +// log body /** * Checks whether all conditions of the rule are met by the event. * @param {Object} evt the event to check * @param {Object} rule the rule with its conditions */ function validConditions(evt, rule) { - for(var property in rule.condition){ + for(var property in rule.conditions){ if(!evt[property] || evt[property] != rule.condition[property]) return false; } return true; @@ -161,76 +171,75 @@ function validConditions(evt, rule) { * @param {Object} evt The event that invoked the action * @param {Object} action The action to be invoked */ -function invokeAction(evt, action) { - var actionargs = {}, - arrModule = action.module.split('->'); +function invokeAction( evt, user, action ) { + console.log('invoking action'); + var actionargs = {}; + //FIXME internal events, such as loopback ha sno arrow //TODO this requires change. the module property will be the identifier // in the actions object (or shall we allow several times the same action?) - if(arrModule.length < 2) { - log.error('EN', 'Invalid rule detected!'); - return; - } - var srvc = listActionModules[arrModule[0]]; - if(srvc && srvc[arrModule[1]]) { + console.log(action.module); + console.log(listActionModules); + var srvc = listActionModules[user][action.module]; + console.log(srvc); + if(srvc && srvc[action.function]) { //FIXME preprocessing not only on data //FIXME no preprocessing at all, why don't we just pass the whole event to the action?' - preprocessActionArguments(evt.payload, action.arguments, actionargs); + // preprocessActionArguments(evt.payload, action.arguments, actionargs); try { - if(srvc[arrModule[1]]) srvc[arrModule[1]](actionargs); + if(srvc[action.function]) srvc[action.function](evt.payload); } catch(err) { log.error('EN', 'during action execution: ' + err); } } - else log.print('EN', 'No api interface found for: ' + action.module); + else log.info('EN', 'No api interface found for: ' + action.module); } -/** - * Action properties may contain event properties which need to be resolved beforehand. - * @param {Object} evt The event whose property values can be used in the rules action - * @param {Object} act The rules action arguments - * @param {Object} res The object to be used to enter the new properties - */ -function preprocessActionArguments(evt, act, res) { - for(var prop in act) { - /* - * If the property is an object itself we go into recursion - */ - if(typeof act[prop] === 'object') { - res[prop] = {}; - preprocessActionArguments(evt, act[prop], res[prop]); - } - else { - var txt = act[prop]; - var arr = txt.match(regex); - /* - * If rules action property holds event properties we resolve them and - * replace the original action property - */ - // console.log(evt); - if(arr) { - for(var i = 0; i < arr.length; i++) { - /* - * The first three characters are '$X.', followed by the property - */ - var actionProp = arr[i].substring(3).toLowerCase(); - // console.log(actionProp); - for(var eprop in evt) { - // our rules language doesn't care about upper or lower case - if(eprop.toLowerCase() === actionProp) { - txt = txt.replace(arr[i], evt[eprop]); - } - } - txt = txt.replace(arr[i], '[property not available]'); - } - } - res[prop] = txt; - } - } -} +// /** +// * Action properties may contain event properties which need to be resolved beforehand. +// * @param {Object} evt The event whose property values can be used in the rules action +// * @param {Object} act The rules action arguments +// * @param {Object} res The object to be used to enter the new properties +// */ +// function preprocessActionArguments(evt, act, res) { +// for(var prop in act) { +// /* +// * If the property is an object itself we go into recursion +// */ +// if(typeof act[prop] === 'object') { +// res[prop] = {}; +// preprocessActionArguments(evt, act[prop], res[prop]); +// } +// else { +// var txt = act[prop]; +// var arr = txt.match(regex); + +// * If rules action property holds event properties we resolve them and +// * replace the original action property + +// // console.log(evt); +// if(arr) { +// for(var i = 0; i < arr.length; i++) { +// /* +// * The first three characters are '$X.', followed by the property +// */ +// var actionProp = arr[i].substring(3).toLowerCase(); +// // console.log(actionProp); +// for(var eprop in evt) { +// // our rules language doesn't care about upper or lower case +// if(eprop.toLowerCase() === actionProp) { +// txt = txt.replace(arr[i], evt[eprop]); +// } +// } +// txt = txt.replace(arr[i], '[property not available]'); +// } +// } +// res[prop] = txt; +// } +// } +// } exports.shutDown = function() { - log.print('EN', 'Shutting down Poller and DB Link'); + if(log) log.info('EN', 'Shutting down Poller and DB Link'); isRunning = false; - if(poller) poller.send('cmd|shutdown'); if(db) db.shutDown(); }; diff --git a/testing/files/testObjects.json b/testing/files/testObjects.json index 29bcaa2..c068b50 100644 --- a/testing/files/testObjects.json +++ b/testing/files/testObjects.json @@ -16,7 +16,7 @@ "eps": { "epOne": { "id":"epOne", - "lang":"0", + "lang":"CoffeeScript", "data":"\n#\n# EmailYak EVENT POLLER\n#\n# Requires user params:\n# - apikey: The user's EmailYak API key\n#\n\nurl = 'https://api.emailyak.com/v1/' + params.apikey + '/json/get/new/email/'\n\nexports.newMail = ( pushEvent ) ->\n needle.get url, ( err, resp, body ) ->\n if not err and resp.statusCode is 200\n mails = JSON.parse( body ).Emails\n pushEvent mail for mail in mails\n else\n log.error 'Error in EmailYak EM newMail: ' + err.message\n\n", "public":"false", "params":"[\"apikey\"]", @@ -24,13 +24,31 @@ }, "epTwo": { "id":"epTwo", - "lang":"0", + "lang":"CoffeeScript", "data":"\nurl = 'https://api.emailyak.com/v1/' + params.firstparam + '/json/get/new/email/'\n\nexports.newEvent = ( pushEvent ) ->\n needle.get url, ( err, resp, body ) ->\n if not err and resp.statusCode is 200\n mails = JSON.parse( body ).Emails\n pushEvent mail for mail in mails\n else\n log.error 'Error in EmailYak EM newMail: ' + err.message\n\nexports.randomNess = ( pushEvent ) ->\n console.log 'test runs: ' + params.secondparam\n", "public":"true", "params":"[\"firstparam\",\"secondparam\"]", "functions":"[\"newEvent\",\"randomNess\"]" } }, + "ais": { + "aiOne": { + "id":"aiOne", + "lang":"CoffeeScript", + "data":"# Send a mail through emailyak\nexports.sendMail = ( args ) ->\n\turl = 'https://api.emailyak.com/v1/' + params.apikey + '/json/send/email/'\n\tpayload =\n\t FromAddress : \"testsender@mscliveweb.simpleyak.com\",\n\t ToAddress: \"dominic.bosch@gmail.com\",\n\t Subject: \"TestMAIL\",\n\t TextBody: \"Hello\"\n\t\n\tneedle.post url, payload, ( err, resp, body ) ->\n\t\tif err\n\t\t\tlog err\n\t\tif resp.statusCode isnt 200\n\t\t\tlog 'Request not successful:'\n\t\t\tlog body\n", + "public":"false", + "params":"[\"apikey\"]", + "functions":"[\"sendMail\"]" + }, + "aiTwo": { + "id":"aiTwo", + "lang":"CoffeeScript", + "data":"# Send a mail through emailyak\nexports.sendMail = ( args ) ->\n\turl = 'https://api.emailyak.com/v1/' + params.apikey + '/json/send/email/'\n\tpayload =\n\t FromAddress : \"testsender@mscliveweb.simpleyak.com\",\n\t ToAddress: \"dominic.bosch@gmail.com\",\n\t Subject: \"TestMAIL\",\n\t TextBody: \"Hello\"\n\t\n\tneedle.post url, payload, ( err, resp, body ) ->\n\t\tif err\n\t\t\tlog err\n\t\tif resp.statusCode isnt 200\n\t\t\tlog 'Request not successful:'\n\t\t\tlog body\n", + "public":"false", + "params":"[\"apikey\",\"andmore\"]", + "functions":"[\"sendMail\"]" + } + }, "userparams": { "epUpOne": { "apikey": "testkey" @@ -52,6 +70,20 @@ "property": "yourValue2" }, "actions": [] + }, + "ruleThree": { + "id": "ruleThree_id", + "event": "custom-test-3", + "conditions": { + "property": "yourValue3" + }, + "actions": [] + }, + "ruleReal": { + "id": "ruleReal", + "event": "epOne -> newMail", + "conditions": {}, + "actions": ["aiOne -> sendMail"] } }, "users": { diff --git a/testing/test_components-manager.coffee b/testing/test_components-manager.coffee index 81b0c3f..65f8b44 100644 --- a/testing/test_components-manager.coffee +++ b/testing/test_components-manager.coffee @@ -25,12 +25,14 @@ db opts oUser = objects.users.userOne oRuleOne = objects.rules.ruleOne oRuleTwo = objects.rules.ruleTwo +oRuleThree = objects.rules.ruleThree oEpOne = objects.eps.epOne oEpTwo = objects.eps.epTwo exports.tearDown = ( cb ) -> db.deleteRule oRuleOne.id db.deleteRule oRuleTwo.id + db.deleteRule oRuleThree.id setTimeout cb, 100 exports.requestProcessing = @@ -56,33 +58,50 @@ exports.requestProcessing = test.done() exports.testListener = ( test ) => - test.expect 2 + test.expect 3 + + strRuleOne = JSON.stringify oRuleOne + strRuleTwo = JSON.stringify oRuleTwo + strRuleThree = JSON.stringify oRuleThree + + db.storeUser oUser + db.storeRule oRuleOne.id, strRuleOne + db.linkRule oRuleOne.id, oUser.username + db.activateRule oRuleOne.id, oUser.username + + db.storeRule oRuleTwo.id, strRuleTwo + db.linkRule oRuleTwo.id, oUser.username + db.activateRule oRuleTwo.id, oUser.username - db.storeRule oRuleOne.id, JSON.stringify oRuleOne request = command: 'forge_rule' - payload: JSON.stringify oRuleTwo + payload: strRuleThree + + cm.addRuleListener ( evt ) => + strEvt = JSON.stringify evt.rule + console.log evt + if evt.event is 'init' + if strEvt is strRuleOne or strEvt is strRuleTwo + test.ok true, 'Dummy true to fill expected tests!' + + if strEvt is strRuleThree + test.ok false, 'Init Rule found test rule number two??' + + if evt.event is 'new' + if strEvt is strRuleOne or strEvt is strRuleTwo + test.ok false, 'New Rule got test rule number one??' + + if strEvt is strRuleThree + test.ok true, 'Dummy true to fill expected tests!' - cm.addListener 'newRule', ( evt ) => - try - newRule = JSON.parse evt - catch err - test.ok false, 'Failed to parse the newRule event' - test.deepEqual newRule, oRuleTwo, 'New Rule is not the same!' - test.done() - cm.addListener 'init', ( evt ) => - try - initRule = JSON.parse evt - catch err - test.ok false, 'Failed to parse the newRule event' - test.deepEqual initRule, oRuleOne, 'Init Rule is not the same!' fWaitForInit = -> cm.processRequest oUser, request, ( answ ) => if answ.code isnt 200 test.ok false, 'testListener failed: ' + answ.message test.done() + setTimeout test.done, 500 setTimeout fWaitForInit, 200 @@ -96,6 +115,7 @@ exports.moduleHandling = test.expect 2 db.eventPollers.storeModule oUser.username, oEpOne + db.eventPollers.storeModule oUser.username, oEpTwo request = command: 'get_event_pollers' @@ -103,23 +123,25 @@ exports.moduleHandling = test.strictEqual 200, answ.code, 'GetModules failed...' oExpected = {} oExpected[oEpOne.id] = JSON.parse oEpOne.functions - test.strictEqual JSON.stringify(oExpected), answ.message, + oExpected[oEpTwo.id] = JSON.parse oEpTwo.functions + test.deepEqual oExpected, JSON.parse(answ.message), 'GetModules retrieved modules is not what we expected' test.done() testGetModuleParams: ( test ) -> test.expect 2 - db.eventPollers.storeModule oUser.username, oEpTwo + db.eventPollers.storeModule oUser.username, oEpOne request = command: 'get_event_poller_params' - payload: '{"id": "' + oEpTwo.id + '"}' - + payload: + id: oEpOne.id + request.payload = JSON.stringify request.payload cm.processRequest oUser, request, ( answ ) => test.strictEqual 200, answ.code, 'Required Module Parameters did not return 200' - test.strictEqual oEpTwo.params, answ.message, + test.strictEqual oEpOne.params, answ.message, 'Required Module Parameters did not match' test.done() diff --git a/testing/test_dynamic-modules.coffee b/testing/test_dynamic-modules.coffee new file mode 100644 index 0000000..dd935e8 --- /dev/null +++ b/testing/test_dynamic-modules.coffee @@ -0,0 +1,41 @@ +fs = require 'fs' +path = require 'path' + +try + data = fs.readFileSync path.resolve( 'testing', 'files', 'testObjects.json' ), 'utf8' + try + objects = JSON.parse data + catch err + console.log 'Error parsing standard objects file: ' + err.message +catch err + console.log 'Error fetching standard objects file: ' + err.message + +logger = require path.join '..', 'js-coffee', 'logging' +log = logger.getLogger + nolog: true +opts = + logger: log + +dm = require path.join '..', 'js-coffee', 'dynamic-modules' +dm opts + +exports.testCompile = ( test ) -> + test.expect 5 + + paramsOne = + testParam: 'First Test' + paramsTwo = + testParam: 'Second Test' + + code = "exports.testFunc = () ->\n\tparams.testParam" + result = dm.compileString code, 'userOne', 'moduleOne', paramsOne, 'CoffeeScript' + test.strictEqual 200, result.answ.code + moduleOne = result.module + test.strictEqual paramsOne.testParam, moduleOne.testFunc(), "Other result expected" + + result = dm.compileString code, 'userOne', 'moduleOne', paramsTwo, 'CoffeeScript' + test.strictEqual 200, result.answ.code + moduleTwo = result.module + test.strictEqual paramsTwo.testParam, moduleTwo.testFunc(), "Other result expected" + test.notStrictEqual paramsOne.testParam, moduleTwo.testFunc(), "Other result expected" + test.done() \ No newline at end of file diff --git a/testing/test_engine.coffee b/testing/test_engine.coffee new file mode 100644 index 0000000..e6df5f0 --- /dev/null +++ b/testing/test_engine.coffee @@ -0,0 +1,84 @@ +fs = require 'fs' +path = require 'path' + +try + data = fs.readFileSync path.resolve( 'testing', 'files', 'testObjects.json' ), 'utf8' + try + objects = JSON.parse data + catch err + console.log 'Error parsing standard objects file: ' + err.message +catch err + console.log 'Error fetching standard objects file: ' + err.message + +logger = require path.join '..', 'js-coffee', 'logging' +log = logger.getLogger() + # nolog: true +opts = + logger: log + +engine = require path.join '..', 'js-coffee', 'engine' +engine opts + +db = require path.join '..', 'js-coffee', 'persistence' +db opts + +oUser = objects.users.userOne +oRuleOne = objects.rules.ruleOne +oRuleTwo = objects.rules.ruleTwo +oRuleReal = objects.rules.ruleReal +oEpOne = objects.eps.epOne +oEpTwo = objects.eps.epTwo +oAiOne = objects.ais.aiOne + +exports.tearDown = ( cb ) -> + db.deleteRule oRuleOne.id + db.deleteRule oRuleTwo.id + db.deleteRule oRuleReal.id + db.actionInvokers.deleteModule oAiOne.id + db.deleteUser oUser.username + setTimeout cb, 100 + +exports.ruleEvents = + # init: first registration, multiple registration + # actions loaded and added correctly + # new: new actions added + # old actions removed + # delete: all actions removed if not required anymore + testInit: ( test ) -> + db.storeUser oUser + test.done() + strRuleOne = JSON.stringify oRuleOne + strRuleTwo = JSON.stringify oRuleTwo + strRuleReal = JSON.stringify oRuleReal + + db.actionInvokers.storeModule oUser.username, oAiOne + + db.storeRule oRuleOne.id, strRuleOne + db.linkRule oRuleOne.id, oUser.username + db.activateRule oRuleOne.id, oUser.username + + db.storeRule oRuleTwo.id, strRuleTwo + db.linkRule oRuleTwo.id, oUser.username + db.activateRule oRuleTwo.id, oUser.username + + db.storeRule oRuleReal.id, strRuleReal + db.linkRule oRuleReal.id, oUser.username + db.activateRule oRuleReal.id, oUser.username + + db.getAllActivatedRuleIdsPerUser ( err, obj ) => + @existingRules = obj + console.log 'existing' + console.log obj + + engine.internalEvent + event: 'init' + user: oUser.username + rule: oRuleReal + + fCheckRules = () -> + db.getAllActivatedRuleIdsPerUser ( err, obj ) => + console.log 'after init' + console.log obj + console.log @existingRules is obj + + setTimeout fCheckRules, 500 \ No newline at end of file diff --git a/webpages/handlers/coffee/forge_action_invoker.coffee b/webpages/handlers/coffee/forge_action_invoker.coffee index c3e2a85..0380058 100644 --- a/webpages/handlers/coffee/forge_action_invoker.coffee +++ b/webpages/handlers/coffee/forge_action_invoker.coffee @@ -10,7 +10,7 @@ fOnLoad = () -> editor.setShowPrintMargin false $( '#editor_mode' ).change ( el ) -> - if $( this ).val() is '0' + if $( this ).val() is 'CoffeeScript' editor.getSession().setMode "ace/mode/coffee" else editor.getSession().setMode "ace/mode/javascript" @@ -80,24 +80,3 @@ fOnLoad = () -> window.location.href = 'forge?page=forge_action_invoker' window.addEventListener 'load', fOnLoad, true - -# "ais": { -# "ai1": { -# "code": " -# url = 'https://api.emailyak.com/v1/' + params.apikey + '/json/send/email/' - -# exports.sendMail = ( args ) -> -# data: -# FromAddress: "no-reply@mscliveweb.simpleyak.com" -# ToAddress: "test@mscliveweb.simpleyak.com" -# log 'set data, posting now' -# needle.post url, data, ( err, resp, body ) -> -# log 'post returned' -# if not err and resp.statusCode is 200 -# log 'Sent mail...' -# else -# log 'Error in EmailYak EM sendMail: ' + err.message -# " - -# } -# }, \ No newline at end of file diff --git a/webpages/handlers/coffee/forge_event_poller.coffee b/webpages/handlers/coffee/forge_event_poller.coffee index e7dda78..3b31051 100644 --- a/webpages/handlers/coffee/forge_event_poller.coffee +++ b/webpages/handlers/coffee/forge_event_poller.coffee @@ -10,7 +10,7 @@ fOnLoad = () -> editor.setShowPrintMargin false $( '#editor_mode' ).change ( el ) -> - if $( this ).val() is '0' + if $( this ).val() is 'CoffeeScript' editor.getSession().setMode "ace/mode/coffee" else editor.getSession().setMode "ace/mode/javascript" diff --git a/webpages/handlers/coffee/forge_rule.coffee b/webpages/handlers/coffee/forge_rule.coffee index c3e38bb..80b3b2f 100644 --- a/webpages/handlers/coffee/forge_rule.coffee +++ b/webpages/handlers/coffee/forge_rule.coffee @@ -1,4 +1,11 @@ - +strPublicKey = '' +$.post( '/usercommand', command: 'get_public_key' ) + .done ( data ) -> + strPublicKey = data.message + .fail ( err ) -> + console.log err + $( '#info' ).text 'Error fetching public key, unable to send user-specific parameters securely' + $( '#info' ).attr 'class', 'error' fOnLoad = () -> @@ -12,19 +19,19 @@ fOnLoad = () -> command: 'get_event_poller_params' payload: id: arr[0] + obj.payload = JSON.stringify( obj.payload ); $.post( '/usercommand', obj ) .done ( data ) -> if data.message arrParams = JSON.parse data.message $( '#event_poller_params table' ).remove() if arrParams.length > 0 - $( '#event_poller_params' ).text 'Required user-specific params:' table = $ '' $( '#event_poller_params' ).append table fAppendParam = ( name ) -> tr = $( '' ) tr.append $( '
' ).css 'width', '20px' - tr.append $( '' ).text name + tr.append $( '' ).attr( 'class', 'key' ).text name inp = $( '' ).attr( 'type', 'password' ).attr 'id', "#{ name }" tr.append $( '' ).text( ' :' ).append inp table.append tr @@ -45,13 +52,17 @@ fOnLoad = () -> command: 'get_event_pollers' $.post( '/usercommand', obj ) .done ( data ) -> + try + oEps = JSON.parse data.message + catch err + console.error 'ERROR: non-object received from server: ' + data.message + return + fAppendEvents = ( id, events ) -> - try - arrNames = JSON.parse events - $( '#select_event' ).append $( '' ).attr( 'class', 'title').text( opt.val() ) table.append tr if $( '#ap_' + arrAI[0] ).length is 0 - div = $( '
' ).attr( 'id', 'ap_' + arrAI[0] ).html "#{ arrAI[0] }" + div = $( '
' ) + .attr( 'id', 'ap_' + arrAI[0] ) + div.append $( '
') + .attr( 'class', 'underlined') + .text arrAI[0] $( '#action_params' ).append div fFetchActionParams div, arrAI[0] opt.remove() @@ -149,7 +169,7 @@ fOnLoad = () -> ep = {} $( "#event_poller_params tr" ).each () -> val = $( 'input', this ).val() - name = $( 'td:nth-child(2)', this ).text() + name = $( this ).children( '.key' ).text() if val is '' throw new Error "Please enter a value for '#{ name }' in the event module!" ep[name] = val @@ -159,7 +179,7 @@ fOnLoad = () -> # Store all selected action invokers ap = {} - $( '#action_params div' ).each () -> + $( '> div', $( '#action_params' ) ).each () -> id = $( this ).attr( 'id' ).substring 3 params = {} $( 'tr', this ).each () -> @@ -168,23 +188,26 @@ fOnLoad = () -> if val is '' throw new Error "'#{ key }' missing for '#{ id }'" params[key] = val - ap[id] = params + encryptedParams = cryptico.encrypt JSON.stringify( params ), strPublicKey + ap[id] = encryptedParams.cipher acts = [] $( '#selected_actions .title' ).each () -> acts.push $( this ).text() + encryptedParams = cryptico.encrypt JSON.stringify( ep ), strPublicKey obj = command: 'forge_rule' payload: id: $( '#input_id' ).val() event: $( '#select_event option:selected' ).val() - event_params: ep + event_params: encryptedParams.cipher conditions: {} #TODO Add conditions! actions: acts action_params: ap obj.payload = JSON.stringify obj.payload $.post( '/usercommand', obj ) .done ( data ) -> + console.log 'success' $( '#info' ).text data.message $( '#info' ).attr 'class', 'success' .fail ( err ) -> diff --git a/webpages/handlers/js/forge_action_invoker.js b/webpages/handlers/js/forge_action_invoker.js index d46edb9..5d412ac 100644 --- a/webpages/handlers/js/forge_action_invoker.js +++ b/webpages/handlers/js/forge_action_invoker.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.6.3 (function() { var fOnLoad; @@ -11,7 +11,7 @@ editor.getSession().setMode("ace/mode/coffee"); editor.setShowPrintMargin(false); $('#editor_mode').change(function(el) { - if ($(this).val() === '0') { + if ($(this).val() === 'CoffeeScript') { return editor.getSession().setMode("ace/mode/coffee"); } else { return editor.getSession().setMode("ace/mode/javascript"); diff --git a/webpages/handlers/js/forge_event_poller.js b/webpages/handlers/js/forge_event_poller.js index edccd11..4585629 100644 --- a/webpages/handlers/js/forge_event_poller.js +++ b/webpages/handlers/js/forge_event_poller.js @@ -1,4 +1,4 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.6.3 (function() { var fOnLoad; @@ -11,7 +11,7 @@ editor.getSession().setMode("ace/mode/coffee"); editor.setShowPrintMargin(false); $('#editor_mode').change(function(el) { - if ($(this).val() === '0') { + if ($(this).val() === 'CoffeeScript') { return editor.getSession().setMode("ace/mode/coffee"); } else { return editor.getSession().setMode("ace/mode/javascript"); diff --git a/webpages/handlers/js/forge_rule.js b/webpages/handlers/js/forge_rule.js index fb43119..2b8e274 100644 --- a/webpages/handlers/js/forge_rule.js +++ b/webpages/handlers/js/forge_rule.js @@ -1,6 +1,18 @@ -// Generated by CoffeeScript 1.7.1 +// Generated by CoffeeScript 1.6.3 (function() { - var fOnLoad; + var fOnLoad, strPublicKey; + + strPublicKey = ''; + + $.post('/usercommand', { + command: 'get_public_key' + }).done(function(data) { + return strPublicKey = data.message; + }).fail(function(err) { + console.log(err); + $('#info').text('Error fetching public key, unable to send user-specific parameters securely'); + return $('#info').attr('class', 'error'); + }); fOnLoad = function() { var arrActionInvoker, fFetchActionParams, fFetchEventParams, obj; @@ -15,20 +27,20 @@ id: arr[0] } }; + obj.payload = JSON.stringify(obj.payload); return $.post('/usercommand', obj).done(function(data) { var arrParams, fAppendParam, table, _i, _len, _results; if (data.message) { arrParams = JSON.parse(data.message); $('#event_poller_params table').remove(); if (arrParams.length > 0) { - $('#event_poller_params').text('Required user-specific params:'); table = $(''); $('#event_poller_params').append(table); fAppendParam = function(name) { var inp, tr; tr = $(''); tr.append($('
').css('width', '20px')); - tr.append($('').text(name)); + tr.append($('').attr('class', 'key').text(name)); inp = $('').attr('type', 'password').attr('id', "" + name); tr.append($('').text(' :').append(inp)); return table.append(tr); @@ -51,25 +63,28 @@ command: 'get_event_pollers' }; $.post('/usercommand', obj).done(function(data) { - var events, fAppendEvents, id, _ref; + var err, events, fAppendEvents, id, oEps; + try { + oEps = JSON.parse(data.message); + } catch (_error) { + err = _error; + console.error('ERROR: non-object received from server: ' + data.message); + return; + } fAppendEvents = function(id, events) { - var arrNames, err, name, _i, _len, _results; - try { - arrNames = JSON.parse(events); - _results = []; - for (_i = 0, _len = arrNames.length; _i < _len; _i++) { - name = arrNames[_i]; - _results.push($('#select_event').append($('').attr('class', 'title').text(opt.val())); table.append(tr); if ($('#ap_' + arrAI[0]).length === 0) { - div = $('
').attr('id', 'ap_' + arrAI[0]).html("" + arrAI[0] + ""); + div = $('
').attr('id', 'ap_' + arrAI[0]); + div.append($('
').attr('class', 'underlined').text(arrAI[0])); $('#action_params').append(div); fFetchActionParams(div, arrAI[0]); } @@ -185,7 +209,7 @@ } }); return $('#but_submit').click(function() { - var acts, ap, ep, err; + var acts, ap, encryptedParams, ep, err; try { if ($('#select_event option:selected').length === 0) { throw new Error('Please create an Event Poller first!'); @@ -197,7 +221,7 @@ $("#event_poller_params tr").each(function() { var name, val; val = $('input', this).val(); - name = $('td:nth-child(2)', this).text(); + name = $(this).children('.key').text(); if (val === '') { throw new Error("Please enter a value for '" + name + "' in the event module!"); } @@ -207,8 +231,8 @@ throw new Error('Please select at least one action or create one!'); } ap = {}; - $('#action_params div').each(function() { - var id, params; + $('> div', $('#action_params')).each(function() { + var encryptedParams, id, params; id = $(this).attr('id').substring(3); params = {}; $('tr', this).each(function() { @@ -220,18 +244,20 @@ } return params[key] = val; }); - return ap[id] = params; + encryptedParams = cryptico.encrypt(JSON.stringify(params), strPublicKey); + return ap[id] = encryptedParams.cipher; }); acts = []; $('#selected_actions .title').each(function() { return acts.push($(this).text()); }); + encryptedParams = cryptico.encrypt(JSON.stringify(ep), strPublicKey); obj = { command: 'forge_rule', payload: { id: $('#input_id').val(), event: $('#select_event option:selected').val(), - event_params: ep, + event_params: encryptedParams.cipher, conditions: {}, actions: acts, action_params: ap @@ -239,6 +265,7 @@ }; obj.payload = JSON.stringify(obj.payload); return $.post('/usercommand', obj).done(function(data) { + console.log('success'); $('#info').text(data.message); return $('#info').attr('class', 'success'); }).fail(function(err) { diff --git a/webpages/handlers/remote-scripts/forge_action_invoker.html b/webpages/handlers/remote-scripts/forge_action_invoker.html index de85dea..9c704a8 100644 --- a/webpages/handlers/remote-scripts/forge_action_invoker.html +++ b/webpages/handlers/remote-scripts/forge_action_invoker.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/webpages/handlers/remote-scripts/forge_event.html b/webpages/handlers/remote-scripts/forge_event.html index de85dea..9c704a8 100644 --- a/webpages/handlers/remote-scripts/forge_event.html +++ b/webpages/handlers/remote-scripts/forge_event.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/webpages/handlers/remote-scripts/forge_event_poller.html b/webpages/handlers/remote-scripts/forge_event_poller.html index de85dea..9c704a8 100644 --- a/webpages/handlers/remote-scripts/forge_event_poller.html +++ b/webpages/handlers/remote-scripts/forge_event_poller.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/webpages/handlers/remote-scripts/forge_rule.html b/webpages/handlers/remote-scripts/forge_rule.html index de85dea..858c181 100644 --- a/webpages/handlers/remote-scripts/forge_rule.html +++ b/webpages/handlers/remote-scripts/forge_rule.html @@ -1 +1,2 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/webpages/handlers/templates/forge_action_invoker.html b/webpages/handlers/templates/forge_action_invoker.html index 227bba8..e59caba 100644 --- a/webpages/handlers/templates/forge_action_invoker.html +++ b/webpages/handlers/templates/forge_action_invoker.html @@ -1,7 +1,7 @@ Action Invoker Name: is public: diff --git a/webpages/handlers/templates/forge_event_poller.html b/webpages/handlers/templates/forge_event_poller.html index 6cfe2d3..a43e5f0 100644 --- a/webpages/handlers/templates/forge_event_poller.html +++ b/webpages/handlers/templates/forge_event_poller.html @@ -1,7 +1,7 @@ Event Poller Name: is public:
diff --git a/webpages/handlers/templates/forge_rule.html b/webpages/handlers/templates/forge_rule.html index 50a36e4..e533c32 100644 --- a/webpages/handlers/templates/forge_rule.html +++ b/webpages/handlers/templates/forge_rule.html @@ -1,6 +1,8 @@ Rule Name:

Event: -
+
+
Required user-specific params:
+


Actions:

diff --git a/webpages/handlers/templates/old_forge_rules.html b/webpages/handlers/templates/old_forge_rules.html deleted file mode 100644 index ed58d2c..0000000 --- a/webpages/handlers/templates/old_forge_rules.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - Forge A Rule - {{{head_requires}}} - - - - - {{{div_menubar}}} -
-
Hi {{user.username}}, forge your own rules!
-

-
-

- - -

- -

-

- -

-
-
-

Available Event Modules:

-
-
-
-

Available Action Modules:

-
-
- -

-
-
- - - - \ No newline at end of file diff --git a/webpages/public/ace-src-min-noconflict/ace.js b/webpages/public/js/ace-src-min-noconflict/ace.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ace.js rename to webpages/public/js/ace-src-min-noconflict/ace.js diff --git a/webpages/public/ace-src-min-noconflict/ext-chromevox.js b/webpages/public/js/ace-src-min-noconflict/ext-chromevox.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-chromevox.js rename to webpages/public/js/ace-src-min-noconflict/ext-chromevox.js diff --git a/webpages/public/ace-src-min-noconflict/ext-elastic_tabstops_lite.js b/webpages/public/js/ace-src-min-noconflict/ext-elastic_tabstops_lite.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-elastic_tabstops_lite.js rename to webpages/public/js/ace-src-min-noconflict/ext-elastic_tabstops_lite.js diff --git a/webpages/public/ace-src-min-noconflict/ext-emmet.js b/webpages/public/js/ace-src-min-noconflict/ext-emmet.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-emmet.js rename to webpages/public/js/ace-src-min-noconflict/ext-emmet.js diff --git a/webpages/public/ace-src-min-noconflict/ext-keybinding_menu.js b/webpages/public/js/ace-src-min-noconflict/ext-keybinding_menu.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-keybinding_menu.js rename to webpages/public/js/ace-src-min-noconflict/ext-keybinding_menu.js diff --git a/webpages/public/ace-src-min-noconflict/ext-language_tools.js b/webpages/public/js/ace-src-min-noconflict/ext-language_tools.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-language_tools.js rename to webpages/public/js/ace-src-min-noconflict/ext-language_tools.js diff --git a/webpages/public/ace-src-min-noconflict/ext-modelist.js b/webpages/public/js/ace-src-min-noconflict/ext-modelist.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-modelist.js rename to webpages/public/js/ace-src-min-noconflict/ext-modelist.js diff --git a/webpages/public/ace-src-min-noconflict/ext-old_ie.js b/webpages/public/js/ace-src-min-noconflict/ext-old_ie.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-old_ie.js rename to webpages/public/js/ace-src-min-noconflict/ext-old_ie.js diff --git a/webpages/public/ace-src-min-noconflict/ext-searchbox.js b/webpages/public/js/ace-src-min-noconflict/ext-searchbox.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-searchbox.js rename to webpages/public/js/ace-src-min-noconflict/ext-searchbox.js diff --git a/webpages/public/ace-src-min-noconflict/ext-settings_menu.js b/webpages/public/js/ace-src-min-noconflict/ext-settings_menu.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-settings_menu.js rename to webpages/public/js/ace-src-min-noconflict/ext-settings_menu.js diff --git a/webpages/public/ace-src-min-noconflict/ext-spellcheck.js b/webpages/public/js/ace-src-min-noconflict/ext-spellcheck.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-spellcheck.js rename to webpages/public/js/ace-src-min-noconflict/ext-spellcheck.js diff --git a/webpages/public/ace-src-min-noconflict/ext-split.js b/webpages/public/js/ace-src-min-noconflict/ext-split.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-split.js rename to webpages/public/js/ace-src-min-noconflict/ext-split.js diff --git a/webpages/public/ace-src-min-noconflict/ext-static_highlight.js b/webpages/public/js/ace-src-min-noconflict/ext-static_highlight.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-static_highlight.js rename to webpages/public/js/ace-src-min-noconflict/ext-static_highlight.js diff --git a/webpages/public/ace-src-min-noconflict/ext-statusbar.js b/webpages/public/js/ace-src-min-noconflict/ext-statusbar.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-statusbar.js rename to webpages/public/js/ace-src-min-noconflict/ext-statusbar.js diff --git a/webpages/public/ace-src-min-noconflict/ext-textarea.js b/webpages/public/js/ace-src-min-noconflict/ext-textarea.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-textarea.js rename to webpages/public/js/ace-src-min-noconflict/ext-textarea.js diff --git a/webpages/public/ace-src-min-noconflict/ext-themelist.js b/webpages/public/js/ace-src-min-noconflict/ext-themelist.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-themelist.js rename to webpages/public/js/ace-src-min-noconflict/ext-themelist.js diff --git a/webpages/public/ace-src-min-noconflict/ext-whitespace.js b/webpages/public/js/ace-src-min-noconflict/ext-whitespace.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/ext-whitespace.js rename to webpages/public/js/ace-src-min-noconflict/ext-whitespace.js diff --git a/webpages/public/ace-src-min-noconflict/keybinding-emacs.js b/webpages/public/js/ace-src-min-noconflict/keybinding-emacs.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/keybinding-emacs.js rename to webpages/public/js/ace-src-min-noconflict/keybinding-emacs.js diff --git a/webpages/public/ace-src-min-noconflict/keybinding-vim.js b/webpages/public/js/ace-src-min-noconflict/keybinding-vim.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/keybinding-vim.js rename to webpages/public/js/ace-src-min-noconflict/keybinding-vim.js diff --git a/webpages/public/ace-src-min-noconflict/mode-abap.js b/webpages/public/js/ace-src-min-noconflict/mode-abap.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-abap.js rename to webpages/public/js/ace-src-min-noconflict/mode-abap.js diff --git a/webpages/public/ace-src-min-noconflict/mode-actionscript.js b/webpages/public/js/ace-src-min-noconflict/mode-actionscript.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-actionscript.js rename to webpages/public/js/ace-src-min-noconflict/mode-actionscript.js diff --git a/webpages/public/ace-src-min-noconflict/mode-ada.js b/webpages/public/js/ace-src-min-noconflict/mode-ada.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-ada.js rename to webpages/public/js/ace-src-min-noconflict/mode-ada.js diff --git a/webpages/public/ace-src-min-noconflict/mode-asciidoc.js b/webpages/public/js/ace-src-min-noconflict/mode-asciidoc.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-asciidoc.js rename to webpages/public/js/ace-src-min-noconflict/mode-asciidoc.js diff --git a/webpages/public/ace-src-min-noconflict/mode-assembly_x86.js b/webpages/public/js/ace-src-min-noconflict/mode-assembly_x86.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-assembly_x86.js rename to webpages/public/js/ace-src-min-noconflict/mode-assembly_x86.js diff --git a/webpages/public/ace-src-min-noconflict/mode-autohotkey.js b/webpages/public/js/ace-src-min-noconflict/mode-autohotkey.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-autohotkey.js rename to webpages/public/js/ace-src-min-noconflict/mode-autohotkey.js diff --git a/webpages/public/ace-src-min-noconflict/mode-batchfile.js b/webpages/public/js/ace-src-min-noconflict/mode-batchfile.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-batchfile.js rename to webpages/public/js/ace-src-min-noconflict/mode-batchfile.js diff --git a/webpages/public/ace-src-min-noconflict/mode-c9search.js b/webpages/public/js/ace-src-min-noconflict/mode-c9search.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-c9search.js rename to webpages/public/js/ace-src-min-noconflict/mode-c9search.js diff --git a/webpages/public/ace-src-min-noconflict/mode-c_cpp.js b/webpages/public/js/ace-src-min-noconflict/mode-c_cpp.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-c_cpp.js rename to webpages/public/js/ace-src-min-noconflict/mode-c_cpp.js diff --git a/webpages/public/ace-src-min-noconflict/mode-clojure.js b/webpages/public/js/ace-src-min-noconflict/mode-clojure.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-clojure.js rename to webpages/public/js/ace-src-min-noconflict/mode-clojure.js diff --git a/webpages/public/ace-src-min-noconflict/mode-cobol.js b/webpages/public/js/ace-src-min-noconflict/mode-cobol.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-cobol.js rename to webpages/public/js/ace-src-min-noconflict/mode-cobol.js diff --git a/webpages/public/ace-src-min-noconflict/mode-coffee.js b/webpages/public/js/ace-src-min-noconflict/mode-coffee.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-coffee.js rename to webpages/public/js/ace-src-min-noconflict/mode-coffee.js diff --git a/webpages/public/ace-src-min-noconflict/mode-coldfusion.js b/webpages/public/js/ace-src-min-noconflict/mode-coldfusion.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-coldfusion.js rename to webpages/public/js/ace-src-min-noconflict/mode-coldfusion.js diff --git a/webpages/public/ace-src-min-noconflict/mode-csharp.js b/webpages/public/js/ace-src-min-noconflict/mode-csharp.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-csharp.js rename to webpages/public/js/ace-src-min-noconflict/mode-csharp.js diff --git a/webpages/public/ace-src-min-noconflict/mode-css.js b/webpages/public/js/ace-src-min-noconflict/mode-css.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-css.js rename to webpages/public/js/ace-src-min-noconflict/mode-css.js diff --git a/webpages/public/ace-src-min-noconflict/mode-curly.js b/webpages/public/js/ace-src-min-noconflict/mode-curly.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-curly.js rename to webpages/public/js/ace-src-min-noconflict/mode-curly.js diff --git a/webpages/public/ace-src-min-noconflict/mode-d.js b/webpages/public/js/ace-src-min-noconflict/mode-d.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-d.js rename to webpages/public/js/ace-src-min-noconflict/mode-d.js diff --git a/webpages/public/ace-src-min-noconflict/mode-dart.js b/webpages/public/js/ace-src-min-noconflict/mode-dart.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-dart.js rename to webpages/public/js/ace-src-min-noconflict/mode-dart.js diff --git a/webpages/public/ace-src-min-noconflict/mode-diff.js b/webpages/public/js/ace-src-min-noconflict/mode-diff.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-diff.js rename to webpages/public/js/ace-src-min-noconflict/mode-diff.js diff --git a/webpages/public/ace-src-min-noconflict/mode-django.js b/webpages/public/js/ace-src-min-noconflict/mode-django.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-django.js rename to webpages/public/js/ace-src-min-noconflict/mode-django.js diff --git a/webpages/public/ace-src-min-noconflict/mode-dot.js b/webpages/public/js/ace-src-min-noconflict/mode-dot.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-dot.js rename to webpages/public/js/ace-src-min-noconflict/mode-dot.js diff --git a/webpages/public/ace-src-min-noconflict/mode-ejs.js b/webpages/public/js/ace-src-min-noconflict/mode-ejs.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-ejs.js rename to webpages/public/js/ace-src-min-noconflict/mode-ejs.js diff --git a/webpages/public/ace-src-min-noconflict/mode-erlang.js b/webpages/public/js/ace-src-min-noconflict/mode-erlang.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-erlang.js rename to webpages/public/js/ace-src-min-noconflict/mode-erlang.js diff --git a/webpages/public/ace-src-min-noconflict/mode-forth.js b/webpages/public/js/ace-src-min-noconflict/mode-forth.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-forth.js rename to webpages/public/js/ace-src-min-noconflict/mode-forth.js diff --git a/webpages/public/ace-src-min-noconflict/mode-ftl.js b/webpages/public/js/ace-src-min-noconflict/mode-ftl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-ftl.js rename to webpages/public/js/ace-src-min-noconflict/mode-ftl.js diff --git a/webpages/public/ace-src-min-noconflict/mode-glsl.js b/webpages/public/js/ace-src-min-noconflict/mode-glsl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-glsl.js rename to webpages/public/js/ace-src-min-noconflict/mode-glsl.js diff --git a/webpages/public/ace-src-min-noconflict/mode-golang.js b/webpages/public/js/ace-src-min-noconflict/mode-golang.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-golang.js rename to webpages/public/js/ace-src-min-noconflict/mode-golang.js diff --git a/webpages/public/ace-src-min-noconflict/mode-groovy.js b/webpages/public/js/ace-src-min-noconflict/mode-groovy.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-groovy.js rename to webpages/public/js/ace-src-min-noconflict/mode-groovy.js diff --git a/webpages/public/ace-src-min-noconflict/mode-haml.js b/webpages/public/js/ace-src-min-noconflict/mode-haml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-haml.js rename to webpages/public/js/ace-src-min-noconflict/mode-haml.js diff --git a/webpages/public/ace-src-min-noconflict/mode-handlebars.js b/webpages/public/js/ace-src-min-noconflict/mode-handlebars.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-handlebars.js rename to webpages/public/js/ace-src-min-noconflict/mode-handlebars.js diff --git a/webpages/public/ace-src-min-noconflict/mode-haskell.js b/webpages/public/js/ace-src-min-noconflict/mode-haskell.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-haskell.js rename to webpages/public/js/ace-src-min-noconflict/mode-haskell.js diff --git a/webpages/public/ace-src-min-noconflict/mode-haxe.js b/webpages/public/js/ace-src-min-noconflict/mode-haxe.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-haxe.js rename to webpages/public/js/ace-src-min-noconflict/mode-haxe.js diff --git a/webpages/public/ace-src-min-noconflict/mode-html.js b/webpages/public/js/ace-src-min-noconflict/mode-html.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-html.js rename to webpages/public/js/ace-src-min-noconflict/mode-html.js diff --git a/webpages/public/ace-src-min-noconflict/mode-html_completions.js b/webpages/public/js/ace-src-min-noconflict/mode-html_completions.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-html_completions.js rename to webpages/public/js/ace-src-min-noconflict/mode-html_completions.js diff --git a/webpages/public/ace-src-min-noconflict/mode-html_ruby.js b/webpages/public/js/ace-src-min-noconflict/mode-html_ruby.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-html_ruby.js rename to webpages/public/js/ace-src-min-noconflict/mode-html_ruby.js diff --git a/webpages/public/ace-src-min-noconflict/mode-ini.js b/webpages/public/js/ace-src-min-noconflict/mode-ini.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-ini.js rename to webpages/public/js/ace-src-min-noconflict/mode-ini.js diff --git a/webpages/public/ace-src-min-noconflict/mode-jack.js b/webpages/public/js/ace-src-min-noconflict/mode-jack.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-jack.js rename to webpages/public/js/ace-src-min-noconflict/mode-jack.js diff --git a/webpages/public/ace-src-min-noconflict/mode-jade.js b/webpages/public/js/ace-src-min-noconflict/mode-jade.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-jade.js rename to webpages/public/js/ace-src-min-noconflict/mode-jade.js diff --git a/webpages/public/ace-src-min-noconflict/mode-java.js b/webpages/public/js/ace-src-min-noconflict/mode-java.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-java.js rename to webpages/public/js/ace-src-min-noconflict/mode-java.js diff --git a/webpages/public/ace-src-min-noconflict/mode-javascript.js b/webpages/public/js/ace-src-min-noconflict/mode-javascript.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-javascript.js rename to webpages/public/js/ace-src-min-noconflict/mode-javascript.js diff --git a/webpages/public/ace-src-min-noconflict/mode-json.js b/webpages/public/js/ace-src-min-noconflict/mode-json.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-json.js rename to webpages/public/js/ace-src-min-noconflict/mode-json.js diff --git a/webpages/public/ace-src-min-noconflict/mode-jsoniq.js b/webpages/public/js/ace-src-min-noconflict/mode-jsoniq.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-jsoniq.js rename to webpages/public/js/ace-src-min-noconflict/mode-jsoniq.js diff --git a/webpages/public/ace-src-min-noconflict/mode-jsp.js b/webpages/public/js/ace-src-min-noconflict/mode-jsp.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-jsp.js rename to webpages/public/js/ace-src-min-noconflict/mode-jsp.js diff --git a/webpages/public/ace-src-min-noconflict/mode-jsx.js b/webpages/public/js/ace-src-min-noconflict/mode-jsx.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-jsx.js rename to webpages/public/js/ace-src-min-noconflict/mode-jsx.js diff --git a/webpages/public/ace-src-min-noconflict/mode-julia.js b/webpages/public/js/ace-src-min-noconflict/mode-julia.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-julia.js rename to webpages/public/js/ace-src-min-noconflict/mode-julia.js diff --git a/webpages/public/ace-src-min-noconflict/mode-latex.js b/webpages/public/js/ace-src-min-noconflict/mode-latex.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-latex.js rename to webpages/public/js/ace-src-min-noconflict/mode-latex.js diff --git a/webpages/public/ace-src-min-noconflict/mode-less.js b/webpages/public/js/ace-src-min-noconflict/mode-less.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-less.js rename to webpages/public/js/ace-src-min-noconflict/mode-less.js diff --git a/webpages/public/ace-src-min-noconflict/mode-liquid.js b/webpages/public/js/ace-src-min-noconflict/mode-liquid.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-liquid.js rename to webpages/public/js/ace-src-min-noconflict/mode-liquid.js diff --git a/webpages/public/ace-src-min-noconflict/mode-lisp.js b/webpages/public/js/ace-src-min-noconflict/mode-lisp.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-lisp.js rename to webpages/public/js/ace-src-min-noconflict/mode-lisp.js diff --git a/webpages/public/ace-src-min-noconflict/mode-livescript.js b/webpages/public/js/ace-src-min-noconflict/mode-livescript.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-livescript.js rename to webpages/public/js/ace-src-min-noconflict/mode-livescript.js diff --git a/webpages/public/ace-src-min-noconflict/mode-logiql.js b/webpages/public/js/ace-src-min-noconflict/mode-logiql.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-logiql.js rename to webpages/public/js/ace-src-min-noconflict/mode-logiql.js diff --git a/webpages/public/ace-src-min-noconflict/mode-lsl.js b/webpages/public/js/ace-src-min-noconflict/mode-lsl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-lsl.js rename to webpages/public/js/ace-src-min-noconflict/mode-lsl.js diff --git a/webpages/public/ace-src-min-noconflict/mode-lua.js b/webpages/public/js/ace-src-min-noconflict/mode-lua.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-lua.js rename to webpages/public/js/ace-src-min-noconflict/mode-lua.js diff --git a/webpages/public/ace-src-min-noconflict/mode-luapage.js b/webpages/public/js/ace-src-min-noconflict/mode-luapage.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-luapage.js rename to webpages/public/js/ace-src-min-noconflict/mode-luapage.js diff --git a/webpages/public/ace-src-min-noconflict/mode-lucene.js b/webpages/public/js/ace-src-min-noconflict/mode-lucene.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-lucene.js rename to webpages/public/js/ace-src-min-noconflict/mode-lucene.js diff --git a/webpages/public/ace-src-min-noconflict/mode-makefile.js b/webpages/public/js/ace-src-min-noconflict/mode-makefile.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-makefile.js rename to webpages/public/js/ace-src-min-noconflict/mode-makefile.js diff --git a/webpages/public/ace-src-min-noconflict/mode-markdown.js b/webpages/public/js/ace-src-min-noconflict/mode-markdown.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-markdown.js rename to webpages/public/js/ace-src-min-noconflict/mode-markdown.js diff --git a/webpages/public/ace-src-min-noconflict/mode-matlab.js b/webpages/public/js/ace-src-min-noconflict/mode-matlab.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-matlab.js rename to webpages/public/js/ace-src-min-noconflict/mode-matlab.js diff --git a/webpages/public/ace-src-min-noconflict/mode-mushcode.js b/webpages/public/js/ace-src-min-noconflict/mode-mushcode.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-mushcode.js rename to webpages/public/js/ace-src-min-noconflict/mode-mushcode.js diff --git a/webpages/public/ace-src-min-noconflict/mode-mushcode_high_rules.js b/webpages/public/js/ace-src-min-noconflict/mode-mushcode_high_rules.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-mushcode_high_rules.js rename to webpages/public/js/ace-src-min-noconflict/mode-mushcode_high_rules.js diff --git a/webpages/public/ace-src-min-noconflict/mode-mysql.js b/webpages/public/js/ace-src-min-noconflict/mode-mysql.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-mysql.js rename to webpages/public/js/ace-src-min-noconflict/mode-mysql.js diff --git a/webpages/public/ace-src-min-noconflict/mode-nix.js b/webpages/public/js/ace-src-min-noconflict/mode-nix.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-nix.js rename to webpages/public/js/ace-src-min-noconflict/mode-nix.js diff --git a/webpages/public/ace-src-min-noconflict/mode-objectivec.js b/webpages/public/js/ace-src-min-noconflict/mode-objectivec.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-objectivec.js rename to webpages/public/js/ace-src-min-noconflict/mode-objectivec.js diff --git a/webpages/public/ace-src-min-noconflict/mode-ocaml.js b/webpages/public/js/ace-src-min-noconflict/mode-ocaml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-ocaml.js rename to webpages/public/js/ace-src-min-noconflict/mode-ocaml.js diff --git a/webpages/public/ace-src-min-noconflict/mode-pascal.js b/webpages/public/js/ace-src-min-noconflict/mode-pascal.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-pascal.js rename to webpages/public/js/ace-src-min-noconflict/mode-pascal.js diff --git a/webpages/public/ace-src-min-noconflict/mode-perl.js b/webpages/public/js/ace-src-min-noconflict/mode-perl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-perl.js rename to webpages/public/js/ace-src-min-noconflict/mode-perl.js diff --git a/webpages/public/ace-src-min-noconflict/mode-pgsql.js b/webpages/public/js/ace-src-min-noconflict/mode-pgsql.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-pgsql.js rename to webpages/public/js/ace-src-min-noconflict/mode-pgsql.js diff --git a/webpages/public/ace-src-min-noconflict/mode-php.js b/webpages/public/js/ace-src-min-noconflict/mode-php.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-php.js rename to webpages/public/js/ace-src-min-noconflict/mode-php.js diff --git a/webpages/public/ace-src-min-noconflict/mode-plain_text.js b/webpages/public/js/ace-src-min-noconflict/mode-plain_text.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-plain_text.js rename to webpages/public/js/ace-src-min-noconflict/mode-plain_text.js diff --git a/webpages/public/ace-src-min-noconflict/mode-powershell.js b/webpages/public/js/ace-src-min-noconflict/mode-powershell.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-powershell.js rename to webpages/public/js/ace-src-min-noconflict/mode-powershell.js diff --git a/webpages/public/ace-src-min-noconflict/mode-prolog.js b/webpages/public/js/ace-src-min-noconflict/mode-prolog.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-prolog.js rename to webpages/public/js/ace-src-min-noconflict/mode-prolog.js diff --git a/webpages/public/ace-src-min-noconflict/mode-properties.js b/webpages/public/js/ace-src-min-noconflict/mode-properties.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-properties.js rename to webpages/public/js/ace-src-min-noconflict/mode-properties.js diff --git a/webpages/public/ace-src-min-noconflict/mode-protobuf.js b/webpages/public/js/ace-src-min-noconflict/mode-protobuf.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-protobuf.js rename to webpages/public/js/ace-src-min-noconflict/mode-protobuf.js diff --git a/webpages/public/ace-src-min-noconflict/mode-python.js b/webpages/public/js/ace-src-min-noconflict/mode-python.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-python.js rename to webpages/public/js/ace-src-min-noconflict/mode-python.js diff --git a/webpages/public/ace-src-min-noconflict/mode-r.js b/webpages/public/js/ace-src-min-noconflict/mode-r.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-r.js rename to webpages/public/js/ace-src-min-noconflict/mode-r.js diff --git a/webpages/public/ace-src-min-noconflict/mode-rdoc.js b/webpages/public/js/ace-src-min-noconflict/mode-rdoc.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-rdoc.js rename to webpages/public/js/ace-src-min-noconflict/mode-rdoc.js diff --git a/webpages/public/ace-src-min-noconflict/mode-rhtml.js b/webpages/public/js/ace-src-min-noconflict/mode-rhtml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-rhtml.js rename to webpages/public/js/ace-src-min-noconflict/mode-rhtml.js diff --git a/webpages/public/ace-src-min-noconflict/mode-ruby.js b/webpages/public/js/ace-src-min-noconflict/mode-ruby.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-ruby.js rename to webpages/public/js/ace-src-min-noconflict/mode-ruby.js diff --git a/webpages/public/ace-src-min-noconflict/mode-rust.js b/webpages/public/js/ace-src-min-noconflict/mode-rust.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-rust.js rename to webpages/public/js/ace-src-min-noconflict/mode-rust.js diff --git a/webpages/public/ace-src-min-noconflict/mode-sass.js b/webpages/public/js/ace-src-min-noconflict/mode-sass.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-sass.js rename to webpages/public/js/ace-src-min-noconflict/mode-sass.js diff --git a/webpages/public/ace-src-min-noconflict/mode-scad.js b/webpages/public/js/ace-src-min-noconflict/mode-scad.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-scad.js rename to webpages/public/js/ace-src-min-noconflict/mode-scad.js diff --git a/webpages/public/ace-src-min-noconflict/mode-scala.js b/webpages/public/js/ace-src-min-noconflict/mode-scala.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-scala.js rename to webpages/public/js/ace-src-min-noconflict/mode-scala.js diff --git a/webpages/public/ace-src-min-noconflict/mode-scheme.js b/webpages/public/js/ace-src-min-noconflict/mode-scheme.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-scheme.js rename to webpages/public/js/ace-src-min-noconflict/mode-scheme.js diff --git a/webpages/public/ace-src-min-noconflict/mode-scss.js b/webpages/public/js/ace-src-min-noconflict/mode-scss.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-scss.js rename to webpages/public/js/ace-src-min-noconflict/mode-scss.js diff --git a/webpages/public/ace-src-min-noconflict/mode-sh.js b/webpages/public/js/ace-src-min-noconflict/mode-sh.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-sh.js rename to webpages/public/js/ace-src-min-noconflict/mode-sh.js diff --git a/webpages/public/ace-src-min-noconflict/mode-sjs.js b/webpages/public/js/ace-src-min-noconflict/mode-sjs.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-sjs.js rename to webpages/public/js/ace-src-min-noconflict/mode-sjs.js diff --git a/webpages/public/ace-src-min-noconflict/mode-snippets.js b/webpages/public/js/ace-src-min-noconflict/mode-snippets.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-snippets.js rename to webpages/public/js/ace-src-min-noconflict/mode-snippets.js diff --git a/webpages/public/ace-src-min-noconflict/mode-soy_template.js b/webpages/public/js/ace-src-min-noconflict/mode-soy_template.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-soy_template.js rename to webpages/public/js/ace-src-min-noconflict/mode-soy_template.js diff --git a/webpages/public/ace-src-min-noconflict/mode-space.js b/webpages/public/js/ace-src-min-noconflict/mode-space.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-space.js rename to webpages/public/js/ace-src-min-noconflict/mode-space.js diff --git a/webpages/public/ace-src-min-noconflict/mode-sql.js b/webpages/public/js/ace-src-min-noconflict/mode-sql.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-sql.js rename to webpages/public/js/ace-src-min-noconflict/mode-sql.js diff --git a/webpages/public/ace-src-min-noconflict/mode-stylus.js b/webpages/public/js/ace-src-min-noconflict/mode-stylus.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-stylus.js rename to webpages/public/js/ace-src-min-noconflict/mode-stylus.js diff --git a/webpages/public/ace-src-min-noconflict/mode-svg.js b/webpages/public/js/ace-src-min-noconflict/mode-svg.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-svg.js rename to webpages/public/js/ace-src-min-noconflict/mode-svg.js diff --git a/webpages/public/ace-src-min-noconflict/mode-tcl.js b/webpages/public/js/ace-src-min-noconflict/mode-tcl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-tcl.js rename to webpages/public/js/ace-src-min-noconflict/mode-tcl.js diff --git a/webpages/public/ace-src-min-noconflict/mode-tex.js b/webpages/public/js/ace-src-min-noconflict/mode-tex.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-tex.js rename to webpages/public/js/ace-src-min-noconflict/mode-tex.js diff --git a/webpages/public/ace-src-min-noconflict/mode-text.js b/webpages/public/js/ace-src-min-noconflict/mode-text.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-text.js rename to webpages/public/js/ace-src-min-noconflict/mode-text.js diff --git a/webpages/public/ace-src-min-noconflict/mode-textile.js b/webpages/public/js/ace-src-min-noconflict/mode-textile.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-textile.js rename to webpages/public/js/ace-src-min-noconflict/mode-textile.js diff --git a/webpages/public/ace-src-min-noconflict/mode-toml.js b/webpages/public/js/ace-src-min-noconflict/mode-toml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-toml.js rename to webpages/public/js/ace-src-min-noconflict/mode-toml.js diff --git a/webpages/public/ace-src-min-noconflict/mode-twig.js b/webpages/public/js/ace-src-min-noconflict/mode-twig.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-twig.js rename to webpages/public/js/ace-src-min-noconflict/mode-twig.js diff --git a/webpages/public/ace-src-min-noconflict/mode-typescript.js b/webpages/public/js/ace-src-min-noconflict/mode-typescript.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-typescript.js rename to webpages/public/js/ace-src-min-noconflict/mode-typescript.js diff --git a/webpages/public/ace-src-min-noconflict/mode-vbscript.js b/webpages/public/js/ace-src-min-noconflict/mode-vbscript.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-vbscript.js rename to webpages/public/js/ace-src-min-noconflict/mode-vbscript.js diff --git a/webpages/public/ace-src-min-noconflict/mode-velocity.js b/webpages/public/js/ace-src-min-noconflict/mode-velocity.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-velocity.js rename to webpages/public/js/ace-src-min-noconflict/mode-velocity.js diff --git a/webpages/public/ace-src-min-noconflict/mode-verilog.js b/webpages/public/js/ace-src-min-noconflict/mode-verilog.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-verilog.js rename to webpages/public/js/ace-src-min-noconflict/mode-verilog.js diff --git a/webpages/public/ace-src-min-noconflict/mode-vhdl.js b/webpages/public/js/ace-src-min-noconflict/mode-vhdl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-vhdl.js rename to webpages/public/js/ace-src-min-noconflict/mode-vhdl.js diff --git a/webpages/public/ace-src-min-noconflict/mode-xml.js b/webpages/public/js/ace-src-min-noconflict/mode-xml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-xml.js rename to webpages/public/js/ace-src-min-noconflict/mode-xml.js diff --git a/webpages/public/ace-src-min-noconflict/mode-xquery.js b/webpages/public/js/ace-src-min-noconflict/mode-xquery.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-xquery.js rename to webpages/public/js/ace-src-min-noconflict/mode-xquery.js diff --git a/webpages/public/ace-src-min-noconflict/mode-yaml.js b/webpages/public/js/ace-src-min-noconflict/mode-yaml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/mode-yaml.js rename to webpages/public/js/ace-src-min-noconflict/mode-yaml.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/abap.js b/webpages/public/js/ace-src-min-noconflict/snippets/abap.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/abap.js rename to webpages/public/js/ace-src-min-noconflict/snippets/abap.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/actionscript.js b/webpages/public/js/ace-src-min-noconflict/snippets/actionscript.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/actionscript.js rename to webpages/public/js/ace-src-min-noconflict/snippets/actionscript.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/ada.js b/webpages/public/js/ace-src-min-noconflict/snippets/ada.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/ada.js rename to webpages/public/js/ace-src-min-noconflict/snippets/ada.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/asciidoc.js b/webpages/public/js/ace-src-min-noconflict/snippets/asciidoc.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/asciidoc.js rename to webpages/public/js/ace-src-min-noconflict/snippets/asciidoc.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/assembly_x86.js b/webpages/public/js/ace-src-min-noconflict/snippets/assembly_x86.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/assembly_x86.js rename to webpages/public/js/ace-src-min-noconflict/snippets/assembly_x86.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/autohotkey.js b/webpages/public/js/ace-src-min-noconflict/snippets/autohotkey.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/autohotkey.js rename to webpages/public/js/ace-src-min-noconflict/snippets/autohotkey.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/batchfile.js b/webpages/public/js/ace-src-min-noconflict/snippets/batchfile.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/batchfile.js rename to webpages/public/js/ace-src-min-noconflict/snippets/batchfile.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/c9search.js b/webpages/public/js/ace-src-min-noconflict/snippets/c9search.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/c9search.js rename to webpages/public/js/ace-src-min-noconflict/snippets/c9search.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/c_cpp.js b/webpages/public/js/ace-src-min-noconflict/snippets/c_cpp.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/c_cpp.js rename to webpages/public/js/ace-src-min-noconflict/snippets/c_cpp.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/clojure.js b/webpages/public/js/ace-src-min-noconflict/snippets/clojure.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/clojure.js rename to webpages/public/js/ace-src-min-noconflict/snippets/clojure.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/cobol.js b/webpages/public/js/ace-src-min-noconflict/snippets/cobol.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/cobol.js rename to webpages/public/js/ace-src-min-noconflict/snippets/cobol.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/coffee.js b/webpages/public/js/ace-src-min-noconflict/snippets/coffee.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/coffee.js rename to webpages/public/js/ace-src-min-noconflict/snippets/coffee.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/coldfusion.js b/webpages/public/js/ace-src-min-noconflict/snippets/coldfusion.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/coldfusion.js rename to webpages/public/js/ace-src-min-noconflict/snippets/coldfusion.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/csharp.js b/webpages/public/js/ace-src-min-noconflict/snippets/csharp.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/csharp.js rename to webpages/public/js/ace-src-min-noconflict/snippets/csharp.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/css.js b/webpages/public/js/ace-src-min-noconflict/snippets/css.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/css.js rename to webpages/public/js/ace-src-min-noconflict/snippets/css.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/curly.js b/webpages/public/js/ace-src-min-noconflict/snippets/curly.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/curly.js rename to webpages/public/js/ace-src-min-noconflict/snippets/curly.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/d.js b/webpages/public/js/ace-src-min-noconflict/snippets/d.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/d.js rename to webpages/public/js/ace-src-min-noconflict/snippets/d.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/dart.js b/webpages/public/js/ace-src-min-noconflict/snippets/dart.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/dart.js rename to webpages/public/js/ace-src-min-noconflict/snippets/dart.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/diff.js b/webpages/public/js/ace-src-min-noconflict/snippets/diff.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/diff.js rename to webpages/public/js/ace-src-min-noconflict/snippets/diff.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/django.js b/webpages/public/js/ace-src-min-noconflict/snippets/django.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/django.js rename to webpages/public/js/ace-src-min-noconflict/snippets/django.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/dot.js b/webpages/public/js/ace-src-min-noconflict/snippets/dot.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/dot.js rename to webpages/public/js/ace-src-min-noconflict/snippets/dot.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/ejs.js b/webpages/public/js/ace-src-min-noconflict/snippets/ejs.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/ejs.js rename to webpages/public/js/ace-src-min-noconflict/snippets/ejs.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/erlang.js b/webpages/public/js/ace-src-min-noconflict/snippets/erlang.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/erlang.js rename to webpages/public/js/ace-src-min-noconflict/snippets/erlang.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/forth.js b/webpages/public/js/ace-src-min-noconflict/snippets/forth.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/forth.js rename to webpages/public/js/ace-src-min-noconflict/snippets/forth.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/ftl.js b/webpages/public/js/ace-src-min-noconflict/snippets/ftl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/ftl.js rename to webpages/public/js/ace-src-min-noconflict/snippets/ftl.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/glsl.js b/webpages/public/js/ace-src-min-noconflict/snippets/glsl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/glsl.js rename to webpages/public/js/ace-src-min-noconflict/snippets/glsl.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/golang.js b/webpages/public/js/ace-src-min-noconflict/snippets/golang.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/golang.js rename to webpages/public/js/ace-src-min-noconflict/snippets/golang.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/groovy.js b/webpages/public/js/ace-src-min-noconflict/snippets/groovy.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/groovy.js rename to webpages/public/js/ace-src-min-noconflict/snippets/groovy.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/haml.js b/webpages/public/js/ace-src-min-noconflict/snippets/haml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/haml.js rename to webpages/public/js/ace-src-min-noconflict/snippets/haml.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/handlebars.js b/webpages/public/js/ace-src-min-noconflict/snippets/handlebars.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/handlebars.js rename to webpages/public/js/ace-src-min-noconflict/snippets/handlebars.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/haskell.js b/webpages/public/js/ace-src-min-noconflict/snippets/haskell.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/haskell.js rename to webpages/public/js/ace-src-min-noconflict/snippets/haskell.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/haxe.js b/webpages/public/js/ace-src-min-noconflict/snippets/haxe.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/haxe.js rename to webpages/public/js/ace-src-min-noconflict/snippets/haxe.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/html.js b/webpages/public/js/ace-src-min-noconflict/snippets/html.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/html.js rename to webpages/public/js/ace-src-min-noconflict/snippets/html.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/html_completions.js b/webpages/public/js/ace-src-min-noconflict/snippets/html_completions.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/html_completions.js rename to webpages/public/js/ace-src-min-noconflict/snippets/html_completions.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/html_ruby.js b/webpages/public/js/ace-src-min-noconflict/snippets/html_ruby.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/html_ruby.js rename to webpages/public/js/ace-src-min-noconflict/snippets/html_ruby.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/ini.js b/webpages/public/js/ace-src-min-noconflict/snippets/ini.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/ini.js rename to webpages/public/js/ace-src-min-noconflict/snippets/ini.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/jack.js b/webpages/public/js/ace-src-min-noconflict/snippets/jack.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/jack.js rename to webpages/public/js/ace-src-min-noconflict/snippets/jack.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/jade.js b/webpages/public/js/ace-src-min-noconflict/snippets/jade.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/jade.js rename to webpages/public/js/ace-src-min-noconflict/snippets/jade.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/java.js b/webpages/public/js/ace-src-min-noconflict/snippets/java.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/java.js rename to webpages/public/js/ace-src-min-noconflict/snippets/java.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/javascript.js b/webpages/public/js/ace-src-min-noconflict/snippets/javascript.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/javascript.js rename to webpages/public/js/ace-src-min-noconflict/snippets/javascript.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/json.js b/webpages/public/js/ace-src-min-noconflict/snippets/json.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/json.js rename to webpages/public/js/ace-src-min-noconflict/snippets/json.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/jsoniq.js b/webpages/public/js/ace-src-min-noconflict/snippets/jsoniq.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/jsoniq.js rename to webpages/public/js/ace-src-min-noconflict/snippets/jsoniq.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/jsp.js b/webpages/public/js/ace-src-min-noconflict/snippets/jsp.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/jsp.js rename to webpages/public/js/ace-src-min-noconflict/snippets/jsp.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/jsx.js b/webpages/public/js/ace-src-min-noconflict/snippets/jsx.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/jsx.js rename to webpages/public/js/ace-src-min-noconflict/snippets/jsx.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/julia.js b/webpages/public/js/ace-src-min-noconflict/snippets/julia.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/julia.js rename to webpages/public/js/ace-src-min-noconflict/snippets/julia.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/latex.js b/webpages/public/js/ace-src-min-noconflict/snippets/latex.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/latex.js rename to webpages/public/js/ace-src-min-noconflict/snippets/latex.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/less.js b/webpages/public/js/ace-src-min-noconflict/snippets/less.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/less.js rename to webpages/public/js/ace-src-min-noconflict/snippets/less.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/liquid.js b/webpages/public/js/ace-src-min-noconflict/snippets/liquid.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/liquid.js rename to webpages/public/js/ace-src-min-noconflict/snippets/liquid.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/lisp.js b/webpages/public/js/ace-src-min-noconflict/snippets/lisp.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/lisp.js rename to webpages/public/js/ace-src-min-noconflict/snippets/lisp.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/livescript.js b/webpages/public/js/ace-src-min-noconflict/snippets/livescript.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/livescript.js rename to webpages/public/js/ace-src-min-noconflict/snippets/livescript.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/logiql.js b/webpages/public/js/ace-src-min-noconflict/snippets/logiql.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/logiql.js rename to webpages/public/js/ace-src-min-noconflict/snippets/logiql.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/lsl.js b/webpages/public/js/ace-src-min-noconflict/snippets/lsl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/lsl.js rename to webpages/public/js/ace-src-min-noconflict/snippets/lsl.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/lua.js b/webpages/public/js/ace-src-min-noconflict/snippets/lua.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/lua.js rename to webpages/public/js/ace-src-min-noconflict/snippets/lua.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/luapage.js b/webpages/public/js/ace-src-min-noconflict/snippets/luapage.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/luapage.js rename to webpages/public/js/ace-src-min-noconflict/snippets/luapage.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/lucene.js b/webpages/public/js/ace-src-min-noconflict/snippets/lucene.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/lucene.js rename to webpages/public/js/ace-src-min-noconflict/snippets/lucene.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/makefile.js b/webpages/public/js/ace-src-min-noconflict/snippets/makefile.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/makefile.js rename to webpages/public/js/ace-src-min-noconflict/snippets/makefile.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/markdown.js b/webpages/public/js/ace-src-min-noconflict/snippets/markdown.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/markdown.js rename to webpages/public/js/ace-src-min-noconflict/snippets/markdown.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/matlab.js b/webpages/public/js/ace-src-min-noconflict/snippets/matlab.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/matlab.js rename to webpages/public/js/ace-src-min-noconflict/snippets/matlab.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/mushcode.js b/webpages/public/js/ace-src-min-noconflict/snippets/mushcode.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/mushcode.js rename to webpages/public/js/ace-src-min-noconflict/snippets/mushcode.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/mushcode_high_rules.js b/webpages/public/js/ace-src-min-noconflict/snippets/mushcode_high_rules.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/mushcode_high_rules.js rename to webpages/public/js/ace-src-min-noconflict/snippets/mushcode_high_rules.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/mysql.js b/webpages/public/js/ace-src-min-noconflict/snippets/mysql.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/mysql.js rename to webpages/public/js/ace-src-min-noconflict/snippets/mysql.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/nix.js b/webpages/public/js/ace-src-min-noconflict/snippets/nix.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/nix.js rename to webpages/public/js/ace-src-min-noconflict/snippets/nix.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/objectivec.js b/webpages/public/js/ace-src-min-noconflict/snippets/objectivec.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/objectivec.js rename to webpages/public/js/ace-src-min-noconflict/snippets/objectivec.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/ocaml.js b/webpages/public/js/ace-src-min-noconflict/snippets/ocaml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/ocaml.js rename to webpages/public/js/ace-src-min-noconflict/snippets/ocaml.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/pascal.js b/webpages/public/js/ace-src-min-noconflict/snippets/pascal.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/pascal.js rename to webpages/public/js/ace-src-min-noconflict/snippets/pascal.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/perl.js b/webpages/public/js/ace-src-min-noconflict/snippets/perl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/perl.js rename to webpages/public/js/ace-src-min-noconflict/snippets/perl.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/pgsql.js b/webpages/public/js/ace-src-min-noconflict/snippets/pgsql.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/pgsql.js rename to webpages/public/js/ace-src-min-noconflict/snippets/pgsql.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/php.js b/webpages/public/js/ace-src-min-noconflict/snippets/php.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/php.js rename to webpages/public/js/ace-src-min-noconflict/snippets/php.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/plain_text.js b/webpages/public/js/ace-src-min-noconflict/snippets/plain_text.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/plain_text.js rename to webpages/public/js/ace-src-min-noconflict/snippets/plain_text.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/powershell.js b/webpages/public/js/ace-src-min-noconflict/snippets/powershell.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/powershell.js rename to webpages/public/js/ace-src-min-noconflict/snippets/powershell.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/prolog.js b/webpages/public/js/ace-src-min-noconflict/snippets/prolog.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/prolog.js rename to webpages/public/js/ace-src-min-noconflict/snippets/prolog.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/properties.js b/webpages/public/js/ace-src-min-noconflict/snippets/properties.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/properties.js rename to webpages/public/js/ace-src-min-noconflict/snippets/properties.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/protobuf.js b/webpages/public/js/ace-src-min-noconflict/snippets/protobuf.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/protobuf.js rename to webpages/public/js/ace-src-min-noconflict/snippets/protobuf.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/python.js b/webpages/public/js/ace-src-min-noconflict/snippets/python.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/python.js rename to webpages/public/js/ace-src-min-noconflict/snippets/python.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/r.js b/webpages/public/js/ace-src-min-noconflict/snippets/r.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/r.js rename to webpages/public/js/ace-src-min-noconflict/snippets/r.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/rdoc.js b/webpages/public/js/ace-src-min-noconflict/snippets/rdoc.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/rdoc.js rename to webpages/public/js/ace-src-min-noconflict/snippets/rdoc.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/rhtml.js b/webpages/public/js/ace-src-min-noconflict/snippets/rhtml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/rhtml.js rename to webpages/public/js/ace-src-min-noconflict/snippets/rhtml.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/ruby.js b/webpages/public/js/ace-src-min-noconflict/snippets/ruby.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/ruby.js rename to webpages/public/js/ace-src-min-noconflict/snippets/ruby.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/rust.js b/webpages/public/js/ace-src-min-noconflict/snippets/rust.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/rust.js rename to webpages/public/js/ace-src-min-noconflict/snippets/rust.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/sass.js b/webpages/public/js/ace-src-min-noconflict/snippets/sass.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/sass.js rename to webpages/public/js/ace-src-min-noconflict/snippets/sass.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/scad.js b/webpages/public/js/ace-src-min-noconflict/snippets/scad.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/scad.js rename to webpages/public/js/ace-src-min-noconflict/snippets/scad.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/scala.js b/webpages/public/js/ace-src-min-noconflict/snippets/scala.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/scala.js rename to webpages/public/js/ace-src-min-noconflict/snippets/scala.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/scheme.js b/webpages/public/js/ace-src-min-noconflict/snippets/scheme.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/scheme.js rename to webpages/public/js/ace-src-min-noconflict/snippets/scheme.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/scss.js b/webpages/public/js/ace-src-min-noconflict/snippets/scss.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/scss.js rename to webpages/public/js/ace-src-min-noconflict/snippets/scss.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/sh.js b/webpages/public/js/ace-src-min-noconflict/snippets/sh.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/sh.js rename to webpages/public/js/ace-src-min-noconflict/snippets/sh.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/sjs.js b/webpages/public/js/ace-src-min-noconflict/snippets/sjs.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/sjs.js rename to webpages/public/js/ace-src-min-noconflict/snippets/sjs.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/snippets.js b/webpages/public/js/ace-src-min-noconflict/snippets/snippets.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/snippets.js rename to webpages/public/js/ace-src-min-noconflict/snippets/snippets.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/soy_template.js b/webpages/public/js/ace-src-min-noconflict/snippets/soy_template.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/soy_template.js rename to webpages/public/js/ace-src-min-noconflict/snippets/soy_template.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/space.js b/webpages/public/js/ace-src-min-noconflict/snippets/space.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/space.js rename to webpages/public/js/ace-src-min-noconflict/snippets/space.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/sql.js b/webpages/public/js/ace-src-min-noconflict/snippets/sql.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/sql.js rename to webpages/public/js/ace-src-min-noconflict/snippets/sql.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/stylus.js b/webpages/public/js/ace-src-min-noconflict/snippets/stylus.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/stylus.js rename to webpages/public/js/ace-src-min-noconflict/snippets/stylus.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/svg.js b/webpages/public/js/ace-src-min-noconflict/snippets/svg.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/svg.js rename to webpages/public/js/ace-src-min-noconflict/snippets/svg.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/tcl.js b/webpages/public/js/ace-src-min-noconflict/snippets/tcl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/tcl.js rename to webpages/public/js/ace-src-min-noconflict/snippets/tcl.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/tex.js b/webpages/public/js/ace-src-min-noconflict/snippets/tex.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/tex.js rename to webpages/public/js/ace-src-min-noconflict/snippets/tex.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/text.js b/webpages/public/js/ace-src-min-noconflict/snippets/text.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/text.js rename to webpages/public/js/ace-src-min-noconflict/snippets/text.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/textile.js b/webpages/public/js/ace-src-min-noconflict/snippets/textile.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/textile.js rename to webpages/public/js/ace-src-min-noconflict/snippets/textile.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/toml.js b/webpages/public/js/ace-src-min-noconflict/snippets/toml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/toml.js rename to webpages/public/js/ace-src-min-noconflict/snippets/toml.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/twig.js b/webpages/public/js/ace-src-min-noconflict/snippets/twig.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/twig.js rename to webpages/public/js/ace-src-min-noconflict/snippets/twig.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/typescript.js b/webpages/public/js/ace-src-min-noconflict/snippets/typescript.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/typescript.js rename to webpages/public/js/ace-src-min-noconflict/snippets/typescript.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/vbscript.js b/webpages/public/js/ace-src-min-noconflict/snippets/vbscript.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/vbscript.js rename to webpages/public/js/ace-src-min-noconflict/snippets/vbscript.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/velocity.js b/webpages/public/js/ace-src-min-noconflict/snippets/velocity.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/velocity.js rename to webpages/public/js/ace-src-min-noconflict/snippets/velocity.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/verilog.js b/webpages/public/js/ace-src-min-noconflict/snippets/verilog.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/verilog.js rename to webpages/public/js/ace-src-min-noconflict/snippets/verilog.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/vhdl.js b/webpages/public/js/ace-src-min-noconflict/snippets/vhdl.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/vhdl.js rename to webpages/public/js/ace-src-min-noconflict/snippets/vhdl.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/xml.js b/webpages/public/js/ace-src-min-noconflict/snippets/xml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/xml.js rename to webpages/public/js/ace-src-min-noconflict/snippets/xml.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/xquery.js b/webpages/public/js/ace-src-min-noconflict/snippets/xquery.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/xquery.js rename to webpages/public/js/ace-src-min-noconflict/snippets/xquery.js diff --git a/webpages/public/ace-src-min-noconflict/snippets/yaml.js b/webpages/public/js/ace-src-min-noconflict/snippets/yaml.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/snippets/yaml.js rename to webpages/public/js/ace-src-min-noconflict/snippets/yaml.js diff --git a/webpages/public/ace-src-min-noconflict/theme-ambiance.js b/webpages/public/js/ace-src-min-noconflict/theme-ambiance.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-ambiance.js rename to webpages/public/js/ace-src-min-noconflict/theme-ambiance.js diff --git a/webpages/public/ace-src-min-noconflict/theme-chaos.js b/webpages/public/js/ace-src-min-noconflict/theme-chaos.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-chaos.js rename to webpages/public/js/ace-src-min-noconflict/theme-chaos.js diff --git a/webpages/public/ace-src-min-noconflict/theme-chrome.js b/webpages/public/js/ace-src-min-noconflict/theme-chrome.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-chrome.js rename to webpages/public/js/ace-src-min-noconflict/theme-chrome.js diff --git a/webpages/public/ace-src-min-noconflict/theme-clouds.js b/webpages/public/js/ace-src-min-noconflict/theme-clouds.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-clouds.js rename to webpages/public/js/ace-src-min-noconflict/theme-clouds.js diff --git a/webpages/public/ace-src-min-noconflict/theme-clouds_midnight.js b/webpages/public/js/ace-src-min-noconflict/theme-clouds_midnight.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-clouds_midnight.js rename to webpages/public/js/ace-src-min-noconflict/theme-clouds_midnight.js diff --git a/webpages/public/ace-src-min-noconflict/theme-cobalt.js b/webpages/public/js/ace-src-min-noconflict/theme-cobalt.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-cobalt.js rename to webpages/public/js/ace-src-min-noconflict/theme-cobalt.js diff --git a/webpages/public/ace-src-min-noconflict/theme-crimson_editor.js b/webpages/public/js/ace-src-min-noconflict/theme-crimson_editor.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-crimson_editor.js rename to webpages/public/js/ace-src-min-noconflict/theme-crimson_editor.js diff --git a/webpages/public/ace-src-min-noconflict/theme-dawn.js b/webpages/public/js/ace-src-min-noconflict/theme-dawn.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-dawn.js rename to webpages/public/js/ace-src-min-noconflict/theme-dawn.js diff --git a/webpages/public/ace-src-min-noconflict/theme-dreamweaver.js b/webpages/public/js/ace-src-min-noconflict/theme-dreamweaver.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-dreamweaver.js rename to webpages/public/js/ace-src-min-noconflict/theme-dreamweaver.js diff --git a/webpages/public/ace-src-min-noconflict/theme-eclipse.js b/webpages/public/js/ace-src-min-noconflict/theme-eclipse.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-eclipse.js rename to webpages/public/js/ace-src-min-noconflict/theme-eclipse.js diff --git a/webpages/public/ace-src-min-noconflict/theme-github.js b/webpages/public/js/ace-src-min-noconflict/theme-github.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-github.js rename to webpages/public/js/ace-src-min-noconflict/theme-github.js diff --git a/webpages/public/ace-src-min-noconflict/theme-idle_fingers.js b/webpages/public/js/ace-src-min-noconflict/theme-idle_fingers.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-idle_fingers.js rename to webpages/public/js/ace-src-min-noconflict/theme-idle_fingers.js diff --git a/webpages/public/ace-src-min-noconflict/theme-kr.js b/webpages/public/js/ace-src-min-noconflict/theme-kr.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-kr.js rename to webpages/public/js/ace-src-min-noconflict/theme-kr.js diff --git a/webpages/public/ace-src-min-noconflict/theme-merbivore.js b/webpages/public/js/ace-src-min-noconflict/theme-merbivore.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-merbivore.js rename to webpages/public/js/ace-src-min-noconflict/theme-merbivore.js diff --git a/webpages/public/ace-src-min-noconflict/theme-merbivore_soft.js b/webpages/public/js/ace-src-min-noconflict/theme-merbivore_soft.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-merbivore_soft.js rename to webpages/public/js/ace-src-min-noconflict/theme-merbivore_soft.js diff --git a/webpages/public/ace-src-min-noconflict/theme-mono_industrial.js b/webpages/public/js/ace-src-min-noconflict/theme-mono_industrial.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-mono_industrial.js rename to webpages/public/js/ace-src-min-noconflict/theme-mono_industrial.js diff --git a/webpages/public/ace-src-min-noconflict/theme-monokai.js b/webpages/public/js/ace-src-min-noconflict/theme-monokai.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-monokai.js rename to webpages/public/js/ace-src-min-noconflict/theme-monokai.js diff --git a/webpages/public/ace-src-min-noconflict/theme-pastel_on_dark.js b/webpages/public/js/ace-src-min-noconflict/theme-pastel_on_dark.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-pastel_on_dark.js rename to webpages/public/js/ace-src-min-noconflict/theme-pastel_on_dark.js diff --git a/webpages/public/ace-src-min-noconflict/theme-solarized_dark.js b/webpages/public/js/ace-src-min-noconflict/theme-solarized_dark.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-solarized_dark.js rename to webpages/public/js/ace-src-min-noconflict/theme-solarized_dark.js diff --git a/webpages/public/ace-src-min-noconflict/theme-solarized_light.js b/webpages/public/js/ace-src-min-noconflict/theme-solarized_light.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-solarized_light.js rename to webpages/public/js/ace-src-min-noconflict/theme-solarized_light.js diff --git a/webpages/public/ace-src-min-noconflict/theme-terminal.js b/webpages/public/js/ace-src-min-noconflict/theme-terminal.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-terminal.js rename to webpages/public/js/ace-src-min-noconflict/theme-terminal.js diff --git a/webpages/public/ace-src-min-noconflict/theme-textmate.js b/webpages/public/js/ace-src-min-noconflict/theme-textmate.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-textmate.js rename to webpages/public/js/ace-src-min-noconflict/theme-textmate.js diff --git a/webpages/public/ace-src-min-noconflict/theme-tomorrow.js b/webpages/public/js/ace-src-min-noconflict/theme-tomorrow.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-tomorrow.js rename to webpages/public/js/ace-src-min-noconflict/theme-tomorrow.js diff --git a/webpages/public/ace-src-min-noconflict/theme-tomorrow_night.js b/webpages/public/js/ace-src-min-noconflict/theme-tomorrow_night.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-tomorrow_night.js rename to webpages/public/js/ace-src-min-noconflict/theme-tomorrow_night.js diff --git a/webpages/public/ace-src-min-noconflict/theme-tomorrow_night_blue.js b/webpages/public/js/ace-src-min-noconflict/theme-tomorrow_night_blue.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-tomorrow_night_blue.js rename to webpages/public/js/ace-src-min-noconflict/theme-tomorrow_night_blue.js diff --git a/webpages/public/ace-src-min-noconflict/theme-tomorrow_night_bright.js b/webpages/public/js/ace-src-min-noconflict/theme-tomorrow_night_bright.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-tomorrow_night_bright.js rename to webpages/public/js/ace-src-min-noconflict/theme-tomorrow_night_bright.js diff --git a/webpages/public/ace-src-min-noconflict/theme-tomorrow_night_eighties.js b/webpages/public/js/ace-src-min-noconflict/theme-tomorrow_night_eighties.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-tomorrow_night_eighties.js rename to webpages/public/js/ace-src-min-noconflict/theme-tomorrow_night_eighties.js diff --git a/webpages/public/ace-src-min-noconflict/theme-twilight.js b/webpages/public/js/ace-src-min-noconflict/theme-twilight.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-twilight.js rename to webpages/public/js/ace-src-min-noconflict/theme-twilight.js diff --git a/webpages/public/ace-src-min-noconflict/theme-vibrant_ink.js b/webpages/public/js/ace-src-min-noconflict/theme-vibrant_ink.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-vibrant_ink.js rename to webpages/public/js/ace-src-min-noconflict/theme-vibrant_ink.js diff --git a/webpages/public/ace-src-min-noconflict/theme-xcode.js b/webpages/public/js/ace-src-min-noconflict/theme-xcode.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/theme-xcode.js rename to webpages/public/js/ace-src-min-noconflict/theme-xcode.js diff --git a/webpages/public/ace-src-min-noconflict/worker-coffee.js b/webpages/public/js/ace-src-min-noconflict/worker-coffee.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/worker-coffee.js rename to webpages/public/js/ace-src-min-noconflict/worker-coffee.js diff --git a/webpages/public/ace-src-min-noconflict/worker-css.js b/webpages/public/js/ace-src-min-noconflict/worker-css.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/worker-css.js rename to webpages/public/js/ace-src-min-noconflict/worker-css.js diff --git a/webpages/public/ace-src-min-noconflict/worker-javascript.js b/webpages/public/js/ace-src-min-noconflict/worker-javascript.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/worker-javascript.js rename to webpages/public/js/ace-src-min-noconflict/worker-javascript.js diff --git a/webpages/public/ace-src-min-noconflict/worker-json.js b/webpages/public/js/ace-src-min-noconflict/worker-json.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/worker-json.js rename to webpages/public/js/ace-src-min-noconflict/worker-json.js diff --git a/webpages/public/ace-src-min-noconflict/worker-lua.js b/webpages/public/js/ace-src-min-noconflict/worker-lua.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/worker-lua.js rename to webpages/public/js/ace-src-min-noconflict/worker-lua.js diff --git a/webpages/public/ace-src-min-noconflict/worker-php.js b/webpages/public/js/ace-src-min-noconflict/worker-php.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/worker-php.js rename to webpages/public/js/ace-src-min-noconflict/worker-php.js diff --git a/webpages/public/ace-src-min-noconflict/worker-xquery.js b/webpages/public/js/ace-src-min-noconflict/worker-xquery.js similarity index 100% rename from webpages/public/ace-src-min-noconflict/worker-xquery.js rename to webpages/public/js/ace-src-min-noconflict/worker-xquery.js diff --git a/webpages/public/lib/codemirror.css b/webpages/public/lib/codemirror.css deleted file mode 100644 index 23eaf74..0000000 --- a/webpages/public/lib/codemirror.css +++ /dev/null @@ -1,263 +0,0 @@ -/* BASICS */ - -.CodeMirror { - /* Set height, width, borders, and global font properties here */ - font-family: monospace; - height: 300px; -} -.CodeMirror-scroll { - /* Set scrolling behaviour here */ - overflow: auto; -} - -/* PADDING */ - -.CodeMirror-lines { - padding: 4px 0; /* Vertical padding around content */ -} -.CodeMirror pre { - padding: 0 4px; /* Horizontal padding of content */ -} - -.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - background-color: white; /* The little square between H and V scrollbars */ -} - -/* GUTTER */ - -.CodeMirror-gutters { - border-right: 1px solid #ddd; - background-color: #f7f7f7; - white-space: nowrap; -} -.CodeMirror-linenumbers {} -.CodeMirror-linenumber { - padding: 0 3px 0 5px; - min-width: 20px; - text-align: right; - color: #999; -} - -/* CURSOR */ - -.CodeMirror div.CodeMirror-cursor { - border-left: 1px solid black; - z-index: 3; -} -/* Shown when moving in bi-directional text */ -.CodeMirror div.CodeMirror-secondarycursor { - border-left: 1px solid silver; -} -.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { - width: auto; - border: 0; - background: #7e7; - z-index: 1; -} -/* Can style cursor different in overwrite (non-insert) mode */ -.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} - -.cm-tab { display: inline-block; } - -/* DEFAULT THEME */ - -.cm-s-default .cm-keyword {color: #708;} -.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-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;} -.cm-s-default .cm-meta {color: #555;} -.cm-s-default .cm-qualifier {color: #555;} -.cm-s-default .cm-builtin {color: #30a;} -.cm-s-default .cm-bracket {color: #997;} -.cm-s-default .cm-tag {color: #170;} -.cm-s-default .cm-attribute {color: #00c;} -.cm-s-default .cm-header {color: blue;} -.cm-s-default .cm-quote {color: #090;} -.cm-s-default .cm-hr {color: #999;} -.cm-s-default .cm-link {color: #00c;} - -.cm-negative {color: #d44;} -.cm-positive {color: #292;} -.cm-header, .cm-strong {font-weight: bold;} -.cm-em {font-style: italic;} -.cm-link {text-decoration: underline;} - -.cm-s-default .cm-error {color: #f00;} -.cm-invalidchar {color: #f00;} - -div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} -div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} -.CodeMirror-activeline-background {background: #e8f2ff;} - -/* STOP */ - -/* The rest of this file contains styles related to the mechanics of - the editor. You probably shouldn't touch them. */ - -.CodeMirror { - line-height: 1; - position: relative; - overflow: hidden; - background: white; - color: black; -} - -.CodeMirror-scroll { - /* 30px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror */ - margin-bottom: -30px; margin-right: -30px; - padding-bottom: 30px; padding-right: 30px; - height: 100%; - outline: none; /* Prevent dragging from highlighting the element */ - position: relative; - -moz-box-sizing: content-box; - box-sizing: content-box; -} -.CodeMirror-sizer { - position: relative; -} - -/* The fake, visible scrollbars. Used to force redraw during scrolling - before actuall scrolling happens, thus preventing shaking and - flickering artifacts. */ -.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { - position: absolute; - z-index: 6; - display: none; -} -.CodeMirror-vscrollbar { - right: 0; top: 0; - overflow-x: hidden; - overflow-y: scroll; -} -.CodeMirror-hscrollbar { - bottom: 0; left: 0; - overflow-y: hidden; - overflow-x: scroll; -} -.CodeMirror-scrollbar-filler { - right: 0; bottom: 0; -} -.CodeMirror-gutter-filler { - left: 0; bottom: 0; -} - -.CodeMirror-gutters { - position: absolute; left: 0; top: 0; - padding-bottom: 30px; - z-index: 3; -} -.CodeMirror-gutter { - white-space: normal; - height: 100%; - -moz-box-sizing: content-box; - box-sizing: content-box; - padding-bottom: 30px; - margin-bottom: -32px; - display: inline-block; - /* Hack to make IE7 behave */ - *zoom:1; - *display:inline; -} -.CodeMirror-gutter-elt { - position: absolute; - cursor: default; - z-index: 4; -} - -.CodeMirror-lines { - cursor: text; -} -.CodeMirror pre { - /* Reset some styles that the rest of the page might have set */ - -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; - border-width: 0; - background: transparent; - font-family: inherit; - font-size: inherit; - margin: 0; - white-space: pre; - word-wrap: normal; - line-height: inherit; - color: inherit; - z-index: 2; - position: relative; - overflow: visible; -} -.CodeMirror-wrap pre { - word-wrap: break-word; - white-space: pre-wrap; - word-break: normal; -} -.CodeMirror-code pre { - border-right: 30px solid transparent; - width: -webkit-fit-content; - width: -moz-fit-content; - width: fit-content; -} -.CodeMirror-wrap .CodeMirror-code pre { - border-right: none; - width: auto; -} -.CodeMirror-linebackground { - position: absolute; - left: 0; right: 0; top: 0; bottom: 0; - z-index: 0; -} - -.CodeMirror-linewidget { - position: relative; - z-index: 2; - overflow: auto; -} - -.CodeMirror-widget {} - -.CodeMirror-wrap .CodeMirror-scroll { - overflow-x: hidden; -} - -.CodeMirror-measure { - position: absolute; - width: 100%; - height: 0; - overflow: hidden; - visibility: hidden; -} -.CodeMirror-measure pre { position: static; } - -.CodeMirror div.CodeMirror-cursor { - position: absolute; - visibility: hidden; - border-right: none; - width: 0; -} -.CodeMirror-focused div.CodeMirror-cursor { - visibility: visible; -} - -.CodeMirror-selected { background: #d9d9d9; } -.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } - -.cm-searching { - background: #ffa; - background: rgba(255, 255, 0, .4); -} - -/* IE7 hack to prevent it from returning funny offsetTops on the spans */ -.CodeMirror span { *vertical-align: text-bottom; } - -@media print { - /* Hide the cursor when printing */ - .CodeMirror div.CodeMirror-cursor { - visibility: hidden; - } -} diff --git a/webpages/public/lib/codemirror.js b/webpages/public/lib/codemirror.js deleted file mode 100644 index f8b2af5..0000000 --- a/webpages/public/lib/codemirror.js +++ /dev/null @@ -1,5944 +0,0 @@ -// CodeMirror version 3.20 -// -// CodeMirror is the only global var we claim -window.CodeMirror = (function() { - "use strict"; - - // BROWSER SNIFFING - - // Crude, but necessary to handle a number of hard-to-feature-detect - // bugs and behavior differences. - var gecko = /gecko\/\d/i.test(navigator.userAgent); - // IE11 currently doesn't count as 'ie', since it has almost none of - // the same bugs as earlier versions. Use ie_gt10 to handle - // incompatibilities in that version. - var ie = /MSIE \d/.test(navigator.userAgent); - var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8); - var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); - var ie_gt10 = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent); - var webkit = /WebKit\//.test(navigator.userAgent); - var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); - var chrome = /Chrome\//.test(navigator.userAgent); - var opera = /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); - - var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); - // This is woefully incomplete. Suggestions for alternative methods welcome. - var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent); - var mac = ios || /Mac/.test(navigator.platform); - var windows = /win/i.test(navigator.platform); - - var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/); - if (opera_version) opera_version = Number(opera_version[1]); - if (opera_version && opera_version >= 15) { opera = false; webkit = true; } - // Some browsers use the wrong event properties to signal cmd/ctrl on OS X - var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11)); - var captureMiddleClick = gecko || (ie && !ie_lt9); - - // Optimize some code when these features are not used - var sawReadOnlySpans = false, sawCollapsedSpans = false; - - // CONSTRUCTOR - - function CodeMirror(place, options) { - if (!(this instanceof CodeMirror)) return new CodeMirror(place, options); - - this.options = options = options || {}; - // Determine effective options based on given values and defaults. - for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt)) - options[opt] = defaults[opt]; - setGuttersForLineNumbers(options); - - var docStart = typeof options.value == "string" ? 0 : options.value.first; - var display = this.display = makeDisplay(place, docStart); - display.wrapper.CodeMirror = this; - updateGutters(this); - if (options.autofocus && !mobile) focusInput(this); - - this.state = {keyMaps: [], - overlays: [], - modeGen: 0, - overwrite: false, focused: false, - suppressEdits: false, pasteIncoming: false, - draggingText: false, - highlight: new Delayed()}; - - themeChanged(this); - if (options.lineWrapping) - this.display.wrapper.className += " CodeMirror-wrap"; - - var doc = options.value; - if (typeof doc == "string") doc = new Doc(options.value, options.mode); - operation(this, attachDoc)(this, doc); - - // Override magic textarea content restore that IE sometimes does - // on our hidden textarea on reload - if (ie) setTimeout(bind(resetInput, this, true), 20); - - registerEventHandlers(this); - // IE throws unspecified error in certain cases, when - // trying to access activeElement before onload - var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { } - if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20); - else onBlur(this); - - operation(this, function() { - for (var opt in optionHandlers) - if (optionHandlers.propertyIsEnumerable(opt)) - optionHandlers[opt](this, options[opt], Init); - for (var i = 0; i < initHooks.length; ++i) initHooks[i](this); - })(); - } - - // DISPLAY CONSTRUCTOR - - function makeDisplay(place, docStart) { - var d = {}; - - var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none; font-size: 4px;"); - if (webkit) input.style.width = "1000px"; - else input.setAttribute("wrap", "off"); - // if border: 0; -- iOS fails to open keyboard (issue #1287) - if (ios) input.style.border = "1px solid black"; - input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off"); input.setAttribute("spellcheck", "false"); - - // Wraps and hides input textarea - d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;"); - // The actual fake scrollbars. - d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar"); - d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar"); - d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler"); - d.gutterFiller = elt("div", null, "CodeMirror-gutter-filler"); - // DIVs containing the selection and the actual code - d.lineDiv = elt("div", null, "CodeMirror-code"); - d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1"); - // Blinky cursor, and element used to ensure cursor fits at the end of a line - d.cursor = elt("div", "\u00a0", "CodeMirror-cursor"); - // Secondary cursor, shown when on a 'jump' in bi-directional text - d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"); - // Used to measure text size - d.measure = elt("div", null, "CodeMirror-measure"); - // Wraps everything that needs to exist inside the vertically-padded coordinate system - d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor], - null, "position: relative; outline: none"); - // Moved around its parent to cover visible view - d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative"); - // Set to the height of the text, causes scrolling - d.sizer = elt("div", [d.mover], "CodeMirror-sizer"); - // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers - d.heightForcer = elt("div", null, null, "position: absolute; height: " + scrollerCutOff + "px; width: 1px;"); - // Will contain the gutters, if any - d.gutters = elt("div", null, "CodeMirror-gutters"); - d.lineGutter = null; - // Provides scrolling - 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"); - // Work around IE7 z-index bug - if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; } - if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper); - - // 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). - else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px"; - - // Current visible range (may be bigger than the view window). - d.viewOffset = d.lastSizeC = 0; - d.showingFrom = d.showingTo = docStart; - - // 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; - // See readInput and resetInput - d.prevInput = ""; - // Set to true when a non-horizontal-scrolling widget is added. As - // an optimization, widget aligning is skipped when d is false. - d.alignWidgets = false; - // Flag that indicates whether we currently expect input to appear - // (after some event like 'keypress' or 'input') and are polling - // intensively. - d.pollingFast = false; - // Self-resetting timeout for the poller - d.poll = new Delayed(); - - d.cachedCharWidth = d.cachedTextHeight = null; - d.measureLineCache = []; - d.measureLineCachePos = 0; - - // Tracks when resetInput has punted to just putting a short - // string instead of the (large) selection. - d.inaccurateSelection = false; - - // Tracks the maximum line length so that the horizontal scrollbar - // can be kept static when scrolling. - d.maxLine = null; - d.maxLineLength = 0; - d.maxLineChanged = false; - - // Used for measuring wheel scrolling granularity - d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null; - - return d; - } - - // STATE UPDATES - - // Used to get the editor into a consistent state again when options change. - - function loadMode(cm) { - cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); - cm.doc.iter(function(line) { - if (line.stateAfter) line.stateAfter = null; - if (line.styles) line.styles = null; - }); - cm.doc.frontier = cm.doc.first; - startWorker(cm, 100); - cm.state.modeGen++; - if (cm.curOp) regChange(cm); - } - - function wrappingChanged(cm) { - if (cm.options.lineWrapping) { - cm.display.wrapper.className += " CodeMirror-wrap"; - cm.display.sizer.style.minWidth = ""; - } else { - cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", ""); - computeMaxLength(cm); - } - estimateLineHeights(cm); - regChange(cm); - clearCaches(cm); - setTimeout(function(){updateScrollbars(cm);}, 100); - } - - function estimateHeight(cm) { - var th = textHeight(cm.display), wrapping = cm.options.lineWrapping; - var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3); - return function(line) { - if (lineIsHidden(cm.doc, line)) - return 0; - else if (wrapping) - return (Math.ceil(line.text.length / perLine) || 1) * th; - else - return th; - }; - } - - function estimateLineHeights(cm) { - var doc = cm.doc, est = estimateHeight(cm); - doc.iter(function(line) { - var estHeight = est(line); - if (estHeight != line.height) updateLineHeight(line, estHeight); - }); - } - - 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 : ""); - cm.state.disableInput = map.disableInput; - } - - 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-"); - clearCaches(cm); - } - - function guttersChanged(cm) { - updateGutters(cm); - regChange(cm); - setTimeout(function(){alignHorizontally(cm);}, 20); - } - - function updateGutters(cm) { - var gutters = cm.display.gutters, specs = cm.options.gutters; - removeChildren(gutters); - for (var i = 0; i < specs.length; ++i) { - var gutterClass = specs[i]; - var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass)); - if (gutterClass == "CodeMirror-linenumbers") { - cm.display.lineGutter = gElt; - gElt.style.width = (cm.display.lineNumWidth || 1) + "px"; - } - } - gutters.style.display = i ? "" : "none"; - } - - function lineLength(doc, line) { - if (line.height == 0) return 0; - var len = line.text.length, merged, cur = line; - while (merged = collapsedSpanAtStart(cur)) { - var found = merged.find(); - cur = getLine(doc, found.from.line); - len += found.from.ch - found.to.ch; - } - cur = line; - while (merged = collapsedSpanAtEnd(cur)) { - var found = merged.find(); - len -= cur.text.length - found.from.ch; - cur = getLine(doc, found.to.line); - len += cur.text.length - found.to.ch; - } - return len; - } - - function computeMaxLength(cm) { - var d = cm.display, doc = cm.doc; - d.maxLine = getLine(doc, doc.first); - d.maxLineLength = lineLength(doc, d.maxLine); - d.maxLineChanged = true; - doc.iter(function(line) { - var len = lineLength(doc, line); - if (len > d.maxLineLength) { - d.maxLineLength = len; - d.maxLine = line; - } - }); - } - - // Make sure the gutters options contains the element - // "CodeMirror-linenumbers" when the lineNumbers option is true. - function setGuttersForLineNumbers(options) { - var found = indexOf(options.gutters, "CodeMirror-linenumbers"); - if (found == -1 && options.lineNumbers) { - options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); - } else if (found > -1 && !options.lineNumbers) { - options.gutters = options.gutters.slice(0); - options.gutters.splice(found, 1); - } - } - - // SCROLLBARS - - // Re-synchronize the fake scrollbars with the actual size of the - // content. Optionally force a scrollTop. - function updateScrollbars(cm) { - var d = cm.display, docHeight = cm.doc.height; - var totalHeight = docHeight + paddingVert(d); - d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px"; - d.gutters.style.height = Math.max(totalHeight, d.scroller.clientHeight - scrollerCutOff) + "px"; - var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight); - var needsH = d.scroller.scrollWidth > (d.scroller.clientWidth + 1); - var needsV = scrollHeight > (d.scroller.clientHeight + 1); - if (needsV) { - d.scrollbarV.style.display = "block"; - d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0"; - d.scrollbarV.firstChild.style.height = - (scrollHeight - d.scroller.clientHeight + 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 = - (d.scroller.scrollWidth - d.scroller.clientWidth + 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 = ""; - - if (mac_geLion && scrollbarWidth(d.measure) === 0) { - d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; - d.scrollbarV.style.pointerEvents = d.scrollbarH.style.pointerEvents = "none"; - } - } - - function visibleLines(display, doc, viewPort) { - var top = display.scroller.scrollTop, height = display.wrapper.clientHeight; - if (typeof viewPort == "number") top = viewPort; - else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;} - top = Math.floor(top - paddingTop(display)); - var bottom = Math.ceil(top + height); - return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)}; - } - - // LINE NUMBERS - - function alignHorizontally(cm) { - var display = cm.display; - if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return; - var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; - var gutterW = display.gutters.offsetWidth, l = comp + "px"; - for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) { - for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l; - } - if (cm.options.fixedGutter) - display.gutters.style.left = (comp + gutterW) + "px"; - } - - function maybeUpdateLineNumberWidth(cm) { - if (!cm.options.lineNumbers) return false; - var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display; - if (last.length != display.lineNumChars) { - var test = display.measure.appendChild(elt("div", [elt("div", last)], - "CodeMirror-linenumber CodeMirror-gutter-elt")); - var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW; - display.lineGutter.style.width = ""; - display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding); - display.lineNumWidth = display.lineNumInnerWidth + padding; - display.lineNumChars = display.lineNumInnerWidth ? last.length : -1; - display.lineGutter.style.width = display.lineNumWidth + "px"; - return true; - } - return false; - } - - function lineNumberFor(options, i) { - return String(options.lineNumberFormatter(i + options.firstLineNumber)); - } - function compensateForHScroll(display) { - return getRect(display.scroller).left - getRect(display.sizer).left; - } - - // DISPLAY DRAWING - - function updateDisplay(cm, changes, viewPort, forced) { - var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated; - var visible = visibleLines(cm.display, cm.doc, viewPort); - for (var first = true;; first = false) { - var oldWidth = cm.display.scroller.clientWidth; - if (!updateDisplayInner(cm, changes, visible, forced)) break; - updated = true; - changes = []; - updateSelection(cm); - updateScrollbars(cm); - if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) { - forced = true; - continue; - } - forced = false; - - // Clip forced viewport to actual scrollable area - if (viewPort) - viewPort = Math.min(cm.display.scroller.scrollHeight - cm.display.scroller.clientHeight, - typeof viewPort == "number" ? viewPort : viewPort.top); - visible = visibleLines(cm.display, cm.doc, viewPort); - if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo) - break; - } - - if (updated) { - signalLater(cm, "update", cm); - if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo) - signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo); - } - return updated; - } - - // Uses a set of changes plus the current scroll position to - // determine which DOM updates have to be made, and makes the - // updates. - function updateDisplayInner(cm, changes, visible, forced) { - var display = cm.display, doc = cm.doc; - if (!display.wrapper.clientWidth) { - display.showingFrom = display.showingTo = doc.first; - display.viewOffset = 0; - return; - } - - // Bail out if the visible area is already rendered and nothing changed. - if (!forced && changes.length == 0 && - visible.from > display.showingFrom && visible.to < display.showingTo) - return; - - if (maybeUpdateLineNumberWidth(cm)) - changes = [{from: doc.first, to: doc.first + doc.size}]; - var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px"; - display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0"; - - // Used to determine which lines need their line numbers updated - var positionsChangedFrom = Infinity; - if (cm.options.lineNumbers) - for (var i = 0; i < changes.length; ++i) - if (changes[i].diff && changes[i].from < positionsChangedFrom) { positionsChangedFrom = changes[i].from; } - - 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); - if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom); - if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo); - if (sawCollapsedSpans) { - from = lineNo(visualLine(doc, getLine(doc, from))); - while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to; - } - - // Create a range of theoretically intact lines, and punch holes - // in that using the change info. - var intact = [{from: Math.max(display.showingFrom, doc.first), - to: Math.min(display.showingTo, end)}]; - if (intact[0].from >= intact[0].to) intact = []; - else intact = computeIntact(intact, changes); - // When merged lines are present, we might have to reduce the - // intact ranges because changes in continued fragments of the - // intact lines do require the lines to be redrawn. - if (sawCollapsedSpans) - for (var i = 0; i < intact.length; ++i) { - var range = intact[i], merged; - while (merged = collapsedSpanAtEnd(getLine(doc, range.to - 1))) { - var newTo = merged.find().from.line; - if (newTo > range.from) range.to = newTo; - else { intact.splice(i--, 1); break; } - } - } - - // Clip off the parts that won't be visible - var intactLines = 0; - for (var i = 0; i < intact.length; ++i) { - var range = intact[i]; - if (range.from < from) range.from = from; - if (range.to > to) range.to = to; - if (range.from >= range.to) intact.splice(i--, 1); - else intactLines += range.to - range.from; - } - if (!forced && intactLines == to - from && from == display.showingFrom && to == display.showingTo) { - updateViewOffset(cm); - return; - } - intact.sort(function(a, b) {return a.from - b.from;}); - - // Avoid crashing on IE's "unspecified error" when in iframes - try { - var focused = document.activeElement; - } catch(e) {} - if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none"; - patchDisplay(cm, from, to, intact, positionsChangedFrom); - display.lineDiv.style.display = ""; - if (focused && document.activeElement != focused && focused.offsetHeight) focused.focus(); - - var different = from != display.showingFrom || to != display.showingTo || - display.lastSizeC != display.wrapper.clientHeight; - // This is just a bogus formula that detects when the editor is - // resized or the font size changes. - if (different) { - display.lastSizeC = display.wrapper.clientHeight; - startWorker(cm, 400); - } - display.showingFrom = from; display.showingTo = to; - - updateHeightsInViewport(cm); - updateViewOffset(cm); - - return true; - } - - function updateHeightsInViewport(cm) { - var display = cm.display; - var prevBottom = display.lineDiv.offsetTop; - for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) { - if (ie_lt8) { - var bot = node.offsetTop + node.offsetHeight; - height = bot - prevBottom; - prevBottom = bot; - } else { - var box = getRect(node); - height = box.bottom - box.top; - } - var diff = node.lineObj.height - height; - if (height < 2) height = textHeight(display); - if (diff > .001 || diff < -.001) { - updateLineHeight(node.lineObj, height); - var widgets = node.lineObj.widgets; - if (widgets) for (var i = 0; i < widgets.length; ++i) - widgets[i].height = widgets[i].node.offsetHeight; - } - } - } - - function updateViewOffset(cm) { - var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom)); - // Position the mover div to align with the current virtual scroll position - cm.display.mover.style.top = off + "px"; - } - - function computeIntact(intact, changes) { - for (var i = 0, l = changes.length || 0; i < l; ++i) { - var change = changes[i], intact2 = [], diff = change.diff || 0; - for (var j = 0, l2 = intact.length; j < l2; ++j) { - var range = intact[j]; - if (change.to <= range.from && change.diff) { - intact2.push({from: range.from + diff, to: range.to + diff}); - } else if (change.to <= range.from || change.from >= range.to) { - intact2.push(range); - } else { - if (change.from > range.from) - intact2.push({from: range.from, to: change.from}); - if (change.to < range.to) - intact2.push({from: change.to + diff, to: range.to + diff}); - } - } - intact = intact2; - } - return intact; - } - - function getDimensions(cm) { - var d = cm.display, left = {}, width = {}; - 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; - } - return {fixedPos: compensateForHScroll(d), - gutterTotalWidth: d.gutters.offsetWidth, - gutterLeft: left, - gutterWidth: width, - wrapperWidth: d.wrapper.clientWidth}; - } - - function patchDisplay(cm, from, to, intact, updateNumbersFrom) { - var dims = getDimensions(cm); - var display = cm.display, lineNumbers = cm.options.lineNumbers; - if (!intact.length && (!webkit || !cm.display.currentWheelTarget)) - removeChildren(display.lineDiv); - var container = display.lineDiv, cur = container.firstChild; - - function rm(node) { - var next = node.nextSibling; - if (webkit && mac && cm.display.currentWheelTarget == node) { - node.style.display = "none"; - node.lineObj = null; - } else { - node.parentNode.removeChild(node); - } - return next; - } - - var nextIntact = intact.shift(), lineN = from; - cm.doc.iter(from, to, function(line) { - if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift(); - if (lineIsHidden(cm.doc, line)) { - if (line.height != 0) updateLineHeight(line, 0); - if (line.widgets && cur && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i) { - var w = line.widgets[i]; - if (w.showIfHidden) { - var prev = cur.previousSibling; - if (/pre/i.test(prev.nodeName)) { - var wrap = elt("div", null, null, "position: relative"); - prev.parentNode.replaceChild(wrap, prev); - wrap.appendChild(prev); - prev = wrap; - } - var wnode = prev.appendChild(elt("div", [w.node], "CodeMirror-linewidget")); - if (!w.handleMouseEvents) wnode.ignoreEvents = true; - positionLineWidget(w, wnode, prev, dims); - } - } - } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) { - // This line is intact. Skip to the actual node. Update its - // line number if needed. - while (cur.lineObj != line) cur = rm(cur); - if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber) - setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN)); - cur = cur.nextSibling; - } else { - // For lines with widgets, make an attempt to find and reuse - // the existing element, so that widgets aren't needlessly - // removed and re-inserted into the dom - if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling) - if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; } - // This line needs to be generated. - var lineNode = buildLineElement(cm, line, lineN, dims, reuse); - if (lineNode != reuse) { - container.insertBefore(lineNode, cur); - } else { - while (cur != reuse) cur = rm(cur); - cur = cur.nextSibling; - } - - lineNode.lineObj = line; - } - ++lineN; - }); - while (cur) cur = rm(cur); - } - - function buildLineElement(cm, line, lineNo, dims, reuse) { - var built = buildLineContent(cm, line), lineElement = built.pre; - var markers = line.gutterMarkers, display = cm.display, wrap; - - var bgClass = built.bgClass ? built.bgClass + " " + (line.bgClass || "") : line.bgClass; - if (!cm.options.lineNumbers && !markers && !bgClass && !line.wrapClass && !line.widgets) - return lineElement; - - // Lines with gutter elements, widgets or a background class need - // to be wrapped again, and have the extra elements added to the - // wrapper div - - if (reuse) { - reuse.alignable = null; - var isOk = true, widgetsSeen = 0, insertBefore = null; - for (var n = reuse.firstChild, next; n; n = next) { - next = n.nextSibling; - if (!/\bCodeMirror-linewidget\b/.test(n.className)) { - reuse.removeChild(n); - } else { - for (var i = 0; i < line.widgets.length; ++i) { - var widget = line.widgets[i]; - if (widget.node == n.firstChild) { - if (!widget.above && !insertBefore) insertBefore = n; - positionLineWidget(widget, n, reuse, dims); - ++widgetsSeen; - break; - } - } - if (i == line.widgets.length) { isOk = false; break; } - } - } - reuse.insertBefore(lineElement, insertBefore); - if (isOk && widgetsSeen == line.widgets.length) { - wrap = reuse; - reuse.className = line.wrapClass || ""; - } - } - if (!wrap) { - wrap = elt("div", null, line.wrapClass, "position: relative"); - wrap.appendChild(lineElement); - } - // Kludge to make sure the styled element lies behind the selection (by z-index) - if (bgClass) - wrap.insertBefore(elt("div", null, bgClass + " CodeMirror-linebackground"), wrap.firstChild); - if (cm.options.lineNumbers || markers) { - var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " + - (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), - wrap.firstChild); - if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap); - if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) - wrap.lineNumber = gutterWrap.appendChild( - elt("div", lineNumberFor(cm.options, lineNo), - "CodeMirror-linenumber CodeMirror-gutter-elt", - "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: " - + display.lineNumInnerWidth + "px")); - if (markers) - for (var k = 0; k < cm.options.gutters.length; ++k) { - var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id]; - if (found) - gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " + - dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px")); - } - } - if (ie_lt8) wrap.style.zIndex = 2; - if (line.widgets && wrap != reuse) 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; - positionLineWidget(widget, node, wrap, dims); - if (widget.above) - wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement); - else - wrap.appendChild(node); - signalLater(widget, "redraw"); - } - return wrap; - } - - function positionLineWidget(widget, node, wrap, dims) { - if (widget.noHScroll) { - (wrap.alignable || (wrap.alignable = [])).push(node); - var width = dims.wrapperWidth; - node.style.left = dims.fixedPos + "px"; - if (!widget.coverGutter) { - width -= dims.gutterTotalWidth; - node.style.paddingLeft = dims.gutterTotalWidth + "px"; - } - node.style.width = width + "px"; - } - if (widget.coverGutter) { - node.style.zIndex = 5; - node.style.position = "relative"; - if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px"; - } - } - - // SELECTION / CURSOR - - function updateSelection(cm) { - var display = cm.display; - var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to); - if (collapsed || cm.options.showCursorWhenSelecting) - updateSelectionCursor(cm); - else - display.cursor.style.display = display.otherCursor.style.display = "none"; - if (!collapsed) - updateSelectionRange(cm); - else - display.selectionDiv.style.display = "none"; - - // Move the hidden textarea near the cursor to prevent scrolling artifacts - if (cm.options.moveInputWithCursor) { - var headPos = cursorCoords(cm, cm.doc.sel.head, "div"); - var wrapOff = getRect(display.wrapper), lineOff = getRect(display.lineDiv); - display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10, - headPos.top + lineOff.top - wrapOff.top)) + "px"; - display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10, - headPos.left + lineOff.left - wrapOff.left)) + "px"; - } - } - - // No selection, plain cursor - function updateSelectionCursor(cm) { - var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div"); - display.cursor.style.left = pos.left + "px"; - display.cursor.style.top = pos.top + "px"; - display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px"; - display.cursor.style.display = ""; - - if (pos.other) { - display.otherCursor.style.display = ""; - display.otherCursor.style.left = pos.other.left + "px"; - display.otherCursor.style.top = pos.other.top + "px"; - display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px"; - } else { display.otherCursor.style.display = "none"; } - } - - // Highlight selection - function updateSelectionRange(cm) { - var display = cm.display, doc = cm.doc, sel = cm.doc.sel; - var fragment = document.createDocumentFragment(); - var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display); - - function add(left, top, width, bottom) { - if (top < 0) top = 0; - fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left + - "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) + - "px; height: " + (bottom - top) + "px")); - } - - function drawForLine(line, fromArg, toArg) { - var lineObj = getLine(doc, line); - var lineLen = lineObj.text.length; - var start, end; - function coords(ch, bias) { - return charCoords(cm, Pos(line, ch), "div", lineObj, bias); - } - - iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) { - var leftPos = coords(from, "left"), rightPos, left, right; - if (from == to) { - rightPos = leftPos; - left = right = leftPos.left; - } else { - rightPos = coords(to - 1, "right"); - if (dir == "rtl") { var tmp = leftPos; leftPos = rightPos; rightPos = tmp; } - left = leftPos.left; - right = rightPos.right; - } - if (fromArg == null && from == 0) left = pl; - if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part - add(left, leftPos.top, null, leftPos.bottom); - left = pl; - if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top); - } - if (toArg == null && to == lineLen) right = clientWidth; - if (!start || leftPos.top < start.top || leftPos.top == start.top && leftPos.left < start.left) - start = leftPos; - if (!end || rightPos.bottom > end.bottom || rightPos.bottom == end.bottom && rightPos.right > end.right) - end = rightPos; - if (left < pl + 1) left = pl; - add(left, rightPos.top, right - left, rightPos.bottom); - }); - return {start: start, end: end}; - } - - if (sel.from.line == sel.to.line) { - drawForLine(sel.from.line, sel.from.ch, sel.to.ch); - } else { - var fromLine = getLine(doc, sel.from.line), toLine = getLine(doc, sel.to.line); - var singleVLine = visualLine(doc, fromLine) == visualLine(doc, toLine); - var leftEnd = drawForLine(sel.from.line, sel.from.ch, singleVLine ? fromLine.text.length : null).end; - var rightStart = drawForLine(sel.to.line, singleVLine ? 0 : null, sel.to.ch).start; - if (singleVLine) { - if (leftEnd.top < rightStart.top - 2) { - add(leftEnd.right, leftEnd.top, null, leftEnd.bottom); - add(pl, rightStart.top, rightStart.left, rightStart.bottom); - } else { - add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom); - } - } - if (leftEnd.bottom < rightStart.top) - add(pl, leftEnd.bottom, null, rightStart.top); - } - - removeChildrenAndAdd(display.selectionDiv, fragment); - display.selectionDiv.style.display = ""; - } - - // Cursor-blinking - function restartBlink(cm) { - if (!cm.state.focused) return; - var display = cm.display; - clearInterval(display.blinker); - var on = true; - display.cursor.style.visibility = display.otherCursor.style.visibility = ""; - if (cm.options.cursorBlinkRate > 0) - display.blinker = setInterval(function() { - display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden"; - }, cm.options.cursorBlinkRate); - } - - // HIGHLIGHT WORKER - - function startWorker(cm, time) { - if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo) - cm.state.highlight.set(time, bind(highlightWorker, cm)); - } - - function highlightWorker(cm) { - var doc = cm.doc; - if (doc.frontier < doc.first) doc.frontier = doc.first; - if (doc.frontier >= cm.display.showingTo) return; - var end = +new Date + cm.options.workTime; - var state = copyState(doc.mode, getStateBefore(cm, doc.frontier)); - var changed = [], prevChange; - doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) { - if (doc.frontier >= cm.display.showingFrom) { // Visible - var oldStyles = line.styles; - line.styles = highlightLine(cm, line, state, true); - var ischange = !oldStyles || oldStyles.length != line.styles.length; - for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; - if (ischange) { - if (prevChange && prevChange.end == doc.frontier) prevChange.end++; - else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1}); - } - line.stateAfter = copyState(doc.mode, state); - } else { - processLine(cm, line.text, state); - line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; - } - ++doc.frontier; - if (+new Date > end) { - startWorker(cm, cm.options.workDelay); - return true; - } - }); - if (changed.length) - operation(cm, function() { - for (var i = 0; i < changed.length; ++i) - regChange(this, changed[i].start, changed[i].end); - })(); - } - - // Finds the line to start with when starting a parse. Tries to - // find a line with a stateAfter, so that it can start with a - // valid state. If that fails, it returns the line with the - // smallest indentation, which tends to need the least context to - // parse correctly. - function findStartLine(cm, n, precise) { - var minindent, minline, doc = cm.doc; - var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); - for (var search = n; search > lim; --search) { - if (search <= doc.first) return doc.first; - var line = getLine(doc, search - 1); - if (line.stateAfter && (!precise || search <= doc.frontier)) return search; - var indented = countColumn(line.text, null, cm.options.tabSize); - if (minline == null || minindent > indented) { - minline = search - 1; - minindent = indented; - } - } - return minline; - } - - function getStateBefore(cm, n, precise) { - var doc = cm.doc, display = cm.display; - if (!doc.mode.startState) return true; - var pos = findStartLine(cm, n, precise), state = pos > doc.first && getLine(doc, pos-1).stateAfter; - if (!state) state = startState(doc.mode); - else state = copyState(doc.mode, state); - doc.iter(pos, n, function(line) { - processLine(cm, line.text, state); - var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo; - line.stateAfter = save ? copyState(doc.mode, state) : null; - ++pos; - }); - if (precise) doc.frontier = pos; - return state; - } - - // POSITION MEASUREMENT - - function paddingTop(display) {return display.lineSpace.offsetTop;} - function paddingVert(display) {return display.mover.offsetHeight - display.lineSpace.offsetHeight;} - function paddingLeft(display) { - var e = removeChildrenAndAdd(display.measure, elt("pre", null, null, "text-align: left")).appendChild(elt("span", "x")); - return e.offsetLeft; - } - - function measureChar(cm, line, ch, data, bias) { - var dir = -1; - data = data || measureLine(cm, line); - if (data.crude) { - var left = data.left + ch * data.width; - return {left: left, right: left + data.width, top: data.top, bottom: data.bottom}; - } - - for (var pos = ch;; pos += dir) { - var r = data[pos]; - if (r) break; - if (dir < 0 && pos == 0) dir = 1; - } - bias = pos > ch ? "left" : pos < ch ? "right" : bias; - if (bias == "left" && r.leftSide) r = r.leftSide; - else if (bias == "right" && r.rightSide) r = r.rightSide; - return {left: pos < ch ? r.right : r.left, - right: pos > ch ? r.left : r.right, - top: r.top, - bottom: r.bottom}; - } - - function findCachedMeasurement(cm, line) { - var cache = cm.display.measureLineCache; - for (var i = 0; i < cache.length; ++i) { - var memo = cache[i]; - if (memo.text == line.text && memo.markedSpans == line.markedSpans && - cm.display.scroller.clientWidth == memo.width && - memo.classes == line.textClass + "|" + line.wrapClass) - return memo; - } - } - - function clearCachedMeasurement(cm, line) { - var exists = findCachedMeasurement(cm, line); - if (exists) exists.text = exists.measure = exists.markedSpans = null; - } - - function measureLine(cm, line) { - // First look in the cache - var cached = findCachedMeasurement(cm, line); - if (cached) return cached.measure; - - // Failing that, recompute and store result in cache - var measure = measureLineInner(cm, line); - var cache = cm.display.measureLineCache; - var memo = {text: line.text, width: cm.display.scroller.clientWidth, - markedSpans: line.markedSpans, measure: measure, - classes: line.textClass + "|" + line.wrapClass}; - if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo; - else cache.push(memo); - return measure; - } - - function measureLineInner(cm, line) { - if (!cm.options.lineWrapping && line.text.length >= cm.options.crudeMeasuringFrom) - return crudelyMeasureLine(cm, line); - - var display = cm.display, measure = emptyArray(line.text.length); - var pre = buildLineContent(cm, line, measure, true).pre; - - // IE does not cache element positions of inline elements between - // calls to getBoundingClientRect. This makes the loop below, - // which gathers the positions of all the characters on the line, - // do an amount of layout work quadratic to the number of - // characters. When line wrapping is off, we try to improve things - // by first subdividing the line into a bunch of inline blocks, so - // that IE can reuse most of the layout information from caches - // for those blocks. This does interfere with line wrapping, so it - // doesn't work when wrapping is on, but in that case the - // situation is slightly better, since IE does cache line-wrapping - // information and only recomputes per-line. - if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) { - var fragment = document.createDocumentFragment(); - var chunk = 10, n = pre.childNodes.length; - for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) { - var wrap = elt("div", null, null, "display: inline-block"); - for (var j = 0; j < chunk && n; ++j) { - wrap.appendChild(pre.firstChild); - --n; - } - fragment.appendChild(wrap); - } - pre.appendChild(fragment); - } - - removeChildrenAndAdd(display.measure, pre); - - var outer = getRect(display.lineDiv); - var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight; - // Work around an IE7/8 bug where it will sometimes have randomly - // replaced our pre with a clone at this point. - if (ie_lt9 && display.measure.first != pre) - removeChildrenAndAdd(display.measure, pre); - - function measureRect(rect) { - var top = rect.top - outer.top, bot = rect.bottom - outer.top; - if (bot > maxBot) bot = maxBot; - if (top < 0) top = 0; - for (var i = vranges.length - 2; i >= 0; i -= 2) { - var rtop = vranges[i], rbot = vranges[i+1]; - if (rtop > bot || rbot < top) continue; - if (rtop <= top && rbot >= bot || - top <= rtop && bot >= rbot || - Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) { - vranges[i] = Math.min(top, rtop); - vranges[i+1] = Math.max(bot, rbot); - break; - } - } - if (i < 0) { i = vranges.length; vranges.push(top, bot); } - return {left: rect.left - outer.left, - right: rect.right - outer.left, - top: i, bottom: null}; - } - function finishRect(rect) { - rect.bottom = vranges[rect.top+1]; - rect.top = vranges[rect.top]; - } - - for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) { - var node = cur, rect = null; - // A widget might wrap, needs special care - if (/\bCodeMirror-widget\b/.test(cur.className) && cur.getClientRects) { - if (cur.firstChild.nodeType == 1) node = cur.firstChild; - var rects = node.getClientRects(); - if (rects.length > 1) { - rect = data[i] = measureRect(rects[0]); - rect.rightSide = measureRect(rects[rects.length - 1]); - } - } - if (!rect) rect = data[i] = measureRect(getRect(node)); - if (cur.measureRight) rect.right = getRect(cur.measureRight).left; - if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide)); - } - removeChildren(cm.display.measure); - for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) { - finishRect(cur); - if (cur.leftSide) finishRect(cur.leftSide); - if (cur.rightSide) finishRect(cur.rightSide); - } - return data; - } - - function crudelyMeasureLine(cm, line) { - var copy = new Line(line.text.slice(0, 100), null); - if (line.textClass) copy.textClass = line.textClass; - var measure = measureLineInner(cm, copy); - var left = measureChar(cm, copy, 0, measure, "left"); - var right = measureChar(cm, copy, 99, measure, "right"); - return {crude: true, top: left.top, left: left.left, bottom: left.bottom, width: (right.right - left.left) / 100}; - } - - function measureLineWidth(cm, line) { - var hasBadSpan = false; - if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) { - var sp = line.markedSpans[i]; - if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true; - } - var cached = !hasBadSpan && findCachedMeasurement(cm, line); - if (cached || line.text.length >= cm.options.crudeMeasuringFrom) - return measureChar(cm, line, line.text.length, cached && cached.measure, "right").right; - - var pre = buildLineContent(cm, line, null, true).pre; - var end = pre.appendChild(zeroWidthElement(cm.display.measure)); - removeChildrenAndAdd(cm.display.measure, pre); - return getRect(end).right - getRect(cm.display.lineDiv).left; - } - - function clearCaches(cm) { - cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0; - cm.display.cachedCharWidth = cm.display.cachedTextHeight = null; - if (!cm.options.lineWrapping) cm.display.maxLineChanged = true; - cm.display.lineNumChars = null; - } - - function pageScrollX() { return window.pageXOffset || (document.documentElement || document.body).scrollLeft; } - function pageScrollY() { return window.pageYOffset || (document.documentElement || document.body).scrollTop; } - - // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), 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]); - rect.top += size; rect.bottom += size; - } - if (context == "line") return rect; - if (!context) context = "local"; - var yOff = heightAtLine(cm, lineObj); - if (context == "local") yOff += paddingTop(cm.display); - else yOff -= cm.display.viewOffset; - if (context == "page" || context == "window") { - var lOff = getRect(cm.display.lineSpace); - yOff += lOff.top + (context == "window" ? 0 : pageScrollY()); - var xOff = lOff.left + (context == "window" ? 0 : pageScrollX()); - rect.left += xOff; rect.right += xOff; - } - rect.top += yOff; rect.bottom += yOff; - return rect; - } - - // Context may be "window", "page", "div", or "local"/null - // Result is in "div" coords - function fromCoordSystem(cm, coords, context) { - if (context == "div") return coords; - var left = coords.left, top = coords.top; - // First move into "page" coordinate system - if (context == "page") { - left -= pageScrollX(); - top -= pageScrollY(); - } else if (context == "local" || !context) { - var localBox = getRect(cm.display.sizer); - left += localBox.left; - top += localBox.top; - } - - var lineSpaceBox = getRect(cm.display.lineSpace); - return {left: left - lineSpaceBox.left, top: top - lineSpaceBox.top}; - } - - function charCoords(cm, pos, context, lineObj, bias) { - if (!lineObj) lineObj = getLine(cm.doc, pos.line); - return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch, null, bias), context); - } - - function cursorCoords(cm, pos, context, lineObj, measurement) { - lineObj = lineObj || getLine(cm.doc, pos.line); - if (!measurement) measurement = measureLine(cm, lineObj); - function get(ch, right) { - var m = measureChar(cm, lineObj, ch, measurement, right ? "right" : "left"); - if (right) m.left = m.right; else m.right = m.left; - return intoCoordSystem(cm, lineObj, m, context); - } - function getBidi(ch, partPos) { - var part = order[partPos], right = part.level % 2; - if (ch == bidiLeft(part) && partPos && part.level < order[partPos - 1].level) { - part = order[--partPos]; - ch = bidiRight(part) - (part.level % 2 ? 0 : 1); - right = true; - } else if (ch == bidiRight(part) && partPos < order.length - 1 && part.level < order[partPos + 1].level) { - part = order[++partPos]; - ch = bidiLeft(part) - part.level % 2; - right = false; - } - if (right && ch == part.to && ch > part.from) return get(ch - 1); - return get(ch, right); - } - var order = getOrder(lineObj), ch = pos.ch; - if (!order) return get(ch); - var partPos = getBidiPartAt(order, ch); - var val = getBidi(ch, partPos); - if (bidiOther != null) val.other = getBidi(ch, bidiOther); - return val; - } - - function PosWithInfo(line, ch, outside, xRel) { - var pos = new Pos(line, ch); - pos.xRel = xRel; - if (outside) pos.outside = true; - return pos; - } - - // Coords must be lineSpace-local - function coordsChar(cm, x, y) { - var doc = cm.doc; - y += cm.display.viewOffset; - if (y < 0) return PosWithInfo(doc.first, 0, true, -1); - var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1; - if (lineNo > last) - return PosWithInfo(doc.first + doc.size - 1, getLine(doc, last).text.length, true, 1); - if (x < 0) x = 0; - - for (;;) { - var lineObj = getLine(doc, lineNo); - var found = coordsCharInner(cm, lineObj, lineNo, x, y); - var merged = collapsedSpanAtEnd(lineObj); - var mergedPos = merged && merged.find(); - if (merged && (found.ch > mergedPos.from.ch || found.ch == mergedPos.from.ch && found.xRel > 0)) - lineNo = mergedPos.to.line; - else - return found; - } - } - - function coordsCharInner(cm, lineObj, lineNo, x, y) { - var innerOff = y - heightAtLine(cm, lineObj); - var wrongLine = false, adjust = 2 * cm.display.wrapper.clientWidth; - var measurement = measureLine(cm, lineObj); - - function getX(ch) { - var sp = cursorCoords(cm, Pos(lineNo, ch), "line", - lineObj, measurement); - wrongLine = true; - if (innerOff > sp.bottom) return sp.left - adjust; - else if (innerOff < sp.top) return sp.left + adjust; - else wrongLine = false; - return sp.left; - } - - var bidi = getOrder(lineObj), dist = lineObj.text.length; - var from = lineLeft(lineObj), to = lineRight(lineObj); - var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine; - - if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1); - // Do a binary search between these bounds. - for (;;) { - if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { - var ch = x < fromX || x - fromX <= toX - x ? from : to; - var xDiff = x - (ch == from ? fromX : toX); - while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; - var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, - xDiff < 0 ? -1 : xDiff ? 1 : 0); - return pos; - } - var step = Math.ceil(dist / 2), middle = from + step; - if (bidi) { - middle = from; - for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1); - } - var middleX = getX(middle); - if (middleX > x) {to = middle; toX = middleX; if (toOutside = wrongLine) toX += 1000; dist = step;} - else {from = middle; fromX = middleX; fromOutside = wrongLine; dist -= step;} - } - } - - var measureText; - function textHeight(display) { - if (display.cachedTextHeight != null) return display.cachedTextHeight; - if (measureText == null) { - measureText = elt("pre"); - // Measure a bunch of lines, for browsers that compute - // fractional heights. - for (var i = 0; i < 49; ++i) { - measureText.appendChild(document.createTextNode("x")); - measureText.appendChild(elt("br")); - } - measureText.appendChild(document.createTextNode("x")); - } - removeChildrenAndAdd(display.measure, measureText); - var height = measureText.offsetHeight / 50; - if (height > 3) display.cachedTextHeight = height; - removeChildren(display.measure); - return height || 1; - } - - function charWidth(display) { - if (display.cachedCharWidth != null) return display.cachedCharWidth; - var anchor = elt("span", "x"); - var pre = elt("pre", [anchor]); - removeChildrenAndAdd(display.measure, pre); - var width = anchor.offsetWidth; - if (width > 2) display.cachedCharWidth = width; - return width || 10; - } - - // OPERATIONS - - // Operations are used to wrap changes in such a way that each - // change won't have to update the cursor and display (which would - // be awkward, slow, and error-prone), but instead updates are - // batched and then all combined and executed at once. - - var nextOpId = 0; - function startOperation(cm) { - cm.curOp = { - // An array of ranges of lines that have to be updated. See - // updateDisplay. - changes: [], - forceUpdate: false, - updateInput: null, - userSelChange: null, - textChanged: null, - selectionChanged: false, - cursorActivity: false, - updateMaxLine: false, - updateScrollPos: false, - id: ++nextOpId - }; - if (!delayedCallbackDepth++) delayedCallbacks = []; - } - - function endOperation(cm) { - var op = cm.curOp, doc = cm.doc, display = cm.display; - cm.curOp = null; - - if (op.updateMaxLine) computeMaxLength(cm); - if (display.maxLineChanged && !cm.options.lineWrapping && display.maxLine) { - var width = measureLineWidth(cm, display.maxLine); - display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px"; - display.maxLineChanged = false; - var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth); - if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos) - setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true); - } - var newScrollPos, updated; - if (op.updateScrollPos) { - newScrollPos = op.updateScrollPos; - } else if (op.selectionChanged && display.scroller.clientHeight) { // don't rescroll if not visible - var coords = cursorCoords(cm, doc.sel.head); - newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom); - } - if (op.changes.length || op.forceUpdate || newScrollPos && newScrollPos.scrollTop != null) { - updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop, op.forceUpdate); - if (cm.display.scroller.offsetHeight) cm.doc.scrollTop = cm.display.scroller.scrollTop; - } - if (!updated && op.selectionChanged) updateSelection(cm); - if (op.updateScrollPos) { - var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, newScrollPos.scrollTop)); - var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, newScrollPos.scrollLeft)); - display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top; - display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left; - alignHorizontally(cm); - if (op.scrollToPos) - scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from), - clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin); - } else if (newScrollPos) { - scrollCursorIntoView(cm); - } - if (op.selectionChanged) restartBlink(cm); - - if (cm.state.focused && op.updateInput) - resetInput(cm, op.userSelChange); - - var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers; - if (hidden) for (var i = 0; i < hidden.length; ++i) - if (!hidden[i].lines.length) signal(hidden[i], "hide"); - 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 (op.textChanged) - signal(cm, "change", cm, op.textChanged); - if (op.cursorActivity) signal(cm, "cursorActivity", cm); - if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](); - } - - // Wraps a function in an operation. Returns the wrapped function. - function operation(cm1, f) { - return function() { - var cm = cm1 || this, withOp = !cm.curOp; - if (withOp) startOperation(cm); - try { var result = f.apply(cm, arguments); } - finally { if (withOp) endOperation(cm); } - return result; - }; - } - function docOperation(f) { - return function() { - var withOp = this.cm && !this.cm.curOp, result; - if (withOp) startOperation(this.cm); - try { result = f.apply(this, arguments); } - finally { if (withOp) endOperation(this.cm); } - return result; - }; - } - function runInOp(cm, f) { - var withOp = !cm.curOp, result; - if (withOp) startOperation(cm); - try { result = f(); } - finally { if (withOp) endOperation(cm); } - return result; - } - - function regChange(cm, from, to, lendiff) { - if (from == null) from = cm.doc.first; - if (to == null) to = cm.doc.first + cm.doc.size; - cm.curOp.changes.push({from: from, to: to, diff: lendiff}); - } - - // INPUT HANDLING - - function slowPoll(cm) { - if (cm.display.pollingFast) return; - cm.display.poll.set(cm.options.pollInterval, function() { - readInput(cm); - if (cm.state.focused) slowPoll(cm); - }); - } - - function fastPoll(cm) { - var missed = false; - cm.display.pollingFast = true; - function p() { - var changed = readInput(cm); - if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);} - else {cm.display.pollingFast = false; slowPoll(cm);} - } - cm.display.poll.set(20, p); - } - - // prevInput is a hack to work with IME. If we reset the textarea - // on every change, that breaks IME. So we look for changes - // compared to the previous content instead. (Modern browsers have - // events that indicate IME taking place, but these are not widely - // supported or compatible enough yet to rely on.) - function readInput(cm) { - var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel; - if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false; - 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 (text == prevInput && posEq(sel.from, sel.to)) return false; - if (ie && !ie_lt9 && cm.display.inputHasSelection === text) { - resetInput(cm, true); - return false; - } - - var withOp = !cm.curOp; - if (withOp) startOperation(cm); - sel.shift = false; - var same = 0, l = Math.min(prevInput.length, text.length); - while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; - var from = sel.from, to = sel.to; - if (same < prevInput.length) - from = Pos(from.line, from.ch - (prevInput.length - same)); - else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming) - to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same))); - - var updateInput = cm.curOp.updateInput; - var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)), - origin: cm.state.pasteIncoming ? "paste" : "+input"}; - makeChange(cm.doc, changeEvent, "end"); - cm.curOp.updateInput = updateInput; - signalLater(cm, "inputRead", cm, changeEvent); - - if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = ""; - else cm.display.prevInput = text; - if (withOp) endOperation(cm); - cm.state.pasteIncoming = false; - return true; - } - - function resetInput(cm, user) { - var minimal, selected, doc = cm.doc; - if (!posEq(doc.sel.from, doc.sel.to)) { - cm.display.prevInput = ""; - minimal = hasCopyEvent && - (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000); - var content = minimal ? "-" : selected || cm.getSelection(); - cm.display.input.value = content; - if (cm.state.focused) selectInput(cm.display.input); - if (ie && !ie_lt9) cm.display.inputHasSelection = content; - } else if (user) { - cm.display.prevInput = cm.display.input.value = ""; - if (ie && !ie_lt9) cm.display.inputHasSelection = null; - } - cm.display.inaccurateSelection = minimal; - } - - function focusInput(cm) { - if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input)) - cm.display.input.focus(); - } - - function isReadOnly(cm) { - return cm.options.readOnly || cm.doc.cantEdit; - } - - // EVENT HANDLERS - - function registerEventHandlers(cm) { - var d = cm.display; - on(d.scroller, "mousedown", operation(cm, onMouseDown)); - if (ie) - 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(getLine(cm.doc, pos.line).text, pos); - extendSelection(cm.doc, word.from, word.to); - })); - else - on(d.scroller, "dblclick", function(e) { signalDOMEvent(cm, e) || e_preventDefault(e); }); - on(d.lineSpace, "selectstart", function(e) { - if (!eventInWidget(d, e)) e_preventDefault(e); - }); - // Gecko browsers fire contextmenu *after* opening the menu, at - // which point we can't mess with it anymore. Context menu is - // handled in onMouseDown for Gecko. - if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);}); - - on(d.scroller, "scroll", function() { - if (d.scroller.clientHeight) { - setScrollTop(cm, d.scroller.scrollTop); - setScrollLeft(cm, d.scroller.scrollLeft, true); - 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); - }); - - on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);}); - on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);}); - - 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; }); - - 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 = knownScrollbarWidth = null; - clearCaches(cm); - runInOp(cm, bind(regChange, cm)); - }, 100); - } - on(window, "resize", onResize); - // 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() { - for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {} - if (p) setTimeout(unregister, 5000); - else off(window, "resize", onResize); - } - setTimeout(unregister, 5000); - - on(d.input, "keyup", operation(cm, function(e) { - if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; - if (e.keyCode == 16) cm.doc.sel.shift = false; - })); - on(d.input, "input", function() { - if (ie && !ie_lt9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null; - fastPoll(cm); - }); - on(d.input, "keydown", operation(cm, onKeyDown)); - on(d.input, "keypress", operation(cm, onKeyPress)); - on(d.input, "focus", bind(onFocus, cm)); - on(d.input, "blur", bind(onBlur, cm)); - - function drag_(e) { - if (signalDOMEvent(cm, e) || cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return; - e_stop(e); - } - if (cm.options.dragDrop) { - on(d.scroller, "dragstart", function(e){onDragStart(cm, e);}); - on(d.scroller, "dragenter", drag_); - on(d.scroller, "dragover", drag_); - on(d.scroller, "drop", operation(cm, onDrop)); - } - on(d.scroller, "paste", function(e) { - if (eventInWidget(d, e)) return; - focusInput(cm); - 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 += "$"; - d.input.selectionStart = start; - d.input.selectionEnd = end; - cm.state.fakedLastChar = true; - } - cm.state.pasteIncoming = true; - fastPoll(cm); - }); - - function prepareCopy() { - if (d.inaccurateSelection) { - d.prevInput = ""; - d.inaccurateSelection = false; - d.input.value = cm.getSelection(); - selectInput(d.input); - } - } - on(d.input, "cut", prepareCopy); - on(d.input, "copy", prepareCopy); - - // Needed to handle Tab key in KHTML - if (khtml) on(d.sizer, "mouseup", function() { - if (document.activeElement == d.input) d.input.blur(); - focusInput(cm); - }); - } - - 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; - } - } - - function posFromMouse(cm, e, liberal) { - var display = cm.display; - if (!liberal) { - var target = e_target(e); - if (target == display.scrollbarH || target == display.scrollbarH.firstChild || - target == display.scrollbarV || target == display.scrollbarV.firstChild || - target == display.scrollbarFiller || target == display.gutterFiller) return null; - } - var x, y, space = getRect(display.lineSpace); - // Fails unpredictably on IE[67] when mouse is dragged around quickly. - try { x = e.clientX; y = e.clientY; } catch (e) { return null; } - return coordsChar(cm, x - space.left, y - space.top); - } - - var lastClick, lastDoubleClick; - function onMouseDown(e) { - if (signalDOMEvent(this, e)) return; - var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel; - sel.shift = e.shiftKey; - - if (eventInWidget(display, e)) { - if (!webkit) { - display.scroller.draggable = false; - setTimeout(function(){display.scroller.draggable = true;}, 100); - } - return; - } - if (clickInGutter(cm, e)) return; - var start = posFromMouse(cm, e); - - switch (e_button(e)) { - case 3: - if (captureMiddleClick) onContextMenu.call(cm, cm, e); - return; - case 2: - if (webkit) cm.state.lastMiddleDown = +new Date; - if (start) extendSelection(cm.doc, start); - setTimeout(bind(focusInput, cm), 20); - e_preventDefault(e); - return; - } - // For button 1, if it was clicked inside the editor - // (posFromMouse returning non-null), we have to adjust the - // selection. - if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;} - - if (!cm.state.focused) onFocus(cm); - - var now = +new Date, type = "single"; - if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { - type = "triple"; - e_preventDefault(e); - setTimeout(bind(focusInput, cm), 20); - selectLine(cm, start.line); - } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { - type = "double"; - lastDoubleClick = {time: now, pos: start}; - e_preventDefault(e); - var word = findWordAt(getLine(doc, start.line).text, start); - extendSelection(cm.doc, word.from, word.to); - } else { lastClick = {time: now, pos: start}; } - - var last = start; - if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) && - !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") { - var dragEnd = operation(cm, function(e2) { - if (webkit) display.scroller.draggable = false; - cm.state.draggingText = false; - off(document, "mouseup", dragEnd); - 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); - focusInput(cm); - } - }); - // Let the drag handler handle this. - if (webkit) display.scroller.draggable = true; - cm.state.draggingText = dragEnd; - // IE's approach to draggable - if (display.scroller.dragDrop) display.scroller.dragDrop(); - on(document, "mouseup", dragEnd); - on(display.scroller, "drop", dragEnd); - return; - } - e_preventDefault(e); - if (type == "single") extendSelection(cm.doc, clipPos(doc, start)); - - var startstart = sel.from, startend = sel.to, lastPos = start; - - function doSelect(cur) { - if (posEq(lastPos, cur)) return; - lastPos = cur; - - if (type == "single") { - extendSelection(cm.doc, clipPos(doc, start), cur); - return; - } - - startstart = clipPos(doc, startstart); - startend = clipPos(doc, startend); - if (type == "double") { - var word = findWordAt(getLine(doc, cur.line).text, cur); - if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend); - else extendSelection(cm.doc, startstart, word.to); - } else if (type == "triple") { - if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0))); - else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0))); - } - } - - var editorSize = getRect(display.wrapper); - // Used to ensure timeout re-tries don't fire when another extend - // happened in the meantime (clearTimeout isn't reliable -- at - // least on Chrome, the timeouts still happen even when cleared, - // if the clear happens after their scheduled firing time). - var counter = 0; - - function extend(e) { - var curCount = ++counter; - var cur = posFromMouse(cm, e, true); - if (!cur) return; - if (!posEq(cur, last)) { - if (!cm.state.focused) onFocus(cm); - last = cur; - doSelect(cur); - var visible = visibleLines(display, doc); - if (cur.line >= visible.to || cur.line < visible.from) - setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150); - } else { - var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0; - if (outside) setTimeout(operation(cm, function() { - if (counter != curCount) return; - display.scroller.scrollTop += outside; - extend(e); - }), 50); - } - } - - function done(e) { - counter = Infinity; - e_preventDefault(e); - focusInput(cm); - off(document, "mousemove", move); - off(document, "mouseup", up); - } - - var move = operation(cm, function(e) { - if (!ie && !e_button(e)) done(e); - else extend(e); - }); - var up = operation(cm, done); - on(document, "mousemove", move); - on(document, "mouseup", up); - } - - function gutterEvent(cm, e, type, prevent, signalfn) { - try { var mX = e.clientX, mY = e.clientY; } - catch(e) { return false; } - if (mX >= Math.floor(getRect(cm.display.gutters).right)) return false; - if (prevent) e_preventDefault(e); - - var display = cm.display; - var lineBox = getRect(display.lineDiv); - - if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); - mY -= lineBox.top - display.viewOffset; - - for (var i = 0; i < cm.options.gutters.length; ++i) { - var g = display.gutters.childNodes[i]; - if (g && getRect(g).right >= mX) { - var line = lineAtHeight(cm.doc, mY); - var gutter = cm.options.gutters[i]; - signalfn(cm, type, cm, line, gutter, e); - return e_defaultPrevented(e); - } - } - } - - function contextMenuInGutter(cm, e) { - if (!hasHandler(cm, "gutterContextMenu")) return false; - return gutterEvent(cm, e, "gutterContextMenu", false, signal); - } - - function clickInGutter(cm, e) { - return gutterEvent(cm, e, "gutterClick", true, signalLater); - } - - // Kludge to work around strange IE behavior where it'll sometimes - // re-fire a series of drag-related events right after the drop (#1551) - var lastDrop = 0; - - function onDrop(e) { - var cm = this; - if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e)))) - return; - e_preventDefault(e); - if (ie) lastDrop = +new Date; - var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files; - if (!pos || isReadOnly(cm)) return; - if (files && files.length && window.FileReader && window.File) { - var n = files.length, text = Array(n), read = 0; - var loadFile = function(file, i) { - var reader = new FileReader; - reader.onload = function() { - text[i] = reader.result; - if (++read == n) { - pos = clipPos(cm.doc, pos); - makeChange(cm.doc, {from: pos, to: pos, text: splitLines(text.join("\n")), origin: "paste"}, "around"); - } - }; - reader.readAsText(file); - }; - for (var i = 0; i < n; ++i) loadFile(files[i], i); - } else { - // Don't do a replace if the drop happened inside of the selected text. - if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) { - cm.state.draggingText(e); - // Ensure the editor is re-focused - setTimeout(bind(focusInput, cm), 20); - return; - } - try { - var text = e.dataTransfer.getData("Text"); - if (text) { - var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to; - setSelection(cm.doc, pos, pos); - if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste"); - cm.replaceSelection(text, null, "paste"); - focusInput(cm); - } - } - catch(e){} - } - } - - function onDragStart(cm, e) { - if (ie && (!cm.state.draggingText || +new Date - lastDrop < 100)) { e_stop(e); return; } - if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) return; - - var txt = cm.getSelection(); - e.dataTransfer.setData("Text", txt); - - // Use dummy image instead of default browsers image. - // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. - if (e.dataTransfer.setDragImage && !safari) { - var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); - img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; - if (opera) { - img.width = img.height = 1; - cm.display.wrapper.appendChild(img); - // Force a relayout, or Opera won't use our image for some obscure reason - img._top = img.offsetTop; - } - e.dataTransfer.setDragImage(img, 0, 0); - if (opera) img.parentNode.removeChild(img); - } - } - - function setScrollTop(cm, val) { - if (Math.abs(cm.doc.scrollTop - val) < 2) return; - cm.doc.scrollTop = val; - if (!gecko) updateDisplay(cm, [], 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, []); - startWorker(cm, 100); - } - function setScrollLeft(cm, val, isScroller) { - if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return; - val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth); - 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; - } - - // Since the delta values reported on mouse wheel events are - // unstandardized between browsers and even browser versions, and - // generally horribly unpredictable, this code starts by measuring - // the scroll effect that the first few mouse wheel events have, - // and, from that, detects the way it can convert deltas to pixel - // offsets afterwards. - // - // The reason we want to know the amount a wheel event will scroll - // is that it gives us a chance to update the display before the - // actual scrolling happens, reducing flickering. - - var wheelSamples = 0, wheelPixelsPerUnit = null; - // Fill in a browser-detected starting value on browsers where we - // know one. These don't have to be accurate -- the result of them - // being wrong would just be a slight flicker on the first wheel - // scroll (if it is large enough). - if (ie) wheelPixelsPerUnit = -.53; - else if (gecko) wheelPixelsPerUnit = 15; - else if (chrome) wheelPixelsPerUnit = -.7; - else if (safari) wheelPixelsPerUnit = -1/3; - - function onScrollWheel(cm, 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; - - var display = cm.display, scroll = display.scroller; - // Quit if there's nothing to scroll here - if (!(dx && scroll.scrollWidth > scroll.clientWidth || - dy && scroll.scrollHeight > scroll.clientHeight)) return; - - // Webkit browsers on OS X abort momentum scrolls when the target - // of the scroll event is removed from the scrollable element. - // This hack (see related code in patchDisplay) makes sure the - // element is kept around. - if (dy && mac && webkit) { - for (var cur = e.target; cur != scroll; cur = cur.parentNode) { - if (cur.lineObj) { - cm.display.currentWheelTarget = cur; - break; - } - } - } - - // On some browsers, horizontal scrolling will cause redraws to - // happen before the gutter has been realigned, causing it to - // wriggle around in a most unseemly way. When we have an - // estimated pixels/delta value, we just handle horizontal - // scrolling entirely here. It'll be slightly off from native, but - // better than glitching out. - if (dx && !gecko && !opera && wheelPixelsPerUnit != null) { - if (dy) - setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight))); - setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth))); - e_preventDefault(e); - display.wheelStartX = null; // Abort measurement, if in progress - return; - } - - if (dy && wheelPixelsPerUnit != null) { - var pixels = dy * wheelPixelsPerUnit; - 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}); - } - - if (wheelSamples < 20) { - if (display.wheelStartX == null) { - display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop; - display.wheelDX = dx; display.wheelDY = dy; - setTimeout(function() { - if (display.wheelStartX == null) return; - var movedX = scroll.scrollLeft - display.wheelStartX; - var movedY = scroll.scrollTop - display.wheelStartY; - var sample = (movedY && display.wheelDY && movedY / display.wheelDY) || - (movedX && display.wheelDX && movedX / display.wheelDX); - display.wheelStartX = display.wheelStartY = null; - if (!sample) return; - wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1); - ++wheelSamples; - }, 200); - } else { - display.wheelDX += dx; display.wheelDY += dy; - } - } - } - - function doHandleBinding(cm, bound, dropShift) { - if (typeof bound == "string") { - bound = commands[bound]; - if (!bound) return false; - } - // Ensure previous input has been read, so that the handler sees a - // consistent view of the document - if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false; - var doc = cm.doc, prevShift = doc.sel.shift, done = false; - try { - if (isReadOnly(cm)) cm.state.suppressEdits = true; - if (dropShift) doc.sel.shift = false; - done = bound(cm) != Pass; - } finally { - doc.sel.shift = prevShift; - cm.state.suppressEdits = false; - } - return done; - } - - 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; - } - - var maybeTransition; - function handleKeyBinding(cm, e) { - // Handle auto 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; - if (!name) return false; - var keymaps = allKeyMaps(cm); - - if (e.shiftKey) { - // 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); - }); - } else { - handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); }); - } - - if (handled) { - e_preventDefault(e); - restartBlink(cm); - if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } - signalLater(cm, "keyHandled", cm, name, e); - } - return handled; - } - - 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; - } - - var lastStoppedKey = null; - function onKeyDown(e) { - var cm = this; - if (!cm.state.focused) onFocus(cm); - if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; - if (ie && e.keyCode == 27) e.returnValue = false; - var code = e.keyCode; - // IE does strange things with escape. - cm.doc.sel.shift = code == 16 || e.shiftKey; - // First give onKeyEvent option a chance to handle this. - var handled = handleKeyBinding(cm, e); - if (opera) { - lastStoppedKey = handled ? code : null; - // Opera has no cut event... we try to at least catch the key combo - if (!handled && code == 88 && !hasCopyEvent && (mac ? e.metaKey : e.ctrlKey)) - cm.replaceSelection(""); - } - } - - function onKeyPress(e) { - var cm = this; - if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; - var keyCode = e.keyCode, charCode = e.charCode; - if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} - if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; - var ch = String.fromCharCode(charCode == null ? keyCode : charCode); - if (this.options.electricChars && this.doc.mode.electricChars && - this.options.smartIndent && !isReadOnly(this) && - this.doc.mode.electricChars.indexOf(ch) > -1) - setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75); - if (handleCharBinding(cm, e, ch)) return; - if (ie && !ie_lt9) cm.display.inputHasSelection = null; - fastPoll(cm); - } - - function onFocus(cm) { - if (cm.options.readOnly == "nocursor") return; - 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) { - resetInput(cm, true); - if (webkit) setTimeout(bind(resetInput, cm, true), 0); // Issue #1730 - } - } - slowPoll(cm); - restartBlink(cm); - } - function onBlur(cm) { - if (cm.state.focused) { - signal(cm, "blur", cm); - cm.state.focused = false; - cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", ""); - } - clearInterval(cm.display.blinker); - setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150); - } - - var detectingSelectAll; - function onContextMenu(cm, e) { - if (signalDOMEvent(cm, e, "contextmenu")) return; - var display = cm.display, sel = cm.doc.sel; - if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return; - - var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; - if (!pos || opera) return; // Opera is difficult. - - // Reset the current text selection only if the click is done outside of the selection - // and 'resetSelectionOnContextMenu' option is true. - var reset = cm.options.resetSelectionOnContextMenu; - if (reset && (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))) - operation(cm, setSelection)(cm.doc, pos, pos); - - var oldCSS = display.input.style.cssText; - display.inputDiv.style.position = "absolute"; - display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + - "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" + - "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);"; - focusInput(cm); - resetInput(cm, true); - // Adds "Select all" to context menu in FF - if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " "; - - function prepareSelectAllHack() { - if (display.input.selectionStart != null) { - var extval = display.input.value = "\u200b" + (posEq(sel.from, sel.to) ? "" : display.input.value); - display.prevInput = "\u200b"; - display.input.selectionStart = 1; display.input.selectionEnd = extval.length; - } - } - function rehide() { - display.inputDiv.style.position = "relative"; - display.input.style.cssText = oldCSS; - if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos; - slowPoll(cm); - - // Try to detect the user choosing select-all - if (display.input.selectionStart != null) { - if (!ie || ie_lt9) prepareSelectAllHack(); - clearTimeout(detectingSelectAll); - var i = 0, poll = function(){ - if (display.prevInput == " " && display.input.selectionStart == 0) - operation(cm, commands.selectAll)(cm); - else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); - else resetInput(cm); - }; - detectingSelectAll = setTimeout(poll, 200); - } - } - - if (ie && !ie_lt9) prepareSelectAllHack(); - if (captureMiddleClick) { - e_stop(e); - var mouseup = function() { - off(window, "mouseup", mouseup); - setTimeout(rehide, 20); - }; - on(window, "mouseup", mouseup); - } else { - setTimeout(rehide, 50); - } - } - - // UPDATING - - var changeEnd = CodeMirror.changeEnd = function(change) { - if (!change.text) return change.to; - return Pos(change.from.line + change.text.length - 1, - lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0)); - }; - - // Make sure a position will be valid after the given change. - function clipPostChange(doc, change, pos) { - if (!posLess(change.from, pos)) return clipPos(doc, pos); - var diff = (change.text.length - 1) - (change.to.line - change.from.line); - if (pos.line > change.to.line + diff) { - var preLine = pos.line - diff, lastLine = doc.first + doc.size - 1; - if (preLine > lastLine) return Pos(lastLine, getLine(doc, lastLine).text.length); - return clipToLen(pos, getLine(doc, preLine).text.length); - } - if (pos.line == change.to.line + diff) - return clipToLen(pos, lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0) + - getLine(doc, change.to.line).text.length - change.to.ch); - var inside = pos.line - change.from.line; - return clipToLen(pos, change.text[inside].length + (inside ? 0 : change.from.ch)); - } - - // Hint can be null|"end"|"start"|"around"|{anchor,head} - function computeSelAfterChange(doc, change, hint) { - if (hint && typeof hint == "object") // Assumed to be {anchor, head} object - return {anchor: clipPostChange(doc, change, hint.anchor), - head: clipPostChange(doc, change, hint.head)}; - - if (hint == "start") return {anchor: change.from, head: change.from}; - - var end = changeEnd(change); - if (hint == "around") return {anchor: change.from, head: end}; - if (hint == "end") return {anchor: end, head: end}; - - // hint is null, leave the selection alone as much as possible - var adjustPos = function(pos) { - if (posLess(pos, change.from)) return pos; - if (!posLess(change.to, pos)) return end; - - var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch; - if (pos.line == change.to.line) ch += end.ch - change.to.ch; - return Pos(line, ch); - }; - return {anchor: adjustPos(doc.sel.anchor), head: adjustPos(doc.sel.head)}; - } - - function filterChange(doc, change, update) { - var obj = { - canceled: false, - from: change.from, - to: change.to, - text: change.text, - origin: change.origin, - cancel: function() { this.canceled = true; } - }; - if (update) obj.update = function(from, to, text, origin) { - if (from) this.from = clipPos(doc, from); - if (to) this.to = clipPos(doc, to); - if (text) this.text = text; - if (origin !== undefined) this.origin = origin; - }; - signal(doc, "beforeChange", doc, obj); - if (doc.cm) signal(doc.cm, "beforeChange", doc.cm, obj); - - if (obj.canceled) return null; - return {from: obj.from, to: obj.to, text: obj.text, origin: obj.origin}; - } - - // Replace the range from from to to by the strings in replacement. - // change is a {from, to, text [, origin]} object - function makeChange(doc, change, selUpdate, ignoreReadOnly) { - if (doc.cm) { - if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly); - if (doc.cm.state.suppressEdits) return; - } - - if (hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange")) { - change = filterChange(doc, change, true); - if (!change) return; - } - - // Possibly split or suppress the update based on the presence - // of read-only spans in its range. - var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to); - if (split) { - for (var i = split.length - 1; i >= 1; --i) - makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]}); - if (split.length) - makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate); - } else { - makeChangeNoReadonly(doc, change, selUpdate); - } - } - - function makeChangeNoReadonly(doc, change, selUpdate) { - if (change.text.length == 1 && change.text[0] == "" && posEq(change.from, change.to)) return; - var selAfter = computeSelAfterChange(doc, change, selUpdate); - addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); - - makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change)); - var rebased = []; - - linkedDocs(doc, function(doc, sharedHist) { - if (!sharedHist && indexOf(rebased, doc.history) == -1) { - rebaseHist(doc.history, change); - rebased.push(doc.history); - } - makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change)); - }); - } - - function makeChangeFromHistory(doc, type) { - if (doc.cm && doc.cm.state.suppressEdits) return; - - var hist = doc.history; - var event = (type == "undo" ? hist.done : hist.undone).pop(); - if (!event) return; - - var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter, - anchorAfter: event.anchorBefore, headAfter: event.headBefore, - generation: hist.generation}; - (type == "undo" ? hist.undone : hist.done).push(anti); - hist.generation = event.generation || ++hist.maxGeneration; - - var filter = hasHandler(doc, "beforeChange") || doc.cm && hasHandler(doc.cm, "beforeChange"); - - for (var i = event.changes.length - 1; i >= 0; --i) { - var change = event.changes[i]; - change.origin = type; - if (filter && !filterChange(doc, change, false)) { - (type == "undo" ? hist.done : hist.undone).length = 0; - return; - } - - anti.changes.push(historyChangeFromChange(doc, change)); - - var after = i ? computeSelAfterChange(doc, change, null) - : {anchor: event.anchorBefore, head: event.headBefore}; - makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change)); - var rebased = []; - - linkedDocs(doc, function(doc, sharedHist) { - if (!sharedHist && indexOf(rebased, doc.history) == -1) { - rebaseHist(doc.history, change); - rebased.push(doc.history); - } - makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change)); - }); - } - } - - function shiftDoc(doc, distance) { - function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);} - doc.first += distance; - if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance); - doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor); - doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to); - } - - function makeChangeSingleDoc(doc, change, selAfter, spans) { - if (doc.cm && !doc.cm.curOp) - return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans); - - if (change.to.line < doc.first) { - shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line)); - return; - } - if (change.from.line > doc.lastLine()) return; - - // Clip the change to the size of this doc - if (change.from.line < doc.first) { - var shift = change.text.length - 1 - (doc.first - change.from.line); - shiftDoc(doc, shift); - change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch), - text: [lst(change.text)], origin: change.origin}; - } - var last = doc.lastLine(); - if (change.to.line > last) { - change = {from: change.from, to: Pos(last, getLine(doc, last).text.length), - text: [change.text[0]], origin: change.origin}; - } - - change.removed = getBetween(doc, change.from, change.to); - - if (!selAfter) selAfter = computeSelAfterChange(doc, change, null); - if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter); - else updateDoc(doc, change, spans, selAfter); - } - - function makeChangeSingleDocInEditor(cm, change, spans, selAfter) { - var doc = cm.doc, display = cm.display, from = change.from, to = change.to; - - var recomputeMaxLength = false, checkWidthStart = from.line; - if (!cm.options.lineWrapping) { - checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line))); - doc.iter(checkWidthStart, to.line + 1, function(line) { - if (line == display.maxLine) { - recomputeMaxLength = true; - return true; - } - }); - } - - if (!posLess(doc.sel.head, change.from) && !posLess(change.to, doc.sel.head)) - cm.curOp.cursorActivity = true; - - updateDoc(doc, change, spans, selAfter, estimateHeight(cm)); - - if (!cm.options.lineWrapping) { - doc.iter(checkWidthStart, from.line + change.text.length, function(line) { - var len = lineLength(doc, line); - if (len > display.maxLineLength) { - display.maxLine = line; - display.maxLineLength = len; - display.maxLineChanged = true; - recomputeMaxLength = false; - } - }); - if (recomputeMaxLength) cm.curOp.updateMaxLine = true; - } - - // Adjust frontier, schedule worker - doc.frontier = Math.min(doc.frontier, from.line); - startWorker(cm, 400); - - var lendiff = change.text.length - (to.line - from.line) - 1; - // Remember that these lines changed, for updating the display - regChange(cm, from.line, to.line + 1, lendiff); - - if (hasHandler(cm, "change")) { - var changeObj = {from: from, to: to, - text: change.text, - removed: change.removed, - origin: change.origin}; - if (cm.curOp.textChanged) { - for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {} - cur.next = changeObj; - } else cm.curOp.textChanged = changeObj; - } - } - - function replaceRange(doc, code, from, to, origin) { - if (!to) to = from; - if (posLess(to, from)) { var tmp = to; to = from; from = tmp; } - if (typeof code == "string") code = splitLines(code); - makeChange(doc, {from: from, to: to, text: code, origin: origin}, null); - } - - // POSITION OBJECT - - function Pos(line, ch) { - if (!(this instanceof Pos)) return new Pos(line, ch); - this.line = line; this.ch = ch; - } - CodeMirror.Pos = Pos; - - function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} - function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} - function copyPos(x) {return Pos(x.line, x.ch);} - - // SELECTION - - function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));} - function clipPos(doc, pos) { - if (pos.line < doc.first) return Pos(doc.first, 0); - var last = doc.first + doc.size - 1; - if (pos.line > last) return Pos(last, getLine(doc, last).text.length); - return clipToLen(pos, getLine(doc, pos.line).text.length); - } - function clipToLen(pos, linelen) { - var ch = pos.ch; - if (ch == null || ch > linelen) return Pos(pos.line, linelen); - else if (ch < 0) return Pos(pos.line, 0); - else return pos; - } - function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;} - - // If shift is held, this will move the selection anchor. Otherwise, - // it'll set the whole selection. - function extendSelection(doc, pos, other, bias) { - if (doc.sel.shift || doc.sel.extend) { - var anchor = doc.sel.anchor; - if (other) { - var posBefore = posLess(pos, anchor); - if (posBefore != posLess(other, anchor)) { - anchor = pos; - pos = other; - } else if (posBefore != posLess(pos, other)) { - pos = other; - } - } - setSelection(doc, anchor, pos, bias); - } else { - setSelection(doc, pos, other || pos, bias); - } - if (doc.cm) doc.cm.curOp.userSelChange = true; - } - - function filterSelectionChange(doc, anchor, head) { - var obj = {anchor: anchor, head: head}; - signal(doc, "beforeSelectionChange", doc, obj); - if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj); - obj.anchor = clipPos(doc, obj.anchor); obj.head = clipPos(doc, obj.head); - return obj; - } - - // Update the selection. Last two args are only used by - // updateDoc, since they have to be expressed in the line - // numbers before the update. - function setSelection(doc, anchor, head, bias, checkAtomic) { - if (!checkAtomic && hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) { - var filtered = filterSelectionChange(doc, anchor, head); - head = filtered.head; - anchor = filtered.anchor; - } - - var sel = doc.sel; - sel.goalColumn = null; - if (bias == null) bias = posLess(head, sel.head) ? -1 : 1; - // Skip over atomic spans. - if (checkAtomic || !posEq(anchor, sel.anchor)) - anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push"); - if (checkAtomic || !posEq(head, sel.head)) - head = skipAtomic(doc, head, bias, checkAtomic != "push"); - - if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return; - - sel.anchor = anchor; sel.head = head; - var inv = posLess(head, anchor); - sel.from = inv ? head : anchor; - sel.to = inv ? anchor : head; - - if (doc.cm) - doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = - doc.cm.curOp.cursorActivity = true; - - signalLater(doc, "cursorActivity", doc); - } - - function reCheckSelection(cm) { - setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push"); - } - - function skipAtomic(doc, pos, bias, mayClear) { - var flipped = false, curPos = pos; - var dir = bias || 1; - doc.cantEdit = false; - search: for (;;) { - var line = getLine(doc, curPos.line); - if (line.markedSpans) { - for (var i = 0; i < line.markedSpans.length; ++i) { - var sp = line.markedSpans[i], m = sp.marker; - if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) && - (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) { - if (mayClear) { - signal(m, "beforeCursorEnter"); - if (m.explicitlyCleared) { - if (!line.markedSpans) break; - else {--i; continue;} - } - } - if (!m.atomic) continue; - var newPos = m.find()[dir < 0 ? "from" : "to"]; - if (posEq(newPos, curPos)) { - newPos.ch += dir; - if (newPos.ch < 0) { - if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1)); - else newPos = null; - } else if (newPos.ch > line.text.length) { - if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0); - else newPos = null; - } - if (!newPos) { - if (flipped) { - // Driven in a corner -- no valid cursor position found at all - // -- try again *with* clearing, if we didn't already - if (!mayClear) return skipAtomic(doc, pos, bias, true); - // Otherwise, turn off editing until further notice, and return the start of the doc - doc.cantEdit = true; - return Pos(doc.first, 0); - } - flipped = true; newPos = pos; dir = -dir; - } - } - curPos = newPos; - continue search; - } - } - } - return curPos; - } - } - - // SCROLLING - - function scrollCursorIntoView(cm) { - var coords = scrollPosIntoView(cm, cm.doc.sel.head, null, cm.options.cursorScrollMargin); - if (!cm.state.focused) return; - var display = cm.display, box = getRect(display.sizer), 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 hidden = display.cursor.style.display == "none"; - if (hidden) { - display.cursor.style.display = ""; - display.cursor.style.left = coords.left + "px"; - display.cursor.style.top = (coords.top - display.viewOffset) + "px"; - } - display.cursor.scrollIntoView(doScroll); - if (hidden) display.cursor.style.display = "none"; - } - } - - function scrollPosIntoView(cm, pos, end, margin) { - if (margin == null) margin = 0; - for (;;) { - 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), - Math.min(coords.top, endCoords.top) - margin, - Math.max(coords.left, endCoords.left), - Math.max(coords.bottom, endCoords.bottom) + margin); - var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; - if (scrollPos.scrollTop != null) { - setScrollTop(cm, scrollPos.scrollTop); - if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true; - } - if (scrollPos.scrollLeft != null) { - setScrollLeft(cm, scrollPos.scrollLeft); - if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true; - } - if (!changed) return coords; - } - } - - function scrollIntoView(cm, x1, y1, x2, y2) { - var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2); - if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop); - if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft); - } - - function calculateScrollPos(cm, x1, y1, x2, y2) { - var display = cm.display, snapMargin = textHeight(cm.display); - if (y1 < 0) y1 = 0; - var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {}; - var docBottom = cm.doc.height + paddingVert(display); - var atTop = y1 < snapMargin, atBottom = y2 > docBottom - snapMargin; - if (y1 < screentop) { - result.scrollTop = atTop ? 0 : y1; - } else if (y2 > screentop + screen) { - var newTop = Math.min(y1, (atBottom ? docBottom : y2) - screen); - if (newTop != screentop) result.scrollTop = newTop; - } - - var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft; - 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; - } - return result; - } - - function updateScrollPos(cm, left, top) { - cm.curOp.updateScrollPos = {scrollLeft: left == null ? cm.doc.scrollLeft : left, - scrollTop: top == null ? cm.doc.scrollTop : top}; - } - - function addToScrollPos(cm, left, top) { - var pos = cm.curOp.updateScrollPos || (cm.curOp.updateScrollPos = {scrollLeft: cm.doc.scrollLeft, scrollTop: cm.doc.scrollTop}); - var scroll = cm.display.scroller; - pos.scrollTop = Math.max(0, Math.min(scroll.scrollHeight - scroll.clientHeight, pos.scrollTop + top)); - pos.scrollLeft = Math.max(0, Math.min(scroll.scrollWidth - scroll.clientWidth, pos.scrollLeft + left)); - } - - // API UTILITIES - - function indentLine(cm, n, how, aggressive) { - var doc = cm.doc; - if (how == null) how = "add"; - if (how == "smart") { - if (!cm.doc.mode.indent) how = "prev"; - else var state = getStateBefore(cm, n); - } - - var tabSize = cm.options.tabSize; - var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); - var curSpaceString = line.text.match(/^\s*/)[0], indentation; - if (how == "smart") { - indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); - if (indentation == Pass) { - if (!aggressive) return; - how = "prev"; - } - } - if (how == "prev") { - if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize); - else indentation = 0; - } else if (how == "add") { - indentation = curSpace + cm.options.indentUnit; - } else if (how == "subtract") { - indentation = curSpace - cm.options.indentUnit; - } else if (typeof how == "number") { - indentation = curSpace + how; - } - indentation = Math.max(0, indentation); - - var indentString = "", pos = 0; - if (cm.options.indentWithTabs) - for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} - if (pos < indentation) indentString += spaceStr(indentation - pos); - - if (indentString != curSpaceString) - replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); - else if (doc.sel.head.line == n && doc.sel.head.ch < curSpaceString.length) - setSelection(doc, Pos(n, curSpaceString.length), Pos(n, curSpaceString.length), 1); - line.stateAfter = null; - } - - function changeLine(cm, handle, op) { - var no = handle, line = handle, doc = cm.doc; - if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle)); - else no = lineNo(handle); - if (no == null) return null; - if (op(line, no)) regChange(cm, no, no + 1); - else return null; - return line; - } - - function findPosH(doc, pos, dir, unit, visually) { - var line = pos.line, ch = pos.ch, origDir = dir; - var lineObj = getLine(doc, line); - var possible = true; - function findNextLine() { - var l = line + dir; - if (l < doc.first || l >= doc.first + doc.size) return (possible = false); - line = l; - return lineObj = getLine(doc, l); - } - function moveOnce(boundToLine) { - var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true); - if (next == null) { - if (!boundToLine && findNextLine()) { - if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj); - else ch = dir < 0 ? lineObj.text.length : 0; - } else return (possible = false); - } else ch = next; - return true; - } - - if (unit == "char") moveOnce(); - else if (unit == "column") moveOnce(true); - else if (unit == "word" || unit == "group") { - var sawType = null, group = unit == "group"; - for (var first = true;; first = false) { - if (dir < 0 && !moveOnce(!first)) break; - var cur = lineObj.text.charAt(ch) || "\n"; - var type = isWordChar(cur) ? "w" - : !group ? null - : /\s/.test(cur) ? null - : "p"; - if (sawType && sawType != type) { - if (dir < 0) {dir = 1; moveOnce();} - break; - } - if (type) sawType = type; - if (dir > 0 && !moveOnce(!first)) break; - } - } - var result = skipAtomic(doc, Pos(line, ch), origDir, true); - if (!possible) result.hitSide = true; - return result; - } - - function findPosV(cm, pos, dir, unit) { - var doc = cm.doc, x = pos.left, y; - if (unit == "page") { - var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); - y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); - } else if (unit == "line") { - y = dir > 0 ? pos.bottom + 3 : pos.top - 3; - } - for (;;) { - var target = coordsChar(cm, x, y); - if (!target.outside) break; - if (dir < 0 ? y <= 0 : y >= doc.height) { target.hitSide = true; break; } - y += dir * 5; - } - return target; - } - - function findWordAt(line, pos) { - 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 {from: Pos(pos.line, start), to: Pos(pos.line, end)}; - } - - function selectLine(cm, line) { - extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0))); - } - - // PROTOTYPE - - // The publicly visible API. Note that operation(null, f) means - // 'wrap f in an operation, performed on its `this` parameter' - - CodeMirror.prototype = { - constructor: CodeMirror, - focus: function(){window.focus(); focusInput(this); fastPoll(this);}, - - setOption: function(option, value) { - var options = this.options, old = options[option]; - if (options[option] == value && option != "mode") return; - options[option] = value; - if (optionHandlers.hasOwnProperty(option)) - operation(this, optionHandlers[option])(this, value, old); - }, - - getOption: function(option) {return this.options[option];}, - getDoc: function() {return this.doc;}, - - addKeyMap: function(map, bottom) { - this.state.keyMaps[bottom ? "push" : "unshift"](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)) { - maps.splice(i, 1); - return true; - } - }, - - addOverlay: operation(null, function(spec, options) { - var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); - if (mode.startState) throw new Error("Overlays may not be stateful."); - this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); - this.state.modeGen++; - regChange(this); - }), - removeOverlay: operation(null, function(spec) { - var overlays = this.state.overlays; - for (var i = 0; i < overlays.length; ++i) { - var cur = overlays[i].modeSpec; - if (cur == spec || typeof spec == "string" && cur.name == spec) { - overlays.splice(i, 1); - this.state.modeGen++; - regChange(this); - return; - } - } - }), - - indentLine: operation(null, function(n, dir, aggressive) { - if (typeof dir != "string" && typeof dir != "number") { - if (dir == null) dir = this.options.smartIndent ? "smart" : "prev"; - else dir = dir ? "add" : "subtract"; - } - if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive); - }), - indentSelection: operation(null, function(how) { - var sel = this.doc.sel; - if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how); - var e = sel.to.line - (sel.to.ch ? 0 : 1); - for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how); - }), - - // 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(), - className: style || null, // Deprecated, use 'type' instead - type: style || null, - state: state}; - }, - - 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 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]; - } - }, - - getModeAt: function(pos) { - var mode = this.doc.mode; - if (!mode.innerMode) return mode; - return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode; - }, - - getHelper: function(pos, type) { - if (!helpers.hasOwnProperty(type)) return; - var help = helpers[type], mode = this.getModeAt(pos); - return mode[type] && help[mode[type]] || - mode.helperType && help[mode.helperType] || - help[mode.name]; - }, - - getStateAfter: function(line, precise) { - var doc = this.doc; - line = clipLine(doc, line == null ? doc.first + doc.size - 1: line); - return getStateBefore(this, line + 1, precise); - }, - - cursorCoords: function(start, mode) { - var pos, sel = this.doc.sel; - if (start == null) pos = sel.head; - else if (typeof start == "object") pos = clipPos(this.doc, start); - else pos = start ? sel.from : sel.to; - return cursorCoords(this, pos, mode || "page"); - }, - - charCoords: function(pos, mode) { - return charCoords(this, clipPos(this.doc, pos), mode || "page"); - }, - - coordsChar: function(coords, mode) { - coords = fromCoordSystem(this, coords, mode || "page"); - return coordsChar(this, coords.left, coords.top); - }, - - lineAtHeight: function(height, mode) { - height = fromCoordSystem(this, {top: height, left: 0}, mode || "page").top; - return lineAtHeight(this.doc, height + this.display.viewOffset); - }, - heightAtLine: function(line, mode) { - var end = false, last = this.doc.first + this.doc.size - 1; - if (line < this.doc.first) line = this.doc.first; - else if (line > last) { line = last; end = true; } - var lineObj = getLine(this.doc, line); - return intoCoordSystem(this, getLine(this.doc, line), {top: 0, left: 0}, mode || "page").top + - (end ? lineObj.height : 0); - }, - - defaultTextHeight: function() { return textHeight(this.display); }, - defaultCharWidth: function() { return charWidth(this.display); }, - - setGutterMarker: operation(null, function(line, gutterID, value) { - return changeLine(this, line, function(line) { - var markers = line.gutterMarkers || (line.gutterMarkers = {}); - markers[gutterID] = value; - if (!value && isEmpty(markers)) line.gutterMarkers = null; - return true; - }); - }), - - clearGutter: operation(null, function(gutterID) { - var cm = this, doc = cm.doc, i = doc.first; - doc.iter(function(line) { - if (line.gutterMarkers && line.gutterMarkers[gutterID]) { - line.gutterMarkers[gutterID] = null; - regChange(cm, i, i + 1); - if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null; - } - ++i; - }); - }), - - addLineClass: operation(null, function(handle, where, cls) { - return changeLine(this, handle, 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: operation(null, function(handle, where, cls) { - return changeLine(this, handle, 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: operation(null, function(handle, node, options) { - return addLineWidget(this, handle, node, options); - }), - - removeLineWidget: function(widget) { widget.clear(); }, - - lineInfo: function(line) { - if (typeof line == "number") { - if (!isLine(this.doc, line)) return null; - var n = line; - line = getLine(this.doc, line); - if (!line) return null; - } else { - var n = lineNo(line); - if (n == null) return null; - } - return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers, - textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass, - widgets: line.widgets}; - }, - - getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};}, - - addWidget: function(pos, node, scroll, vert, horiz) { - var display = this.display; - pos = cursorCoords(this, clipPos(this.doc, pos)); - var top = pos.bottom, left = pos.left; - node.style.position = "absolute"; - display.sizer.appendChild(node); - if (vert == "over") { - top = pos.top; - } else if (vert == "above" || vert == "near") { - var vspace = Math.max(display.wrapper.clientHeight, this.doc.height), - hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth); - // Default to positioning above (if specified and possible); otherwise default to positioning below - if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight) - top = pos.top - node.offsetHeight; - else if (pos.bottom + node.offsetHeight <= vspace) - top = pos.bottom; - if (left + node.offsetWidth > hspace) - left = hspace - node.offsetWidth; - } - node.style.top = top + "px"; - node.style.left = node.style.right = ""; - if (horiz == "right") { - left = display.sizer.clientWidth - node.offsetWidth; - node.style.right = "0px"; - } else { - if (horiz == "left") left = 0; - else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2; - node.style.left = left + "px"; - } - if (scroll) - scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight); - }, - - triggerOnKeyDown: operation(null, onKeyDown), - - execCommand: function(cmd) {return commands[cmd](this);}, - - findPosH: function(from, amount, unit, visually) { - var dir = 1; - if (amount < 0) { dir = -1; amount = -amount; } - for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { - cur = findPosH(this.doc, cur, dir, unit, visually); - if (cur.hitSide) break; - } - return cur; - }, - - moveH: operation(null, function(dir, unit) { - var sel = this.doc.sel, pos; - if (sel.shift || sel.extend || posEq(sel.from, sel.to)) - pos = findPosH(this.doc, sel.head, dir, unit, this.options.rtlMoveVisually); - else - pos = dir < 0 ? sel.from : sel.to; - extendSelection(this.doc, pos, pos, dir); - }), - - deleteH: operation(null, function(dir, unit) { - var sel = this.doc.sel; - if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete"); - else replaceRange(this.doc, "", sel.from, findPosH(this.doc, sel.head, dir, unit, false), "+delete"); - this.curOp.userSelChange = true; - }), - - findPosV: function(from, amount, unit, goalColumn) { - var dir = 1, x = goalColumn; - if (amount < 0) { dir = -1; amount = -amount; } - for (var i = 0, cur = clipPos(this.doc, from); i < amount; ++i) { - var coords = cursorCoords(this, cur, "div"); - if (x == null) x = coords.left; - else coords.left = x; - cur = findPosV(this, coords, dir, unit); - if (cur.hitSide) break; - } - return cur; - }, - - moveV: operation(null, function(dir, unit) { - var sel = this.doc.sel; - var pos = cursorCoords(this, sel.head, "div"); - if (sel.goalColumn != null) pos.left = sel.goalColumn; - var target = findPosV(this, pos, dir, unit); - - if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top); - extendSelection(this.doc, target, target, dir); - sel.goalColumn = pos.left; - }), - - toggleOverwrite: function(value) { - if (value != null && value == this.state.overwrite) return; - if (this.state.overwrite = !this.state.overwrite) - this.display.cursor.className += " CodeMirror-overwrite"; - else - this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", ""); - }, - hasFocus: function() { return this.state.focused; }, - - scrollTo: operation(null, function(x, y) { - updateScrollPos(this, x, y); - }), - getScrollInfo: function() { - var scroller = this.display.scroller, co = scrollerCutOff; - return {left: scroller.scrollLeft, top: scroller.scrollTop, - height: scroller.scrollHeight - co, width: scroller.scrollWidth - co, - clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; - }, - - scrollIntoView: operation(null, function(range, margin) { - if (range == null) range = {from: this.doc.sel.head, to: null}; - else if (typeof range == "number") range = {from: Pos(range, 0), to: null}; - else if (range.from == null) range = {from: range, to: null}; - if (!range.to) range.to = range.from; - if (!margin) margin = 0; - - var coords = range; - if (range.from.line != null) { - this.curOp.scrollToPos = {from: range.from, to: range.to, margin: margin}; - coords = {from: cursorCoords(this, range.from), - to: cursorCoords(this, range.to)}; - } - var sPos = calculateScrollPos(this, Math.min(coords.from.left, coords.to.left), - Math.min(coords.from.top, coords.to.top) - margin, - Math.max(coords.from.right, coords.to.right), - Math.max(coords.from.bottom, coords.to.bottom) + margin); - updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop); - }), - - setSize: operation(null, function(width, height) { - 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) - this.display.measureLineCache.length = this.display.measureLineCachePos = 0; - this.curOp.forceUpdate = true; - }), - - operation: function(f){return runInOp(this, f);}, - - refresh: operation(null, function() { - var badHeight = this.display.cachedTextHeight == null; - clearCaches(this); - updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop); - regChange(this); - if (badHeight) estimateLineHeights(this); - }), - - swapDoc: operation(null, function(doc) { - var old = this.doc; - old.cm = null; - attachDoc(this, doc); - clearCaches(this); - resetInput(this, true); - updateScrollPos(this, doc.scrollLeft, doc.scrollTop); - signalLater(this, "swapDoc", this, old); - return old; - }), - - getInputField: function(){return this.display.input;}, - getWrapperElement: function(){return this.display.wrapper;}, - getScrollerElement: function(){return this.display.scroller;}, - getGutterElement: function(){return this.display.gutters;} - }; - eventMixin(CodeMirror); - - // OPTION DEFAULTS - - var optionHandlers = CodeMirror.optionHandlers = {}; - - // The default configuration options. - var defaults = CodeMirror.defaults = {}; - - function option(name, deflt, handle, notOnInit) { - CodeMirror.defaults[name] = deflt; - if (handle) optionHandlers[name] = - notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle; - } - - var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}}; - - // These two are, on init, called from the constructor because they - // have to be initialized before the editor can start at all. - option("value", "", function(cm, val) { - cm.setValue(val); - }, true); - option("mode", null, function(cm, val) { - cm.doc.modeOption = val; - loadMode(cm); - }, true); - - option("indentUnit", 2, loadMode, true); - option("indentWithTabs", false); - option("smartIndent", true); - option("tabSize", 4, function(cm) { - loadMode(cm); - clearCaches(cm); - regChange(cm); - }, true); - option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm, val) { - cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); - cm.refresh(); - }, true); - option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); - option("electricChars", true); - option("rtlMoveVisually", !windows); - option("wholeLineUpdateBefore", true); - - option("theme", "default", function(cm) { - themeChanged(cm); - guttersChanged(cm); - }, true); - option("keyMap", "default", keyMapChanged); - option("extraKeys", null); - - option("onKeyEvent", null); - option("onDragEvent", null); - - option("lineWrapping", false, wrappingChanged, true); - option("gutters", [], function(cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("fixedGutter", true, function(cm, val) { - cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0"; - cm.refresh(); - }, true); - option("coverGutterNextToScrollbar", false, updateScrollbars, true); - option("lineNumbers", false, function(cm) { - setGuttersForLineNumbers(cm.options); - guttersChanged(cm); - }, true); - option("firstLineNumber", 1, guttersChanged, true); - option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); - option("showCursorWhenSelecting", false, updateSelection, true); - - option("resetSelectionOnContextMenu", true); - - option("readOnly", false, function(cm, val) { - if (val == "nocursor") { - onBlur(cm); - cm.display.input.blur(); - cm.display.disabled = true; - } else { - cm.display.disabled = false; - if (!val) resetInput(cm, true); - } - }); - option("dragDrop", true); - - option("cursorBlinkRate", 530); - option("cursorScrollMargin", 0); - option("cursorHeight", 1); - option("workTime", 100); - option("workDelay", 100); - option("flattenSpans", true); - option("pollInterval", 100); - option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;}); - option("historyEventDelay", 500); - option("viewportMargin", 10, function(cm){cm.refresh();}, true); - option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true); - option("crudeMeasuringFrom", 10000); - option("moveInputWithCursor", true, function(cm, val) { - if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0; - }); - - option("tabindex", null, function(cm, val) { - cm.display.input.tabIndex = val || ""; - }); - option("autofocus", null); - - // MODE DEFINITION AND QUERYING - - // Known modes, by name and by MIME - var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; - - 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]); - } - 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)) { - var found = mimeModes[spec.name]; - spec = createObj(found, spec); - spec.name = found.name; - } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { - return CodeMirror.resolveMode("application/xml"); - } - if (typeof spec == "string") return {name: spec}; - else return spec || {name: "null"}; - }; - - CodeMirror.getMode = function(options, spec) { - var spec = CodeMirror.resolveMode(spec); - var mfactory = modes[spec.name]; - if (!mfactory) return CodeMirror.getMode(options, "text/plain"); - var modeObj = mfactory(options, spec); - if (modeExtensions.hasOwnProperty(spec.name)) { - var exts = modeExtensions[spec.name]; - for (var prop in exts) { - if (!exts.hasOwnProperty(prop)) continue; - if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop]; - modeObj[prop] = exts[prop]; - } - } - modeObj.name = spec.name; - - return modeObj; - }; - - CodeMirror.defineMode("null", function() { - return {token: function(stream) {stream.skipToEnd();}}; - }); - CodeMirror.defineMIME("text/plain", "null"); - - var modeExtensions = CodeMirror.modeExtensions = {}; - CodeMirror.extendMode = function(mode, properties) { - var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); - copyObj(properties, exts); - }; - - // EXTENSIONS - - CodeMirror.defineExtension = function(name, func) { - CodeMirror.prototype[name] = func; - }; - CodeMirror.defineDocExtension = function(name, func) { - Doc.prototype[name] = func; - }; - CodeMirror.defineOption = option; - - var initHooks = []; - CodeMirror.defineInitHook = function(f) {initHooks.push(f);}; - - var helpers = CodeMirror.helpers = {}; - CodeMirror.registerHelper = function(type, name, value) { - if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {}; - helpers[type][name] = value; - }; - - // UTILITIES - - CodeMirror.isWordChar = isWordChar; - - // MODE STATE HANDLING - - // Utility functions for working with state. Exported because modes - // sometimes need to do this. - function copyState(mode, state) { - if (state === true) return state; - if (mode.copyState) return mode.copyState(state); - var nstate = {}; - for (var n in state) { - var val = state[n]; - if (val instanceof Array) val = val.concat([]); - nstate[n] = val; - } - return nstate; - } - CodeMirror.copyState = copyState; - - function startState(mode, a1, a2) { - return mode.startState ? mode.startState(a1, a2) : true; - } - CodeMirror.startState = startState; - - CodeMirror.innerMode = function(mode, state) { - while (mode.innerMode) { - var info = mode.innerMode(state); - if (!info || info.mode == mode) break; - state = info.state; - mode = info.mode; - } - return info || {mode: mode, state: state}; - }; - - // STANDARD COMMANDS - - var commands = CodeMirror.commands = { - selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));}, - killLine: function(cm) { - var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); - if (!sel && cm.getLine(from.line).length == from.ch) - cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete"); - else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete"); - }, - deleteLine: function(cm) { - var l = cm.getCursor().line; - cm.replaceRange("", Pos(l, 0), Pos(l), "+delete"); - }, - delLineLeft: function(cm) { - var cur = cm.getCursor(); - cm.replaceRange("", Pos(cur.line, 0), cur, "+delete"); - }, - undo: function(cm) {cm.undo();}, - redo: function(cm) {cm.redo();}, - goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));}, - goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));}, - goLineStart: function(cm) { - cm.extendSelection(lineStart(cm, cm.getCursor().line)); - }, - goLineStartSmart: function(cm) { - var cur = cm.getCursor(), start = lineStart(cm, cur.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 = cur.line == start.line && cur.ch <= firstNonWS && cur.ch; - cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS)); - } else cm.extendSelection(start); - }, - goLineEnd: function(cm) { - cm.extendSelection(lineEnd(cm, cm.getCursor().line)); - }, - goLineRight: function(cm) { - var top = cm.charCoords(cm.getCursor(), "div").top + 5; - cm.extendSelection(cm.coordsChar({left: cm.display.lineDiv.offsetWidth + 100, top: top}, "div")); - }, - goLineLeft: function(cm) { - var top = cm.charCoords(cm.getCursor(), "div").top + 5; - cm.extendSelection(cm.coordsChar({left: 0, top: top}, "div")); - }, - goLineUp: function(cm) {cm.moveV(-1, "line");}, - goLineDown: function(cm) {cm.moveV(1, "line");}, - goPageUp: function(cm) {cm.moveV(-1, "page");}, - goPageDown: function(cm) {cm.moveV(1, "page");}, - goCharLeft: function(cm) {cm.moveH(-1, "char");}, - goCharRight: function(cm) {cm.moveH(1, "char");}, - goColumnLeft: function(cm) {cm.moveH(-1, "column");}, - goColumnRight: function(cm) {cm.moveH(1, "column");}, - goWordLeft: function(cm) {cm.moveH(-1, "word");}, - goGroupRight: function(cm) {cm.moveH(1, "group");}, - goGroupLeft: function(cm) {cm.moveH(-1, "group");}, - goWordRight: function(cm) {cm.moveH(1, "word");}, - delCharBefore: function(cm) {cm.deleteH(-1, "char");}, - delCharAfter: function(cm) {cm.deleteH(1, "char");}, - delWordBefore: function(cm) {cm.deleteH(-1, "word");}, - delWordAfter: function(cm) {cm.deleteH(1, "word");}, - delGroupBefore: function(cm) {cm.deleteH(-1, "group");}, - delGroupAfter: function(cm) {cm.deleteH(1, "group");}, - indentAuto: function(cm) {cm.indentSelection("smart");}, - indentMore: function(cm) {cm.indentSelection("add");}, - indentLess: function(cm) {cm.indentSelection("subtract");}, - insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");}, - defaultTab: function(cm) { - if (cm.somethingSelected()) cm.indentSelection("add"); - else cm.replaceSelection("\t", "end", "+input"); - }, - transposeChars: function(cm) { - var cur = cm.getCursor(), line = cm.getLine(cur.line); - 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)); - }, - newlineAndIndent: function(cm) { - operation(cm, function() { - cm.replaceSelection("\n", "end", "+input"); - cm.indentLine(cm.getCursor().line, null, true); - })(); - }, - 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", - "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", - "Tab": "defaultTab", "Shift-Tab": "indentAuto", - "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" - }; - // Note that the save and find-related commands aren't defined by - // default. Unknown commands are simply ignored. - keyMap.pcDefault = { - "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", - "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", - "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", - "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", - 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", - fallthrough: ["basic", "emacsy"] - }; - keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; - keyMap.emacsy = { - "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", - "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", - "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 DISPATCH - - function getKeyMap(val) { - if (typeof val == "string") return keyMap[val]; - else return val; - } - - function lookupKey(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"; - - var fallthrough = map.fallthrough; - if (fallthrough == null) return false; - if (Object.prototype.toString.call(fallthrough) != "[object Array]") - return lookup(fallthrough); - for (var i = 0, e = fallthrough.length; i < e; ++i) { - var done = lookup(fallthrough[i]); - if (done) return done; - } - return false; - } - - for (var i = 0; i < maps.length; ++i) { - var done = lookup(maps[i]); - if (done) return done != "stop"; - } - } - function isModifierKey(event) { - var name = keyNames[event.keyCode]; - return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; - } - function keyName(event, noShift) { - if (opera && event.keyCode == 34 && event["char"]) return false; - var name = keyNames[event.keyCode]; - 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; - return name; - } - CodeMirror.lookupKey = lookupKey; - CodeMirror.isModifierKey = isModifierKey; - CodeMirror.keyName = keyName; - - // FROMTEXTAREA - - CodeMirror.fromTextArea = function(textarea, options) { - if (!options) options = {}; - options.value = textarea.value; - if (!options.tabindex && textarea.tabindex) - options.tabindex = textarea.tabindex; - if (!options.placeholder && textarea.placeholder) - options.placeholder = textarea.placeholder; - // Set autofocus to true if this textarea is focused, or if it has - // autofocus and no other element is focused. - if (options.autofocus == null) { - var hasFocus = document.body; - // doc.activeElement occasionally throws on IE - try { hasFocus = document.activeElement; } catch(e) {} - options.autofocus = hasFocus == textarea || - textarea.getAttribute("autofocus") != null && hasFocus == document.body; - } - - function save() {textarea.value = cm.getValue();} - if (textarea.form) { - on(textarea.form, "submit", save); - // Deplorable hack to make the submit method do the right thing. - if (!options.leaveSubmitMethodAlone) { - var form = textarea.form, realSubmit = form.submit; - try { - var wrappedSubmit = form.submit = function() { - save(); - form.submit = realSubmit; - form.submit(); - form.submit = wrappedSubmit; - }; - } catch(e) {} - } - } - - 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; - }; - - // STRING STREAM - - // Fed to the mode parsers, provides helper functions to make - // parsers more succinct. - - // The character stream used by a mode's parser. - function StringStream(string, tabSize) { - this.pos = this.start = 0; - this.string = string; - this.tabSize = tabSize || 8; - this.lastColumnPos = this.lastColumnValue = 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) || undefined;}, - 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() { - if (this.lastColumnPos < this.start) { - this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); - this.lastColumnPos = this.start; - } - return this.lastColumnValue; - }, - indentation: function() {return countColumn(this.string, null, this.tabSize);}, - 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);} - }; - CodeMirror.StringStream = StringStream; - - // TEXTMARKERS - - function TextMarker(doc, type) { - this.lines = []; - this.type = type; - this.doc = doc; - } - CodeMirror.TextMarker = TextMarker; - eventMixin(TextMarker); - - TextMarker.prototype.clear = function() { - if (this.explicitlyCleared) return; - var cm = this.doc.cm, withOp = cm && !cm.curOp; - if (withOp) startOperation(cm); - if (hasHandler(this, "clear")) { - var found = this.find(); - if (found) signalLater(this, "clear", found.from, found.to); - } - var min = null, max = null; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (span.to != null) max = lineNo(line); - line.markedSpans = removeMarkedSpan(line.markedSpans, span); - if (span.from != null) - min = lineNo(line); - else if (this.collapsed && !lineIsHidden(this.doc, line) && cm) - updateLineHeight(line, textHeight(cm.display)); - } - if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) { - var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual); - if (len > cm.display.maxLineLength) { - cm.display.maxLine = visual; - cm.display.maxLineLength = len; - cm.display.maxLineChanged = true; - } - } - - if (min != null && cm) regChange(cm, min, max + 1); - this.lines.length = 0; - this.explicitlyCleared = true; - if (this.atomic && this.doc.cantEdit) { - this.doc.cantEdit = false; - if (cm) reCheckSelection(cm); - } - if (withOp) endOperation(cm); - }; - - TextMarker.prototype.find = function() { - var from, to; - for (var i = 0; i < this.lines.length; ++i) { - var line = this.lines[i]; - var span = getMarkedSpanFor(line.markedSpans, this); - if (span.from != null || span.to != null) { - var found = lineNo(line); - if (span.from != null) from = Pos(found, span.from); - if (span.to != null) to = Pos(found, span.to); - } - } - if (this.type == "bookmark") return from; - return from && {from: from, to: to}; - }; - - TextMarker.prototype.changed = function() { - var pos = this.find(), cm = this.doc.cm; - if (!pos || !cm) return; - if (this.type != "bookmark") pos = pos.from; - var line = getLine(this.doc, pos.line); - clearCachedMeasurement(cm, line); - if (pos.line >= cm.display.showingFrom && pos.line < cm.display.showingTo) { - for (var node = cm.display.lineDiv.firstChild; node; node = node.nextSibling) if (node.lineObj == line) { - if (node.offsetHeight != line.height) updateLineHeight(line, node.offsetHeight); - break; - } - runInOp(cm, function() { - cm.curOp.selectionChanged = cm.curOp.forceUpdate = cm.curOp.updateMaxLine = true; - }); - } - }; - - TextMarker.prototype.attachLine = function(line) { - if (!this.lines.length && this.doc.cm) { - var op = this.doc.cm.curOp; - if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1) - (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this); - } - this.lines.push(line); - }; - TextMarker.prototype.detachLine = function(line) { - this.lines.splice(indexOf(this.lines, line), 1); - if (!this.lines.length && this.doc.cm) { - var op = this.doc.cm.curOp; - (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this); - } - }; - - function markText(doc, from, to, options, type) { - if (options && options.shared) return markTextShared(doc, from, to, options, type); - if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); - - var marker = new TextMarker(doc, type); - if (posLess(to, from) || posEq(from, to) && type == "range" && - !(options.inclusiveLeft && options.inclusiveRight)) - return marker; - if (options) copyObj(options, marker); - if (marker.replacedWith) { - marker.collapsed = true; - marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget"); - if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true; - } - if (marker.collapsed) sawCollapsedSpans = true; - - if (marker.addToHistory) - addToHistory(doc, {from: from, to: to, origin: "markText"}, - {head: doc.sel.head, anchor: doc.sel.anchor}, NaN); - - var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine; - doc.iter(curLine, to.line + 1, function(line) { - if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine) - updateMaxLine = true; - var span = {from: null, to: null, marker: marker}; - size += line.text.length; - if (curLine == from.line) {span.from = from.ch; size -= from.ch;} - if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;} - if (marker.collapsed) { - if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch); - if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch); - else updateLineHeight(line, 0); - } - addMarkedSpan(line, span); - ++curLine; - }); - if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) { - if (lineIsHidden(doc, line)) updateLineHeight(line, 0); - }); - - if (marker.clearOnEnter) on(marker, "beforeCursorEnter", function() { marker.clear(); }); - - if (marker.readOnly) { - sawReadOnlySpans = true; - if (doc.history.done.length || doc.history.undone.length) - doc.clearHistory(); - } - if (marker.collapsed) { - if (collapsedAtStart != collapsedAtEnd) - throw new Error("Inserting collapsed marker overlapping an existing one"); - marker.size = size; - marker.atomic = true; - } - if (cm) { - if (updateMaxLine) cm.curOp.updateMaxLine = true; - if (marker.className || marker.title || marker.startStyle || marker.endStyle || marker.collapsed) - regChange(cm, from.line, to.line + 1); - if (marker.atomic) reCheckSelection(cm); - } - return marker; - } - - // SHARED TEXTMARKERS - - function SharedTextMarker(markers, primary) { - this.markers = markers; - this.primary = primary; - for (var i = 0, me = this; i < markers.length; ++i) { - markers[i].parent = this; - on(markers[i], "clear", function(){me.clear();}); - } - } - CodeMirror.SharedTextMarker = SharedTextMarker; - eventMixin(SharedTextMarker); - - SharedTextMarker.prototype.clear = function() { - if (this.explicitlyCleared) return; - this.explicitlyCleared = true; - for (var i = 0; i < this.markers.length; ++i) - this.markers[i].clear(); - signalLater(this, "clear"); - }; - SharedTextMarker.prototype.find = function() { - return this.primary.find(); - }; - - function markTextShared(doc, from, to, options, type) { - options = copyObj(options); - options.shared = false; - var markers = [markText(doc, from, to, options, type)], primary = markers[0]; - var widget = options.replacedWith; - linkedDocs(doc, function(doc) { - if (widget) options.replacedWith = widget.cloneNode(true); - markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type)); - for (var i = 0; i < doc.linked.length; ++i) - if (doc.linked[i].isParent) return; - primary = lst(markers); - }); - return new SharedTextMarker(markers, primary); - } - - // TEXTMARKER SPANS - - function getMarkedSpanFor(spans, marker) { - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if (span.marker == marker) return span; - } - } - function removeMarkedSpan(spans, span) { - for (var r, i = 0; i < spans.length; ++i) - if (spans[i] != span) (r || (r = [])).push(spans[i]); - return r; - } - function addMarkedSpan(line, span) { - line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span]; - span.marker.attachLine(line); - } - - function markedSpansBefore(old, startCh, isInsert) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); - if (startsBefore || - (marker.inclusiveLeft && marker.inclusiveRight || marker.type == "bookmark") && - span.from == startCh && (!isInsert || !span.marker.insertLeft)) { - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); - (nw || (nw = [])).push({from: span.from, - to: endsAfter ? null : span.to, - marker: marker}); - } - } - return nw; - } - - function markedSpansAfter(old, endCh, isInsert) { - if (old) for (var i = 0, nw; i < old.length; ++i) { - var span = old[i], marker = span.marker; - var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); - if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) { - var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); - (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, - to: span.to == null ? null : span.to - endCh, - marker: marker}); - } - } - return nw; - } - - function stretchSpansOverChange(doc, change) { - 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; - - var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to); - // Get the spans that 'stick out' on both sides - var first = markedSpansBefore(oldFirst, startCh, isInsert); - var last = markedSpansAfter(oldLast, endCh, isInsert); - - // Next, merge those two ends - var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0); - if (first) { - // Fix up .to properties of first - for (var i = 0; i < first.length; ++i) { - var span = first[i]; - if (span.to == null) { - var found = getMarkedSpanFor(last, span.marker); - if (!found) span.to = startCh; - else if (sameLine) span.to = found.to == null ? null : found.to + offset; - } - } - } - if (last) { - // Fix up .from in last (or move them into first in case of sameLine) - for (var i = 0; i < last.length; ++i) { - var span = last[i]; - if (span.to != null) span.to += offset; - if (span.from == null) { - var found = getMarkedSpanFor(first, span.marker); - if (!found) { - span.from = offset; - if (sameLine) (first || (first = [])).push(span); - } - } else { - span.from += offset; - if (sameLine) (first || (first = [])).push(span); - } - } - } - if (sameLine && first) { - // Make sure we didn't create any zero-length spans - for (var i = 0; i < first.length; ++i) - if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark") - first.splice(i--, 1); - if (!first.length) first = null; - } - - var newMarkers = [first]; - if (!sameLine) { - // Fill gap with whole-line-spans - var gap = change.text.length - 2, gapMarkers; - if (gap > 0 && first) - for (var i = 0; i < first.length; ++i) - if (first[i].to == null) - (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker}); - for (var i = 0; i < gap; ++i) - newMarkers.push(gapMarkers); - newMarkers.push(last); - } - return newMarkers; - } - - function mergeOldSpans(doc, change) { - var old = getOldSpans(doc, change); - var stretched = stretchSpansOverChange(doc, change); - if (!old) return stretched; - if (!stretched) return old; - - for (var i = 0; i < old.length; ++i) { - var oldCur = old[i], stretchCur = stretched[i]; - if (oldCur && stretchCur) { - spans: for (var j = 0; j < stretchCur.length; ++j) { - var span = stretchCur[j]; - for (var k = 0; k < oldCur.length; ++k) - if (oldCur[k].marker == span.marker) continue spans; - oldCur.push(span); - } - } else if (stretchCur) { - old[i] = stretchCur; - } - } - return old; - } - - function removeReadOnlyRanges(doc, from, to) { - var markers = null; - doc.iter(from.line, to.line + 1, function(line) { - if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) { - var mark = line.markedSpans[i].marker; - if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) - (markers || (markers = [])).push(mark); - } - }); - if (!markers) return null; - var parts = [{from: from, to: to}]; - for (var i = 0; i < markers.length; ++i) { - var mk = markers[i], m = mk.find(); - for (var j = 0; j < parts.length; ++j) { - var p = parts[j]; - if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue; - var newParts = [j, 1]; - if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from)) - newParts.push({from: p.from, to: m.from}); - if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to)) - newParts.push({from: m.to, to: p.to}); - parts.splice.apply(parts, newParts); - j += newParts.length - 1; - } - } - return parts; - } - - function collapsedSpanAt(line, ch) { - var sps = sawCollapsedSpans && line.markedSpans, found; - if (sps) for (var sp, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (!sp.marker.collapsed) continue; - if ((sp.from == null || sp.from < ch) && - (sp.to == null || sp.to > ch) && - (!found || found.width < sp.marker.width)) - found = sp.marker; - } - return found; - } - function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); } - function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); } - - function visualLine(doc, line) { - var merged; - while (merged = collapsedSpanAtStart(line)) - line = getLine(doc, merged.find().from.line); - return line; - } - - function lineIsHidden(doc, line) { - var sps = sawCollapsedSpans && line.markedSpans; - if (sps) for (var sp, i = 0; i < sps.length; ++i) { - sp = sps[i]; - if (!sp.marker.collapsed) continue; - if (sp.from == null) return true; - if (sp.marker.replacedWith) continue; - if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) - return true; - } - } - function lineIsHiddenInner(doc, line, span) { - if (span.to == null) { - var end = span.marker.find().to, endLine = getLine(doc, end.line); - return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker)); - } - if (span.marker.inclusiveRight && span.to == line.text.length) - return true; - for (var sp, i = 0; i < line.markedSpans.length; ++i) { - sp = line.markedSpans[i]; - if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to && - (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && - lineIsHiddenInner(doc, line, sp)) return true; - } - } - - function detachMarkedSpans(line) { - var spans = line.markedSpans; - if (!spans) return; - for (var i = 0; i < spans.length; ++i) - spans[i].marker.detachLine(line); - line.markedSpans = null; - } - - function attachMarkedSpans(line, spans) { - if (!spans) return; - for (var i = 0; i < spans.length; ++i) - spans[i].marker.attachLine(line); - line.markedSpans = spans; - } - - // LINE WIDGETS - - var LineWidget = CodeMirror.LineWidget = function(cm, node, options) { - if (options) for (var opt in options) if (options.hasOwnProperty(opt)) - this[opt] = options[opt]; - this.cm = cm; - this.node = node; - }; - eventMixin(LineWidget); - function widgetOperation(f) { - return function() { - var withOp = !this.cm.curOp; - if (withOp) startOperation(this.cm); - try {var result = f.apply(this, arguments);} - finally {if (withOp) endOperation(this.cm);} - return result; - }; - } - LineWidget.prototype.clear = widgetOperation(function() { - var ws = this.line.widgets, no = lineNo(this.line); - if (no == null || !ws) return; - for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1); - if (!ws.length) this.line.widgets = null; - var aboveVisible = heightAtLine(this.cm, this.line) < this.cm.doc.scrollTop; - updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this))); - if (aboveVisible) addToScrollPos(this.cm, 0, -this.height); - regChange(this.cm, no, no + 1); - }); - LineWidget.prototype.changed = widgetOperation(function() { - var oldH = this.height; - this.height = null; - var diff = widgetHeight(this) - oldH; - if (!diff) return; - updateLineHeight(this.line, this.line.height + diff); - var no = lineNo(this.line); - regChange(this.cm, no, no + 1); - }); - - function widgetHeight(widget) { - if (widget.height != null) return widget.height; - if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1) - removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative")); - 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, 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); - widget.line = line; - if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) { - var aboveVisible = heightAtLine(cm, line) < cm.doc.scrollTop; - updateLineHeight(line, line.height + widgetHeight(widget)); - if (aboveVisible) addToScrollPos(cm, 0, widget.height); - } - return true; - }); - return widget; - } - - // LINE DATA STRUCTURE - - // Line objects. These hold state related to a line, including - // highlighting info (the styles array). - var Line = CodeMirror.Line = function(text, markedSpans, estimateHeight) { - this.text = text; - attachMarkedSpans(this, markedSpans); - this.height = estimateHeight ? estimateHeight(this) : 1; - }; - eventMixin(Line); - Line.prototype.lineNo = function() { return lineNo(this); }; - - function updateLine(line, text, markedSpans, estimateHeight) { - line.text = text; - if (line.stateAfter) line.stateAfter = null; - if (line.styles) line.styles = null; - if (line.order != null) line.order = null; - detachMarkedSpans(line); - attachMarkedSpans(line, markedSpans); - var estHeight = estimateHeight ? estimateHeight(line) : 1; - if (estHeight != line.height) updateLineHeight(line, estHeight); - } - - function cleanUpLine(line) { - line.parent = null; - detachMarkedSpans(line); - } - - // Run the given mode's parser over a line, update the styles - // array, which contains alternating fragments of text and CSS - // classes. - function runMode(cm, text, mode, state, f, 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); - while (!stream.eol()) { - if (stream.pos > cm.options.maxHighlightLength) { - flattenSpans = false; - if (forceToEnd) processLine(cm, text, state, stream.pos); - stream.pos = text.length; - style = null; - } else { - style = mode.token(stream, state); - } - if (!flattenSpans || curStyle != style) { - if (curStart < stream.start) f(stream.start, curStyle); - curStart = stream.start; curStyle = style; - } - stream.start = stream.pos; - } - while (curStart < stream.pos) { - // Webkit seems to refuse to render text nodes longer than 57444 characters - var pos = Math.min(stream.pos, curStart + 50000); - f(pos, curStyle); - curStart = pos; - } - } - - 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]; - // Compute the base array of styles - runMode(cm, line.text, cm.doc.mode, state, function(end, style) { - st.push(end, style); - }, forceToEnd); - - // Run overlays, adjust style array. - for (var o = 0; o < cm.state.overlays.length; ++o) { - var overlay = cm.state.overlays[o], i = 1, at = 0; - runMode(cm, line.text, overlay.mode, true, function(end, style) { - var start = i; - // Ensure there's a token end at the current position, and that i points at it - while (at < end) { - var i_end = st[i]; - if (i_end > end) - st.splice(i, 1, end, st[i+1], i_end); - i += 2; - at = Math.min(end, i_end); - } - if (!style) return; - if (overlay.opaque) { - st.splice(start, i - start, end, style); - i = start + 2; - } else { - for (; start < i; start += 2) { - var cur = st[start+1]; - st[start+1] = cur ? cur + " " + style : style; - } - } - }); - } - - return st; - } - - function getLineStyles(cm, line) { - if (!line.styles || line.styles[0] != cm.state.modeGen) - line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line))); - return line.styles; - } - - // Lightweight form of highlight -- proceed over this line and - // update state, but don't save a style array. - function processLine(cm, text, state, startAt) { - 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); - while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { - mode.token(stream, state); - stream.start = stream.pos; - } - } - - var styleToClassCache = {}; - 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]; - } - return styleToClassCache[style] || - (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); - } - - function buildLineContent(cm, realLine, measure, copyWidgets) { - var merged, line = realLine, empty = true; - while (merged = collapsedSpanAtStart(line)) - line = getLine(cm.doc, merged.find().from.line); - - var builder = {pre: elt("pre"), col: 0, pos: 0, - measure: null, measuredSomething: false, cm: cm, - copyWidgets: copyWidgets}; - - do { - if (line.text) empty = false; - builder.measure = line == realLine && measure; - builder.pos = 0; - builder.addToken = builder.measure ? buildTokenMeasure : buildToken; - if ((ie || webkit) && cm.getOption("lineWrapping")) - builder.addToken = buildTokenSplitSpaces(builder.addToken); - var next = insertLineContent(line, builder, getLineStyles(cm, line)); - if (measure && line == realLine && !builder.measuredSomething) { - measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure)); - builder.measuredSomething = true; - } - if (next) line = getLine(cm.doc, next.to.line); - } while (next); - - if (measure && !builder.measuredSomething && !measure[0]) - measure[0] = builder.pre.appendChild(empty ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure)); - if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine)) - builder.pre.appendChild(document.createTextNode("\u00a0")); - - var order; - // Work around problem with the reported dimensions of single-char - // direction spans on IE (issue #1129). See also the comment in - // cursorCoords. - if (measure && (ie || ie_gt10) && (order = getOrder(line))) { - var l = order.length - 1; - if (order[l].from == order[l].to) --l; - var last = order[l], prev = order[l - 1]; - if (last.from + 1 == last.to && prev && last.level < prev.level) { - var span = measure[builder.pos - 1]; - if (span) span.parentNode.insertBefore(span.measureRight = zeroWidthElement(cm.display.measure), - span.nextSibling); - } - } - - var textClass = builder.textClass ? builder.textClass + " " + (realLine.textClass || "") : realLine.textClass; - if (textClass) builder.pre.className = textClass; - - signal(cm, "renderLine", cm, realLine, builder.pre); - return builder; - } - - function defaultSpecialCharPlaceholder(ch) { - var token = elt("span", "\u2022", "cm-invalidchar"); - token.title = "\\u" + ch.charCodeAt(0).toString(16); - return token; - } - - function buildToken(builder, text, style, startStyle, endStyle, title) { - if (!text) return; - var special = builder.cm.options.specialChars; - if (!special.test(text)) { - builder.col += text.length; - var content = document.createTextNode(text); - } else { - var content = document.createDocumentFragment(), pos = 0; - while (true) { - special.lastIndex = pos; - var m = special.exec(text); - var skipped = m ? m.index - pos : text.length - pos; - if (skipped) { - content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); - builder.col += skipped; - } - if (!m) break; - pos += skipped + 1; - if (m[0] == "\t") { - var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize; - content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); - builder.col += tabWidth; - } else { - var token = builder.cm.options.specialCharPlaceholder(m[0]); - content.appendChild(token); - builder.col += 1; - } - } - } - if (style || startStyle || endStyle || builder.measure) { - var fullStyle = style || ""; - if (startStyle) fullStyle += startStyle; - if (endStyle) fullStyle += endStyle; - var token = elt("span", [content], fullStyle); - if (title) token.title = title; - return builder.pre.appendChild(token); - } - builder.pre.appendChild(content); - } - - function buildTokenMeasure(builder, text, style, startStyle, endStyle) { - var wrapping = builder.cm.options.lineWrapping; - for (var i = 0; i < text.length; ++i) { - var ch = text.charAt(i), start = i == 0; - if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) { - ch = text.slice(i, i + 2); - ++i; - } else if (i && wrapping && spanAffectsWrapping(text, i)) { - builder.pre.appendChild(elt("wbr")); - } - var old = builder.measure[builder.pos]; - var span = builder.measure[builder.pos] = - buildToken(builder, ch, style, - start && startStyle, i == text.length - 1 && endStyle); - if (old) span.leftSide = old.leftSide || old; - // In IE single-space nodes wrap differently than spaces - // embedded in larger text nodes, except when set to - // white-space: normal (issue #1268). - if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) && - i < text.length - 1 && !/\s/.test(text.charAt(i + 1))) - span.style.whiteSpace = "normal"; - builder.pos += ch.length; - } - if (text.length) builder.measuredSomething = true; - } - - function buildTokenSplitSpaces(inner) { - function split(old) { - var out = " "; - for (var i = 0; i < old.length - 2; ++i) out += i % 2 ? " " : "\u00a0"; - out += " "; - return out; - } - return function(builder, text, style, startStyle, endStyle, title) { - return inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title); - }; - } - - function buildCollapsedSpan(builder, size, marker, ignoreWidget) { - var widget = !ignoreWidget && marker.replacedWith; - if (widget) { - if (builder.copyWidgets) widget = widget.cloneNode(true); - builder.pre.appendChild(widget); - if (builder.measure) { - if (size) { - builder.measure[builder.pos] = widget; - } else { - var elt = zeroWidthElement(builder.cm.display.measure); - if (marker.type == "bookmark" && !marker.insertLeft) - builder.measure[builder.pos] = builder.pre.appendChild(elt); - else if (builder.measure[builder.pos]) - return; - else - builder.measure[builder.pos] = builder.pre.insertBefore(elt, widget); - } - builder.measuredSomething = true; - } - } - builder.pos += size; - } - - // Outputs a number of spans to make up a line, taking highlighting - // and marked text into account. - function insertLineContent(line, builder, styles) { - 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)); - return; - } - - var len = allText.length, pos = 0, i = 1, text = "", style; - var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed; - for (;;) { - if (nextChange == pos) { // Update current marker set - spanStyle = spanEndStyle = spanStartStyle = title = ""; - collapsed = null; nextChange = Infinity; - var foundBookmarks = []; - for (var j = 0; j < spans.length; ++j) { - var sp = spans[j], m = sp.marker; - 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.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; - if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; - if (m.title && !title) title = m.title; - if (m.collapsed && (!collapsed || collapsed.marker.size < m.size)) - collapsed = sp; - } else if (sp.from > pos && nextChange > sp.from) { - nextChange = sp.from; - } - if (m.type == "bookmark" && sp.from == pos && m.replacedWith) foundBookmarks.push(m); - } - if (collapsed && (collapsed.from || 0) == pos) { - buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos, - collapsed.marker, collapsed.from == null); - if (collapsed.to == null) return collapsed.marker.find(); - } - if (!collapsed && foundBookmarks.length) for (var j = 0; j < foundBookmarks.length; ++j) - buildCollapsedSpan(builder, 0, foundBookmarks[j]); - } - if (pos >= len) break; - - var upto = Math.min(len, nextChange); - while (true) { - if (text) { - var end = pos + text.length; - 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); - } - 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); - } - } - } - - // DOCUMENT DATA STRUCTURE - - function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) { - function spansFor(n) {return markedSpans ? markedSpans[n] : null;} - function update(line, text, spans) { - updateLine(line, text, spans, estimateHeight); - signalLater(line, "change", line, change); - } - - 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; - - // First adjust the line structure - if (from.ch == 0 && to.ch == 0 && lastText == "" && - (!doc.cm || doc.cm.options.wholeLineUpdateBefore)) { - // This is a whole-line replace. Treated specially to make - // sure line objects move the way they are supposed to. - for (var i = 0, e = text.length - 1, added = []; i < e; ++i) - added.push(new Line(text[i], spansFor(i), estimateHeight)); - update(lastLine, lastLine.text, lastSpans); - if (nlines) doc.remove(from.line, nlines); - if (added.length) doc.insert(from.line, added); - } else if (firstLine == lastLine) { - 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, e = text.length - 1; i < e; ++i) - added.push(new Line(text[i], spansFor(i), estimateHeight)); - 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); - } - } else if (text.length == 1) { - update(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch), spansFor(0)); - doc.remove(from.line + 1, nlines); - } 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, e = text.length - 1, added = []; i < e; ++i) - added.push(new Line(text[i], spansFor(i), estimateHeight)); - if (nlines > 1) doc.remove(from.line + 1, nlines - 1); - doc.insert(from.line + 1, added); - } - - signalLater(doc, "change", doc, change); - setSelection(doc, selAfter.anchor, selAfter.head, null, true); - } - - function LeafChunk(lines) { - this.lines = lines; - this.parent = null; - for (var i = 0, e = lines.length, height = 0; i < e; ++i) { - lines[i].parent = this; - height += lines[i].height; - } - this.height = height; - } - - LeafChunk.prototype = { - chunkSize: function() { return this.lines.length; }, - removeInner: function(at, n) { - for (var i = at, e = at + n; i < e; ++i) { - var line = this.lines[i]; - this.height -= line.height; - cleanUpLine(line); - signalLater(line, "delete"); - } - this.lines.splice(at, n); - }, - collapse: function(lines) { - lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); - }, - insertInner: function(at, lines, height) { - this.height += height; - this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); - for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; - }, - iterN: function(at, n, op) { - for (var e = at + n; at < e; ++at) - if (op(this.lines[at])) return true; - } - }; - - function BranchChunk(children) { - this.children = children; - var size = 0, height = 0; - for (var i = 0, e = children.length; i < e; ++i) { - var ch = children[i]; - size += ch.chunkSize(); height += ch.height; - ch.parent = this; - } - this.size = size; - this.height = height; - this.parent = null; - } - - BranchChunk.prototype = { - chunkSize: function() { return this.size; }, - removeInner: function(at, n) { - this.size -= n; - for (var i = 0; i < this.children.length; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var rm = Math.min(n, sz - at), oldHeight = child.height; - child.removeInner(at, rm); - this.height -= oldHeight - child.height; - if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } - if ((n -= rm) == 0) break; - at = 0; - } else at -= sz; - } - if (this.size - n < 25) { - var lines = []; - this.collapse(lines); - this.children = [new LeafChunk(lines)]; - this.children[0].parent = this; - } - }, - collapse: function(lines) { - for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); - }, - insertInner: function(at, lines, height) { - this.size += lines.length; - this.height += height; - for (var i = 0, e = this.children.length; i < e; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at <= sz) { - child.insertInner(at, lines, height); - if (child.lines && child.lines.length > 50) { - while (child.lines.length > 50) { - var spilled = child.lines.splice(child.lines.length - 25, 25); - var newleaf = new LeafChunk(spilled); - child.height -= newleaf.height; - this.children.splice(i + 1, 0, newleaf); - newleaf.parent = this; - } - this.maybeSpill(); - } - break; - } - at -= sz; - } - }, - maybeSpill: function() { - if (this.children.length <= 10) return; - var me = this; - do { - var spilled = me.children.splice(me.children.length - 5, 5); - var sibling = new BranchChunk(spilled); - if (!me.parent) { // Become the parent node - var copy = new BranchChunk(me.children); - copy.parent = me; - me.children = [copy, sibling]; - me = copy; - } else { - me.size -= sibling.size; - me.height -= sibling.height; - var myIndex = indexOf(me.parent.children, me); - me.parent.children.splice(myIndex + 1, 0, sibling); - } - sibling.parent = me.parent; - } while (me.children.length > 10); - me.parent.maybeSpill(); - }, - iterN: function(at, n, op) { - for (var i = 0, e = this.children.length; i < e; ++i) { - var child = this.children[i], sz = child.chunkSize(); - if (at < sz) { - var used = Math.min(n, sz - at); - if (child.iterN(at, used, op)) return true; - if ((n -= used) == 0) break; - at = 0; - } else at -= sz; - } - } - }; - - var nextDocId = 0; - var Doc = CodeMirror.Doc = function(text, mode, firstLine) { - if (!(this instanceof Doc)) return new Doc(text, mode, firstLine); - if (firstLine == null) firstLine = 0; - - BranchChunk.call(this, [new LeafChunk([new Line("", null)])]); - this.first = firstLine; - this.scrollTop = this.scrollLeft = 0; - this.cantEdit = false; - this.history = makeHistory(); - this.cleanGeneration = 1; - this.frontier = firstLine; - var start = Pos(firstLine, 0); - this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null}; - this.id = ++nextDocId; - this.modeOption = mode; - - if (typeof text == "string") text = splitLines(text); - updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start}); - }; - - Doc.prototype = createObj(BranchChunk.prototype, { - constructor: Doc, - iter: function(from, to, op) { - if (op) this.iterN(from - this.first, to - from, op); - else this.iterN(this.first, this.first + this.size, from); - }, - - insert: function(at, lines) { - var height = 0; - for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; - this.insertInner(at - this.first, lines, height); - }, - remove: function(at, n) { this.removeInner(at - this.first, n); }, - - getValue: function(lineSep) { - var lines = getLines(this, this.first, this.first + this.size); - if (lineSep === false) return lines; - return lines.join(lineSep || "\n"); - }, - setValue: 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"}, - {head: top, anchor: top}, true); - }, - replaceRange: function(code, from, to, origin) { - from = clipPos(this, from); - to = to ? clipPos(this, to) : from; - replaceRange(this, code, from, to, origin); - }, - getRange: function(from, to, lineSep) { - var lines = getBetween(this, clipPos(this, from), clipPos(this, to)); - if (lineSep === false) return lines; - return lines.join(lineSep || "\n"); - }, - - getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;}, - setLine: function(line, text) { - if (isLine(this, line)) - replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line))); - }, - removeLine: function(line) { - if (line) replaceRange(this, "", clipPos(this, Pos(line - 1)), clipPos(this, Pos(line))); - else replaceRange(this, "", Pos(0, 0), clipPos(this, Pos(1, 0))); - }, - - getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);}, - getLineNumber: function(line) {return lineNo(line);}, - - getLineHandleVisualStart: function(line) { - if (typeof line == "number") line = getLine(this, line); - return visualLine(this, line); - }, - - lineCount: function() {return this.size;}, - firstLine: function() {return this.first;}, - lastLine: function() {return this.first + this.size - 1;}, - - clipPos: function(pos) {return clipPos(this, pos);}, - - getCursor: function(start) { - var sel = this.sel, pos; - if (start == null || start == "head") pos = sel.head; - else if (start == "anchor") pos = sel.anchor; - else if (start == "end" || start === false) pos = sel.to; - else pos = sel.from; - return copyPos(pos); - }, - somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);}, - - setCursor: docOperation(function(line, ch, extend) { - var pos = clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line); - if (extend) extendSelection(this, pos); - else setSelection(this, pos, pos); - }), - setSelection: docOperation(function(anchor, head, bias) { - setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), bias); - }), - extendSelection: docOperation(function(from, to, bias) { - extendSelection(this, clipPos(this, from), to && clipPos(this, to), bias); - }), - - getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);}, - replaceSelection: function(code, collapse, origin) { - makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || "around"); - }, - undo: docOperation(function() {makeChangeFromHistory(this, "undo");}), - redo: docOperation(function() {makeChangeFromHistory(this, "redo");}), - - setExtending: function(val) {this.sel.extend = val;}, - - historySize: function() { - var hist = this.history; - return {undo: hist.done.length, redo: hist.undone.length}; - }, - clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);}, - - markClean: function() { - this.cleanGeneration = this.changeGeneration(); - }, - changeGeneration: function() { - this.history.lastOp = this.history.lastOrigin = null; - return this.history.generation; - }, - isClean: function (gen) { - return this.history.generation == (gen || this.cleanGeneration); - }, - - getHistory: function() { - return {done: copyHistoryArray(this.history.done), - undone: copyHistoryArray(this.history.undone)}; - }, - setHistory: function(histData) { - var hist = this.history = makeHistory(this.history.maxGeneration); - hist.done = histData.done.slice(0); - hist.undone = histData.undone.slice(0); - }, - - markText: function(from, to, options) { - return markText(this, clipPos(this, from), clipPos(this, to), options, "range"); - }, - setBookmark: function(pos, options) { - var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), - insertLeft: options && options.insertLeft}; - pos = clipPos(this, pos); - return markText(this, pos, pos, realOpts, "bookmark"); - }, - findMarksAt: function(pos) { - pos = clipPos(this, pos); - var markers = [], spans = getLine(this, pos.line).markedSpans; - if (spans) for (var i = 0; i < spans.length; ++i) { - var span = spans[i]; - if ((span.from == null || span.from <= pos.ch) && - (span.to == null || span.to >= pos.ch)) - markers.push(span.marker.parent || span.marker); - } - return markers; - }, - getAllMarks: function() { - var markers = []; - this.iter(function(line) { - var sps = line.markedSpans; - if (sps) for (var i = 0; i < sps.length; ++i) - if (sps[i].from != null) markers.push(sps[i].marker); - }); - return markers; - }, - - posFromIndex: function(off) { - var ch, lineNo = this.first; - this.iter(function(line) { - var sz = line.text.length + 1; - if (sz > off) { ch = off; return true; } - off -= sz; - ++lineNo; - }); - return clipPos(this, Pos(lineNo, ch)); - }, - indexFromPos: function (coords) { - coords = clipPos(this, coords); - var index = coords.ch; - if (coords.line < this.first || coords.ch < 0) return 0; - this.iter(this.first, coords.line, function (line) { - index += line.text.length + 1; - }); - return index; - }, - - copy: function(copyHistory) { - var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first); - doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft; - doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor, - shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn}; - if (copyHistory) { - doc.history.undoDepth = this.history.undoDepth; - doc.setHistory(this.getHistory()); - } - return doc; - }, - - linkedDoc: function(options) { - if (!options) options = {}; - var from = this.first, to = this.first + this.size; - if (options.from != null && options.from > from) from = options.from; - if (options.to != null && options.to < to) to = options.to; - var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from); - 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}]; - return copy; - }, - unlinkDoc: function(other) { - if (other instanceof CodeMirror) other = other.doc; - if (this.linked) for (var i = 0; i < this.linked.length; ++i) { - var link = this.linked[i]; - if (link.doc != other) continue; - this.linked.splice(i, 1); - other.unlinkDoc(this); - break; - } - // If the histories were shared, split them again - if (other.history == this.history) { - var splitIds = [other.id]; - linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true); - other.history = makeHistory(); - other.history.done = copyHistoryArray(this.history.done, splitIds); - other.history.undone = copyHistoryArray(this.history.undone, splitIds); - } - }, - iterLinkedDocs: function(f) {linkedDocs(this, f);}, - - getMode: function() {return this.mode;}, - getEditor: function() {return this.cm;} - }); - - Doc.prototype.eachLine = Doc.prototype.iter; - - // The Doc methods that should be available on CodeMirror instances - var dontDelegate = "iter insert remove copy getEditor".split(" "); - for (var prop in Doc.prototype) if (Doc.prototype.hasOwnProperty(prop) && indexOf(dontDelegate, prop) < 0) - CodeMirror.prototype[prop] = (function(method) { - return function() {return method.apply(this.doc, arguments);}; - })(Doc.prototype[prop]); - - eventMixin(Doc); - - function linkedDocs(doc, f, sharedHistOnly) { - function propagate(doc, skip, sharedHist) { - if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) { - var rel = doc.linked[i]; - if (rel.doc == skip) continue; - var shared = sharedHist && rel.sharedHist; - if (sharedHistOnly && !shared) continue; - f(rel.doc, shared); - propagate(rel.doc, doc, shared); - } - } - propagate(doc, null, true); - } - - function attachDoc(cm, doc) { - if (doc.cm) throw new Error("This document is already in use."); - cm.doc = doc; - doc.cm = cm; - estimateLineHeights(cm); - loadMode(cm); - if (!cm.options.lineWrapping) computeMaxLength(cm); - cm.options.mode = doc.modeOption; - regChange(cm); - } - - // LINE UTILITIES - - function getLine(chunk, n) { - n -= chunk.first; - while (!chunk.lines) { - for (var i = 0;; ++i) { - var child = chunk.children[i], sz = child.chunkSize(); - if (n < sz) { chunk = child; break; } - n -= sz; - } - } - return chunk.lines[n]; - } - - function getBetween(doc, start, end) { - var out = [], n = start.line; - doc.iter(start.line, end.line + 1, function(line) { - var text = line.text; - if (n == end.line) text = text.slice(0, end.ch); - if (n == start.line) text = text.slice(start.ch); - out.push(text); - ++n; - }); - return out; - } - function getLines(doc, from, to) { - var out = []; - doc.iter(from, to, function(line) { out.push(line.text); }); - return out; - } - - function updateLineHeight(line, height) { - var diff = height - line.height; - for (var n = line; n; n = n.parent) n.height += diff; - } - - function lineNo(line) { - if (line.parent == null) return null; - var cur = line.parent, no = indexOf(cur.lines, line); - for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { - for (var i = 0;; ++i) { - if (chunk.children[i] == cur) break; - no += chunk.children[i].chunkSize(); - } - } - return no + cur.first; - } - - function lineAtHeight(chunk, h) { - var n = chunk.first; - outer: do { - for (var i = 0, e = chunk.children.length; i < e; ++i) { - var child = chunk.children[i], ch = child.height; - if (h < ch) { chunk = child; continue outer; } - h -= ch; - n += child.chunkSize(); - } - return n; - } while (!chunk.lines); - for (var i = 0, e = chunk.lines.length; i < e; ++i) { - var line = chunk.lines[i], lh = line.height; - if (h < lh) break; - h -= lh; - } - return n + i; - } - - function heightAtLine(cm, lineObj) { - lineObj = visualLine(cm.doc, lineObj); - - var h = 0, chunk = lineObj.parent; - for (var i = 0; i < chunk.lines.length; ++i) { - var line = chunk.lines[i]; - if (line == lineObj) break; - else h += line.height; - } - for (var p = chunk.parent; p; chunk = p, p = chunk.parent) { - for (var i = 0; i < p.children.length; ++i) { - var cur = p.children[i]; - if (cur == chunk) break; - else h += cur.height; - } - } - return h; - } - - function getOrder(line) { - var order = line.order; - if (order == null) order = line.order = bidiOrdering(line.text); - return order; - } - - // HISTORY - - function makeHistory(startGen) { - return { - // Arrays of history events. Doing something adds an event to - // done and clears undo. Undoing moves events from done to - // undone, redoing moves them in the other direction. - done: [], undone: [], undoDepth: Infinity, - // Used to track when changes can be merged into a single undo - // event - lastTime: 0, lastOp: null, lastOrigin: null, - // Used by the isClean() method - generation: startGen || 1, maxGeneration: startGen || 1 - }; - } - - function attachLocalSpans(doc, change, from, to) { - var existing = change["spans_" + doc.id], n = 0; - doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) { - if (line.markedSpans) - (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans; - ++n; - }); - } - - function historyChangeFromChange(doc, change) { - var from = { line: change.from.line, ch: change.from.ch }; - var histChange = {from: from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)}; - attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1); - linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true); - return histChange; - } - - function addToHistory(doc, change, selAfter, opId) { - var hist = doc.history; - hist.undone.length = 0; - var time = +new Date, cur = lst(hist.done); - - if (cur && - (hist.lastOp == opId || - hist.lastOrigin == change.origin && change.origin && - ((change.origin.charAt(0) == "+" && doc.cm && hist.lastTime > time - doc.cm.options.historyEventDelay) || - change.origin.charAt(0) == "*"))) { - // Merge this change into the last event - var last = lst(cur.changes); - if (posEq(change.from, change.to) && posEq(change.from, last.to)) { - // Optimized case for simple insertion -- don't want to add - // new changesets for every character typed - last.to = changeEnd(change); - } else { - // Add new sub-event - cur.changes.push(historyChangeFromChange(doc, change)); - } - cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head; - } else { - // Can not be merged, start a new event. - cur = {changes: [historyChangeFromChange(doc, change)], - generation: hist.generation, - anchorBefore: doc.sel.anchor, headBefore: doc.sel.head, - anchorAfter: selAfter.anchor, headAfter: selAfter.head}; - hist.done.push(cur); - hist.generation = ++hist.maxGeneration; - while (hist.done.length > hist.undoDepth) - hist.done.shift(); - } - hist.lastTime = time; - hist.lastOp = opId; - hist.lastOrigin = change.origin; - } - - function removeClearedSpans(spans) { - if (!spans) return null; - for (var i = 0, out; i < spans.length; ++i) { - if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); } - else if (out) out.push(spans[i]); - } - return !out ? spans : out.length ? out : null; - } - - function getOldSpans(doc, change) { - var found = change["spans_" + doc.id]; - if (!found) return null; - for (var i = 0, nw = []; i < change.text.length; ++i) - nw.push(removeClearedSpans(found[i])); - return nw; - } - - // Used both to provide a JSON-safe object in .getHistory, and, when - // detaching a document, to split the history in two - function copyHistoryArray(events, newGroup) { - for (var i = 0, copy = []; i < events.length; ++i) { - var event = events[i], changes = event.changes, newChanges = []; - copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore, - anchorAfter: event.anchorAfter, headAfter: event.headAfter}); - for (var j = 0; j < changes.length; ++j) { - var change = changes[j], m; - newChanges.push({from: change.from, to: change.to, text: change.text}); - if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) { - if (indexOf(newGroup, Number(m[1])) > -1) { - lst(newChanges)[prop] = change[prop]; - delete change[prop]; - } - } - } - } - return copy; - } - - // Rebasing/resetting history to deal with externally-sourced changes - - function rebaseHistSel(pos, from, to, diff) { - if (to < pos.line) { - pos.line += diff; - } else if (from < pos.line) { - pos.line = from; - pos.ch = 0; - } - } - - // Tries to rebase an array of history events given a change in the - // document. If the change touches the same lines as the event, the - // event, and everything 'behind' it, is discarded. If the change is - // before the event, the event's positions are updated. Uses a - // copy-on-write scheme for the positions, to avoid having to - // reallocate them all on every rebase, but also avoid problems with - // shared position objects being unsafely updated. - function rebaseHistArray(array, from, to, diff) { - for (var i = 0; i < array.length; ++i) { - var sub = array[i], ok = true; - for (var j = 0; j < sub.changes.length; ++j) { - var cur = sub.changes[j]; - if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); } - if (to < cur.from.line) { - cur.from.line += diff; - cur.to.line += diff; - } else if (from <= cur.to.line) { - ok = false; - break; - } - } - if (!sub.copied) { - sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore); - sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter); - sub.copied = true; - } - if (!ok) { - array.splice(0, i + 1); - i = 0; - } else { - rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore); - rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter); - } - } - } - - function rebaseHist(hist, change) { - var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1; - rebaseHistArray(hist.done, from, to, diff); - rebaseHistArray(hist.undone, from, to, diff); - } - - // EVENT OPERATORS - - function stopMethod() {e_stop(this);} - // Ensure an event has a stop method. - function addStop(event) { - if (!event.stop) event.stop = stopMethod; - return event; - } - - function e_preventDefault(e) { - if (e.preventDefault) e.preventDefault(); - else e.returnValue = false; - } - function e_stopPropagation(e) { - if (e.stopPropagation) e.stopPropagation(); - else e.cancelBubble = true; - } - function e_defaultPrevented(e) { - return e.defaultPrevented != null ? e.defaultPrevented : e.returnValue == false; - } - function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} - CodeMirror.e_stop = e_stop; - CodeMirror.e_preventDefault = e_preventDefault; - CodeMirror.e_stopPropagation = e_stopPropagation; - - function e_target(e) {return e.target || e.srcElement;} - function e_button(e) { - var b = e.which; - if (b == null) { - if (e.button & 1) b = 1; - else if (e.button & 2) b = 3; - else if (e.button & 4) b = 2; - } - if (mac && e.ctrlKey && b == 1) b = 3; - return b; - } - - // EVENT HANDLING - - function on(emitter, type, f) { - if (emitter.addEventListener) - emitter.addEventListener(type, f, false); - else if (emitter.attachEvent) - emitter.attachEvent("on" + type, f); - else { - var map = emitter._handlers || (emitter._handlers = {}); - var arr = map[type] || (map[type] = []); - arr.push(f); - } - } - - function off(emitter, type, f) { - if (emitter.removeEventListener) - emitter.removeEventListener(type, f, false); - else if (emitter.detachEvent) - emitter.detachEvent("on" + type, f); - else { - var arr = emitter._handlers && emitter._handlers[type]; - if (!arr) return; - for (var i = 0; i < arr.length; ++i) - if (arr[i] == f) { arr.splice(i, 1); break; } - } - } - - function signal(emitter, type /*, values...*/) { - var arr = emitter._handlers && emitter._handlers[type]; - if (!arr) return; - var args = Array.prototype.slice.call(arguments, 2); - for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args); - } - - 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); - } - function bnd(f) {return function(){f.apply(null, args);};}; - for (var i = 0; i < arr.length; ++i) - delayedCallbacks.push(bnd(arr[i])); - } - - function signalDOMEvent(cm, e, override) { - signal(cm, override || e.type, cm, e); - return e_defaultPrevented(e) || e.codemirrorIgnore; - } - - function fireDelayed() { - --delayedCallbackDepth; - var delayed = delayedCallbacks; - delayedCallbacks = null; - for (var i = 0; i < delayed.length; ++i) delayed[i](); - } - - function hasHandler(emitter, type) { - var arr = emitter._handlers && emitter._handlers[type]; - return arr && arr.length > 0; - } - - CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal; - - function eventMixin(ctor) { - ctor.prototype.on = function(type, f) {on(this, type, f);}; - ctor.prototype.off = function(type, f) {off(this, type, f);}; - } - - // MISC UTILITIES - - // Number of pixels added to scroller and sizer to hide scrollbar - var scrollerCutOff = 30; - - // Returned or thrown by various protocols to signal 'I'm not - // handling this'. - var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; - - function Delayed() {this.id = null;} - Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; - - // Counts the column offset in a string, taking tabs into account. - // Used mostly to find indentation. - function countColumn(string, end, tabSize, startIndex, startValue) { - if (end == null) { - end = string.search(/[^\s\u00a0]/); - if (end == -1) end = string.length; - } - for (var i = startIndex || 0, n = startValue || 0; i < end; ++i) { - if (string.charAt(i) == "\t") n += tabSize - (n % tabSize); - else ++n; - } - return n; - } - CodeMirror.countColumn = countColumn; - - var spaceStrs = [""]; - function spaceStr(n) { - while (spaceStrs.length <= n) - spaceStrs.push(lst(spaceStrs) + " "); - return spaceStrs[n]; - } - - function lst(arr) { return arr[arr.length-1]; } - - function selectInput(node) { - if (ios) { // Mobile Safari apparently has a bug where select() is broken. - node.selectionStart = 0; - node.selectionEnd = node.value.length; - } else { - // Suppress mysterious IE10 errors - try { node.select(); } - catch(_e) {} - } - } - - 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; - } - - function createObj(base, props) { - function Obj() {} - Obj.prototype = base; - var inst = new Obj(); - if (props) copyObj(props, inst); - return inst; - } - - function copyObj(obj, target) { - if (!target) target = {}; - for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; - return target; - } - - function emptyArray(size) { - for (var a = [], i = 0; i < size; ++i) a.push(undefined); - return a; - } - - function bind(f) { - var args = Array.prototype.slice.call(arguments, 1); - return function(){return f.apply(null, args);}; - } - - var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/; - function isWordChar(ch) { - return /\w/.test(ch) || ch > "\x80" && - (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch)); - } - - function isEmpty(obj) { - for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false; - return true; - } - - var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\u1DC0–\u1DFF\u20D0–\u20FF\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff\uFE20–\uFE2F]/; - - // DOM 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") setTextContent(e, content); - else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); - return e; - } - - function removeChildren(e) { - for (var count = e.childNodes.length; count > 0; --count) - e.removeChild(e.firstChild); - return e; - } - - function removeChildrenAndAdd(parent, e) { - return removeChildren(parent).appendChild(e); - } - - function setTextContent(e, str) { - if (ie_lt9) { - e.innerHTML = ""; - e.appendChild(document.createTextNode(str)); - } else e.textContent = str; - } - - function getRect(node) { - return node.getBoundingClientRect(); - } - CodeMirror.replaceGetRect = function(f) { getRect = f; }; - - // 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_lt9) return false; - var div = elt('div'); - return "draggable" in div || "dragDrop" in div; - }(); - - // For a reason I have yet to figure out, some browsers disallow - // word wrapping between certain characters *only* if a new inline - // element is started between them. This makes it hard to reliably - // measure the position of things, since that requires inserting an - // extra span. This terribly fragile set of tests matches the - // character combinations that suffer from this phenomenon on the - // various browsers. - function spanAffectsWrapping() { return false; } - if (gecko) // Only for "$'" - spanAffectsWrapping = function(str, i) { - return str.charCodeAt(i - 1) == 36 && str.charCodeAt(i) == 39; - }; - else if (safari && !/Version\/([6-9]|\d\d)\b/.test(navigator.userAgent)) - spanAffectsWrapping = function(str, i) { - return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1)); - }; - else if (webkit && /Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent)) - spanAffectsWrapping = function(str, i) { - var code = str.charCodeAt(i - 1); - return code >= 8208 && code <= 8212; - }; - else if (webkit) - spanAffectsWrapping = function(str, i) { - if (i > 1 && str.charCodeAt(i - 1) == 45) { - if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true; - if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false; - } - return /[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); - }; - - 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_lt8; - } - if (zwspSupported) return elt("span", "\u200b"); - else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px"); - } - - // See if "".split is the broken IE version, if so, provide an - // alternative way to split lines. - var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { - var pos = 0, result = [], l = string.length; - while (pos <= l) { - var nl = string.indexOf("\n", pos); - if (nl == -1) nl = string.length; - var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl); - var rt = line.indexOf("\r"); - if (rt != -1) { - result.push(line.slice(0, rt)); - pos += rt + 1; - } else { - result.push(line); - pos = nl + 1; - } - } - return result; - } : function(string){return string.split(/\r\n?|\n/);}; - CodeMirror.splitLines = splitLines; - - var hasSelection = window.getSelection ? function(te) { - try { return te.selectionStart != te.selectionEnd; } - catch(e) { return false; } - } : function(te) { - try {var range = te.ownerDocument.selection.createRange();} - catch(e) {} - if (!range || range.parentElement() != te) return false; - return range.compareEndPoints("StartToEnd", range) != 0; - }; - - var hasCopyEvent = (function() { - var e = elt("div"); - if ("oncopy" in e) return true; - e.setAttribute("oncopy", "return;"); - return typeof e.oncopy == 'function'; - })(); - - // KEY NAMING - - var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", - 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", - 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", - 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", - 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", - 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", - 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; - CodeMirror.keyNames = keyNames; - (function() { - // Number keys - for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); - // Alphabetic keys - for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); - // Function keys - for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; - })(); - - // BIDI HELPERS - - function iterateBidiSections(order, from, to, f) { - if (!order) return f(from, to, "ltr"); - var found = false; - for (var i = 0; i < order.length; ++i) { - var part = order[i]; - if (part.from < to && part.to > from || from == to && part.to == from) { - f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr"); - found = true; - } - } - if (!found) f(from, to, "ltr"); - } - - function bidiLeft(part) { return part.level % 2 ? part.to : part.from; } - function bidiRight(part) { return part.level % 2 ? part.from : part.to; } - - function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; } - function lineRight(line) { - var order = getOrder(line); - if (!order) return line.text.length; - return bidiRight(lst(order)); - } - - function lineStart(cm, lineN) { - var line = getLine(cm.doc, lineN); - var visual = visualLine(cm.doc, line); - if (visual != line) lineN = lineNo(visual); - var order = getOrder(visual); - var ch = !order ? 0 : order[0].level % 2 ? lineRight(visual) : lineLeft(visual); - return Pos(lineN, ch); - } - function lineEnd(cm, lineN) { - var merged, line; - while (merged = collapsedSpanAtEnd(line = getLine(cm.doc, lineN))) - lineN = merged.find().to.line; - var order = getOrder(line); - var ch = !order ? line.text.length : order[0].level % 2 ? lineLeft(line) : lineRight(line); - return Pos(lineN, ch); - } - - function compareBidiLevel(order, a, b) { - var linedir = order[0].level; - if (a == linedir) return true; - if (b == linedir) return false; - return a < b; - } - var bidiOther; - function getBidiPartAt(order, pos) { - for (var i = 0, found; i < order.length; ++i) { - var cur = order[i]; - if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; } - if (cur.from == pos || cur.to == pos) { - if (found == null) { - found = i; - } else if (compareBidiLevel(order, cur.level, order[found].level)) { - bidiOther = found; - return i; - } else { - bidiOther = i; - return found; - } - } - } - bidiOther = null; - return found; - } - - function moveInLine(line, pos, dir, byUnit) { - if (!byUnit) return pos + dir; - do pos += dir; - while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); - return pos; - } - - // This is somewhat involved. It is needed in order to move - // 'visually' through bi-directional text -- i.e., pressing left - // should make the cursor go left, even when in RTL text. The - // tricky part is the 'jumps', where RTL and LTR text touch each - // other. This often requires the cursor offset to move more than - // one unit, in order to visually move one unit. - function moveVisually(line, start, dir, byUnit) { - var bidi = getOrder(line); - if (!bidi) return moveLogically(line, start, dir, byUnit); - var pos = getBidiPartAt(bidi, start), part = bidi[pos]; - var target = moveInLine(line, start, part.level % 2 ? -dir : dir, byUnit); - - for (;;) { - if (target > part.from && target < part.to) return target; - if (target == part.from || target == part.to) { - if (getBidiPartAt(bidi, target) == pos) return target; - part = bidi[pos += dir]; - return (dir > 0) == part.level % 2 ? part.to : part.from; - } else { - part = bidi[pos += dir]; - if (!part) return null; - if ((dir > 0) == part.level % 2) - target = moveInLine(line, part.to, -1, byUnit); - else - target = moveInLine(line, part.from, 1, byUnit); - } - } - } - - function moveLogically(line, start, dir, byUnit) { - var target = start + dir; - if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir; - return target < 0 || target > line.text.length ? null : target; - } - - // Bidirectional ordering algorithm - // See http://unicode.org/reports/tr9/tr9-13.html for the algorithm - // that this (partially) implements. - - // One-char codes used for character types: - // L (L): Left-to-Right - // R (R): Right-to-Left - // r (AL): Right-to-Left Arabic - // 1 (EN): European Number - // + (ES): European Number Separator - // % (ET): European Number Terminator - // n (AN): Arabic Number - // , (CS): Common Number Separator - // m (NSM): Non-Spacing Mark - // b (BN): Boundary Neutral - // s (B): Paragraph Separator - // t (S): Segment Separator - // w (WS): Whitespace - // N (ON): Other Neutrals - - // Returns null if characters are ordered as they appear - // (left-to-right), or an array of sections ({from, to, level} - // objects) in the order in which they occur visually. - var bidiOrdering = (function() { - // Character types for codepoints 0 to 0xff - var lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL"; - // Character types for codepoints 0x600 to 0x6ff - var arabicTypes = "rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmmrrrrrrrrrrrrrrrrrr"; - function charType(code) { - if (code <= 0xff) return lowTypes.charAt(code); - else if (0x590 <= code && code <= 0x5f4) return "R"; - else if (0x600 <= code && code <= 0x6ff) return arabicTypes.charAt(code - 0x600); - else if (0x700 <= code && code <= 0x8ac) return "r"; - else return "L"; - } - - var bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; - var isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/; - // Browsers seem to always treat the boundaries of block elements as being L. - var outerType = "L"; - - return function(str) { - if (!bidiRE.test(str)) return false; - var len = str.length, types = []; - for (var i = 0, type; i < len; ++i) - types.push(type = charType(str.charCodeAt(i))); - - // W1. Examine each non-spacing mark (NSM) in the level run, and - // change the type of the NSM to the type of the previous - // character. If the NSM is at the start of the level run, it will - // get the type of sor. - for (var i = 0, prev = outerType; i < len; ++i) { - var type = types[i]; - if (type == "m") types[i] = prev; - else prev = type; - } - - // W2. Search backwards from each instance of a European number - // until the first strong type (R, L, AL, or sor) is found. If an - // AL is found, change the type of the European number to Arabic - // number. - // W3. Change all ALs to R. - for (var i = 0, cur = outerType; i < len; ++i) { - var type = types[i]; - if (type == "1" && cur == "r") types[i] = "n"; - else if (isStrong.test(type)) { cur = type; if (type == "r") types[i] = "R"; } - } - - // W4. A single European separator between two European numbers - // changes to a European number. A single common separator between - // two numbers of the same type changes to that type. - for (var i = 1, prev = types[0]; i < len - 1; ++i) { - var type = types[i]; - if (type == "+" && prev == "1" && types[i+1] == "1") types[i] = "1"; - else if (type == "," && prev == types[i+1] && - (prev == "1" || prev == "n")) types[i] = prev; - prev = type; - } - - // W5. A sequence of European terminators adjacent to European - // numbers changes to all European numbers. - // W6. Otherwise, separators and terminators change to Other - // Neutral. - for (var i = 0; i < len; ++i) { - var type = types[i]; - if (type == ",") types[i] = "N"; - else if (type == "%") { - for (var end = i + 1; end < len && types[end] == "%"; ++end) {} - var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N"; - for (var j = i; j < end; ++j) types[j] = replace; - i = end - 1; - } - } - - // W7. Search backwards from each instance of a European number - // until the first strong type (R, L, or sor) is found. If an L is - // found, then change the type of the European number to L. - for (var i = 0, cur = outerType; i < len; ++i) { - var type = types[i]; - if (cur == "L" && type == "1") types[i] = "L"; - else if (isStrong.test(type)) cur = type; - } - - // N1. A sequence of neutrals takes the direction of the - // surrounding strong text if the text on both sides has the same - // direction. European and Arabic numbers act as if they were R in - // terms of their influence on neutrals. Start-of-level-run (sor) - // and end-of-level-run (eor) are used at level run boundaries. - // N2. Any remaining neutrals take the embedding direction. - for (var i = 0; i < len; ++i) { - if (isNeutral.test(types[i])) { - for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} - var before = (i ? types[i-1] : outerType) == "L"; - var after = (end < len - 1 ? types[end] : outerType) == "L"; - var replace = before || after ? "L" : "R"; - for (var j = i; j < end; ++j) types[j] = replace; - i = end - 1; - } - } - - // Here we depart from the documented algorithm, in order to avoid - // building up an actual levels array. Since there are only three - // levels (0, 1, 2) in an implementation that doesn't take - // explicit embedding into account, we can build up the order on - // the fly, without following the level-based algorithm. - var order = [], m; - for (var i = 0; i < len;) { - if (countsAsLeft.test(types[i])) { - var start = i; - for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} - order.push({from: start, to: i, level: 0}); - } else { - var pos = i, at = order.length; - for (++i; i < len && types[i] != "L"; ++i) {} - for (var j = pos; j < i;) { - if (countsAsNum.test(types[j])) { - if (pos < j) order.splice(at, 0, {from: pos, to: j, level: 1}); - var nstart = j; - for (++j; j < i && countsAsNum.test(types[j]); ++j) {} - order.splice(at, 0, {from: nstart, to: j, level: 2}); - pos = j; - } else ++j; - } - if (pos < i) order.splice(at, 0, {from: pos, to: i, level: 1}); - } - } - if (order[0].level == 1 && (m = str.match(/^\s+/))) { - order[0].from = m[0].length; - order.unshift({from: 0, to: m[0].length, level: 0}); - } - if (lst(order).level == 1 && (m = str.match(/\s+$/))) { - lst(order).to -= m[0].length; - order.push({from: len - m[0].length, to: len, level: 0}); - } - if (order[0].level != lst(order).level) - order.push({from: len, to: len, level: order[0].level}); - - return order; - }; - })(); - - // THE END - - CodeMirror.version = "3.20.0"; - - return CodeMirror; -})(); diff --git a/webpages/public/style.css b/webpages/public/style.css index ff87a40..27be15c 100644 --- a/webpages/public/style.css +++ b/webpages/public/style.css @@ -114,3 +114,10 @@ input[type=text]:focus { margin-left: 10px; } +.underlined { + padding-left: 10px; + font-style: italic; + font-weight: bold; + text-decoration: underline; +} +