openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit feba1e0bf3d0a798224ed7f7e4460a8c3e7ce8d3
parent daf63d7db805dbd1fcb27514a0a1c56755a2be17
Author: Jan Dankert <devnull@localhost>
Date:   Tue, 22 May 2018 22:39:53 +0200

Fix für PHP 7.2: 'Object' darf nun nicht mehr als Klassennamen verwendet werden. AUCH NICHT IN EINEM NAMESPACE! WTF, wozu habe ich das in einen verfickten Namespace gepackt? Wozu soll der sonst da sein??? Amateure. Daher nun notgedrungen unbenannt in 'BaseObject'.

Diffstat:
modules/cms-core/action/ElementAction.class.php | 6+++---
modules/cms-core/action/PageelementAction.class.php | 12++++++------
modules/cms-core/action/WebdavAction.class.php | 4++--
modules/cms-core/model/require.php | 2+-
modules/cms-ui/themes/default/html/views/element/advanced.php | 20++++++++++++++++++++
modules/cms-ui/themes/default/html/views/element/prop.php | 326+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/cms-ui/themes/default/html/views/folder/create.php | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/cms-ui/themes/default/html/views/folder/createfolder.php | 28++++++++++++++++++++++++++++
modules/cms-ui/themes/default/html/views/folder/info.php | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/cms-ui/themes/default/html/views/folder/order.php | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/cms-ui/themes/default/html/views/folder/prop.php | 40++++++++++++++++++++++++++++++++++++++++
modules/cms-ui/themes/default/html/views/folder/structure.php | 8++++++++
modules/cms-ui/themes/default/html/views/group/edit.php | 20++++++++++++++++++++
modules/cms-ui/themes/default/html/views/image/edit.php | 22++++++++++++++++++++++
modules/cms-ui/themes/default/html/views/page/edit.php | 2+-
modules/cms-ui/themes/default/html/views/page/src.tpl.src.xml | 14+++++++++++---
modules/cms-ui/themes/default/html/views/profile/edit.php | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/cms-ui/themes/default/html/views/project/export.php | 16++++++++++++++++
modules/cms-ui/themes/default/html/views/template/prop.php | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/cms-ui/themes/default/html/views/user/edit.php | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/.gitattributes | 8++++++++
modules/editor/codemirror/CHANGELOG.md | 1272+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/README.md | 35+++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/comment/comment.js | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/comment/comment.min.js | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/dialog/dialog.js | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/display/panel.js | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/display/placeholder.min.js | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/edit/closebrackets.min.js | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/edit/closetag.min.js | 169+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/edit/continuelist.min.js | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/edit/matchbrackets.js | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/edit/matchbrackets.min.js | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/fold/brace-fold.js | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/fold/markdown-fold.min.js | 49+++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/hint/css-hint.js | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/hint/show-hint.js | 432+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/hint/show-hint.min.js | 432+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/lint/html-lint.min.js | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/lint/json-lint.min.js | 37+++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/merge/merge.min.js | 1001+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/mode/multiplex_test.min.js | 33+++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/mode/simple.js | 216+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/runmode/colorize.min.js | 40++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/runmode/runmode.node.js | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/scroll/scrollpastend.min.js | 48++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/search/searchcursor.js | 289++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/selection/active-line.js | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/selection/selection-pointer.js | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/tern/tern.min.js | 717+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/addon/tern/worker.min.js | 44++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/demo/buffers.html | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/demo/emacs.html | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/demo/fullscreen.html | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/demo/html5complete.html | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/demo/indentwrap.html | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/demo/loadmode.html | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/demo/marker.html | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/demo/matchhighlighter.html | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/demo/simplemode.html | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/demo/trailingspace.html | 48++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/doc/activebookmark.min.js | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/doc/releases.html | 1557+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/index.html | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/crystal/crystal.min.js | 433+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/css/gss_test.min.js | 17+++++++++++++++++
modules/editor/codemirror/mode/css/scss_test.min.js | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/d/test.min.js | 11+++++++++++
modules/editor/codemirror/mode/diff/diff.min.js | 47+++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/dtd/dtd.min.js | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/ebnf/ebnf.min.js | 195+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/gfm/gfm.min.js | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/haskell-literate/haskell-literate.min.js | 43+++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/haxe/haxe.min.js | 515+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/julia/julia.min.js | 418+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/markdown/markdown.min.js | 861+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/markdown/test.min.js | 1289+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/meta.min.js | 216+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/octave/octave.min.js | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/pegjs/pegjs.min.js | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/php/test.min.js | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/powershell/powershell.min.js | 398+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/rpm/rpm.min.js | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/ruby/ruby.min.js | 296+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/rust/test.min.js | 39+++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/scheme/scheme.min.js | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/sieve/sieve.min.js | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/smalltalk/smalltalk.min.js | 168+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/sparql/sparql.min.js | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/stex/stex.min.js | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/ttcn/ttcn.min.js | 283+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/twig/twig.min.js | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/verilog/verilog.min.js | 675+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/xquery/xquery.min.js | 448+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/mode/yaml-frontmatter/yaml-frontmatter.min.js | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/display/gutters.min.js | 34++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/display/highlight_worker.js | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/display/scroll_events.min.js | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/display/selection.min.js | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/display/update_display.js | 260+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/edit/drop_events.js | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/edit/fromTextArea.min.js | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/edit/global_events.js | 44++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/edit/methods.min.js | 539+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/edit/options.js | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/edit/utils.js | 7+++++++
modules/editor/codemirror/src/input/ContentEditableInput.min.js | 517+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/input/TextareaInput.js | 350+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/input/keymap.js | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/line/highlight.js | 284+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/line/line_data.min.js | 337+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/line/pos.js | 40++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/line/pos.min.js | 40++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/line/spans.js | 372+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/line/spans.min.js | 372+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/model/Doc.js | 432+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/model/Doc.min.js | 432+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/model/chunk.min.js | 167+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/model/document_data.js | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/model/history.min.js | 228+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/model/selection_updates.js | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/modes.js | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/util/StringStream.js | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/util/StringStream.min.js | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/util/bidi.js | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/util/bidi.min.js | 214+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/util/browser.min.js | 33+++++++++++++++++++++++++++++++++
modules/editor/codemirror/src/util/feature_detection.js | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/test/comment_test.min.js | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/test/driver.js | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/test/index.html | 269+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/test/lint.min.js | 20++++++++++++++++++++
modules/editor/codemirror/test/mode_test.css | 23+++++++++++++++++++++++
modules/editor/codemirror/test/mode_test.min.js | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/test/multi_test.min.js | 285+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/test/phantom_driver.min.js | 31+++++++++++++++++++++++++++++++
modules/editor/codemirror/test/scroll_test.js | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/test/search_test.min.js | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/test/sublime_test.min.js | 307+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/test/vim_test.min.js | 4238+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/ambiance.css | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/dracula.css | 40++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/duotone-dark.css | 35+++++++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/icecoder.css | 43+++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/panda-syntax.css | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/pastel-on-dark.css | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/the-matrix.css | 30++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/tomorrow-night-eighties.css | 38++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/twilight.css | 32++++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/vibrant-ink.css | 34++++++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/xq-light.css | 43+++++++++++++++++++++++++++++++++++++++++++
modules/editor/codemirror/theme/zenburn.css | 37+++++++++++++++++++++++++++++++++++++
152 files changed, 31743 insertions(+), 16 deletions(-)

diff --git a/modules/cms-core/action/ElementAction.class.php b/modules/cms-core/action/ElementAction.class.php @@ -6,7 +6,7 @@ namespace cms\action; use cms\model\Element; use cms\model\Template; use cms\model\Folder; -use cms\model\Object; +use cms\model\BaseObject; use Text; @@ -531,7 +531,7 @@ class ElementAction extends Action // Ermitteln aller verfuegbaren Objekt-IDs foreach( Folder::getAllObjectIds() as $id ) { - $o = new Object( $id ); + $o = new BaseObject( $id ); $o->load(); switch( $this->element->type ) @@ -586,7 +586,7 @@ class ElementAction extends Action // Ermitteln aller verf?gbaren Objekt-IDs foreach( Folder::getAllFolders() as $id ) { - $o = new Object( $id ); + $o = new BaseObject( $id ); $o->load(); $folders[ $id ] = ''; diff --git a/modules/cms-core/action/PageelementAction.class.php b/modules/cms-core/action/PageelementAction.class.php @@ -9,7 +9,7 @@ use cms\model\Element; use cms\model\Template; use cms\model\Page; use cms\model\Folder; -use cms\model\Object; +use cms\model\BaseObject; use Html; use Http; use Session; @@ -49,13 +49,13 @@ class PageelementAction extends Action /** * Enthaelt das Seitenobjekt - * @type Object + * @type BaseObject */ var $page; /** * Enthaelt das Elementobjekt - * @type Object + * @type BaseObject */ var $element; @@ -63,7 +63,7 @@ class PageelementAction extends Action /** * Enth�lt den Inhalt * - * @var Object + * @var BaseObject */ var $value; @@ -498,7 +498,7 @@ class PageelementAction extends Action foreach( Folder::getAllObjectIds($types) as $id ) { - $o = new Object( $id ); + $o = new BaseObject( $id ); $o->load(); // if ( in_array( $o->getType(),$types )) @@ -557,7 +557,7 @@ class PageelementAction extends Action foreach( $t->getDependentObjectIds() as $id ) { - $o = new Object( $id ); + $o = new BaseObject( $id ); $o->load(); // if ( in_array( $o->getType(),$types )) diff --git a/modules/cms-core/action/WebdavAction.class.php b/modules/cms-core/action/WebdavAction.class.php @@ -6,7 +6,7 @@ use cms\model\User; use cms\model\Project; use cms\model\Page; use cms\model\Folder; -use cms\model\Object; +use cms\model\BaseObject; use cms\model\File; use cms\model\Link; @@ -1182,7 +1182,7 @@ class WebdavAction extends Action else { Logger::trace( 'Teil '.$uriPart); - $o = new Object($oid); + $o = new BaseObject($oid); $o->load(); $ergebnis['object'] = $o; diff --git a/modules/cms-core/model/require.php b/modules/cms-core/model/require.php @@ -6,7 +6,7 @@ require_once( __DIR__.'/ModelBase.class.php' ); require_once( __DIR__.'/Value.class.php' ); require_once( __DIR__.'/Acl.class.php' ); require_once( __DIR__.'/Template.class.php' ); -require_once( __DIR__.'/Object.class.php' ); +require_once(__DIR__ . '/BaseObject.class.php'); require_once( __DIR__.'/Folder.class.php' ); require_once( __DIR__.'/Link.class.php' ); require_once( __DIR__.'/Url.class.php' ); diff --git a/modules/cms-ui/themes/default/html/views/element/advanced.php b/modules/cms-ui/themes/default/html/views/element/advanced.php @@ -0,0 +1,19 @@ + + + + + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="POST" enctype="application/x-www-form-urlencoded" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><div> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('ELEMENT_TYPE')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_type" name="type" title="" class=""<?php if (count($types)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($types,$type,0,1) ?><?php if (count($types)==0) { ?><input type="hidden" name="type" value="" /><?php } ?><?php if (count($types)==1) { ?><input type="hidden" name="type" value="<?php echo array_keys($types)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + </div></fieldset> + <div class="bottom"><div class="command "><input type="button" class="submit ok" value="OK" /></div></div></form> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/element/prop.php b/modules/cms-ui/themes/default/html/views/element/prop.php @@ -0,0 +1,325 @@ + + + + + <?php $if2=(config('security','disable_dynamic_code')); if($if2){?> + <?php $if3=(!'1'); if($if3){?> + <div class="message warn"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'NOTICE_CODE_DISABLED'.'')))); ?></span> + + </div> + <?php } ?> + <?php } ?> + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="POST" enctype="application/x-www-form-urlencoded" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><div> + <?php $if4=(!empty($subtype)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('ELEMENT_SUBTYPE')))); ?></span> + + </div> + <div class="input"> + <?php $if7=(!empty($subtypes)); if($if7){?> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_subtype" name="subtype" title="" class=""<?php if (count($subtypes)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($subtypes,$subtype,1,0) ?><?php if (count($subtypes)==0) { ?><input type="hidden" name="subtype" value="" /><?php } ?><?php if (count($subtypes)==1) { ?><input type="hidden" name="subtype" value="<?php echo array_keys($subtypes)[0] ?>" /><?php } ?> + </select></div> + <?php } ?> + <?php $if7=!(!empty($subtypes)); if($if7){?> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_subtype" name="subtype<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="text" value="<?php echo Text::encodeHtml(@$subtype) ?>" /><?php if ('') { ?><input type="hidden" name="subtype" value="<?php $subtype ?>"/><?php } ?></div> + + <?php } ?> + </div> + </div> + <?php } ?> + <?php $if4=(!empty($with_icon)); if($if4){?> + <div class="line"> + <div class="label"> + </div> + <div class="input"> + <?php { $tmpname = 'with_icon';$default = '';$readonly = ''; + if ( isset($$tmpname) ) + $checked = $$tmpname; + else + $checked = $default; + + ?><input class="checkbox" type="checkbox" id="<?php echo REQUEST_ID ?>_<?php echo $tmpname ?>" name="<?php echo $tmpname ?>" <?php if ($readonly) echo ' disabled="disabled"' ?> value="1" <?php if( $checked ) echo 'checked="checked"' ?> /><?php + + if ( $readonly && $checked ) + { + ?><input type="hidden" name="<?php echo $tmpname ?>" value="1" /><?php + } + } ?> + + <label for="<?php echo REQUEST_ID ?>_with_icon" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_WITH_ICON')))); ?></span> + + </label> + </div> + </div> + <?php } ?> + <?php $if4=(!empty($all_languages)); if($if4){?> + <div class="line"> + <div class="label"> + </div> + <div class="input"> + <?php { $tmpname = 'all_languages';$default = '';$readonly = ''; + if ( isset($$tmpname) ) + $checked = $$tmpname; + else + $checked = $default; + + ?><input class="checkbox" type="checkbox" id="<?php echo REQUEST_ID ?>_<?php echo $tmpname ?>" name="<?php echo $tmpname ?>" <?php if ($readonly) echo ' disabled="disabled"' ?> value="1" <?php if( $checked ) echo 'checked="checked"' ?> /><?php + + if ( $readonly && $checked ) + { + ?><input type="hidden" name="<?php echo $tmpname ?>" value="1" /><?php + } + } ?> + + <label for="<?php echo REQUEST_ID ?>_all_languages" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_ALL_LANGUAGES')))); ?></span> + + </label> + </div> + </div> + <?php } ?> + <?php $if4=(!empty($writable)); if($if4){?> + <div class="line"> + <div class="label"> + </div> + <div class="input"> + <?php { $tmpname = 'writable';$default = '';$readonly = ''; + if ( isset($$tmpname) ) + $checked = $$tmpname; + else + $checked = $default; + + ?><input class="checkbox" type="checkbox" id="<?php echo REQUEST_ID ?>_<?php echo $tmpname ?>" name="<?php echo $tmpname ?>" <?php if ($readonly) echo ' disabled="disabled"' ?> value="1" <?php if( $checked ) echo 'checked="checked"' ?> /><?php + + if ( $readonly && $checked ) + { + ?><input type="hidden" name="<?php echo $tmpname ?>" value="1" /><?php + } + } ?> + + <label for="<?php echo REQUEST_ID ?>_writable" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_writable')))); ?></span> + + </label> + </div> + </div> + <?php } ?> + <?php $if4=(!empty($width)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('width')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_width" name="width<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="text" value="<?php echo Text::encodeHtml(@$width) ?>" /><?php if ('') { ?><input type="hidden" name="width" value="<?php $width ?>"/><?php } ?></div> + + </div> + </div> + <?php } ?> + <?php $if4=(!empty($height)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('height')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_height" name="height<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="text" value="<?php echo Text::encodeHtml(@$height) ?>" /><?php if ('') { ?><input type="hidden" name="height" value="<?php $height ?>"/><?php } ?></div> + + </div> + </div> + <?php } ?> + <?php $if4=(!empty($dateformat)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_DATEFORMAT')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_dateformat" name="dateformat" title="" class=""<?php if (count($dateformats)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($dateformats,$dateformat,0,0) ?><?php if (count($dateformats)==0) { ?><input type="hidden" name="dateformat" value="" /><?php } ?><?php if (count($dateformats)==1) { ?><input type="hidden" name="dateformat" value="<?php echo array_keys($dateformats)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + <?php } ?> + <?php $if4=(!empty($format)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_FORMAT')))); ?></span> + + </div> + <div class="input"> + <?php include_once( 'modules/template-engine/components/html/radiobox/component-radio-box.php') ?><?php component_radio_box('format',$formatlist,$format) ?> + + </div> + </div> + <?php } ?> + <?php $if4=(!empty($decimals)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_DECIMALS')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_decimals" name="decimals<?php if ('') echo '_disabled' ?>" type="text" maxlength="2" class="text" value="<?php echo Text::encodeHtml(@$decimals) ?>" /><?php if ('') { ?><input type="hidden" name="decimals" value="<?php $decimals ?>"/><?php } ?></div> + + </div> + </div> + <?php } ?> + <?php $if4=(!empty($dec_point)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_DEC_POINT')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_dec_point" name="dec_point<?php if ('') echo '_disabled' ?>" type="text" maxlength="5" class="text" value="<?php echo Text::encodeHtml(@$dec_point) ?>" /><?php if ('') { ?><input type="hidden" name="dec_point" value="<?php $dec_point ?>"/><?php } ?></div> + + </div> + </div> + <?php } ?> + <?php $if4=(!empty($thousand_sep)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_thousand_sep')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_thousand_sep" name="thousand_sep<?php if ('') echo '_disabled' ?>" type="text" maxlength="1" class="text" value="<?php echo Text::encodeHtml(@$thousand_sep) ?>" /><?php if ('') { ?><input type="hidden" name="thousand_sep" value="<?php $thousand_sep ?>"/><?php } ?></div> + + </div> + </div> + <?php } ?> + <?php $if4=(!empty($default_text)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_default_text')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_default_text" name="default_text<?php if ('') echo '_disabled' ?>" type="text" maxlength="255" class="text" value="<?php echo Text::encodeHtml(@$default_text) ?>" /><?php if ('') { ?><input type="hidden" name="default_text" value="<?php $default_text ?>"/><?php } ?></div> + + </div> + </div> + <?php } ?> + <?php $if4=(!empty($default_longtext)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_default_longtext')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><textarea class="inputarea" name="default_longtext"><?php echo Text::encodeHtml($default_longtext) ?></textarea></div> + + </div> + </div> + <?php } ?> + <?php $if4=(!empty($parameters)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_DYNAMIC_PARAMETERS')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><textarea class="inputarea" name="parameters"><?php echo Text::encodeHtml($parameters) ?></textarea></div> + + </div> + </div> + <div class="line"> + <div class="label"> + </div> + <div class="input"> + <?php foreach($dynamic_class_parameters as $paramName=>$defaultValue){ ?> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($paramName))); ?></span> + + <span class="text"><?php echo nl2br('&nbsp;('); ?></span> + + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('GLOBAL_DEFAULT')))); ?></span> + + <span class="text"><?php echo nl2br(')&nbsp;=&nbsp;'); ?></span> + + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($defaultValue))); ?></span> + + <br/> + + <?php } ?> + </div> + </div> + <?php } ?> + <?php $if4=(!empty($select_items)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_select_items')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><textarea class="inputarea" name="select_items"><?php echo Text::encodeHtml($select_items) ?></textarea></div> + + </div> + </div> + <?php } ?> + <?php $if4=(!empty($linkelement)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_LINK')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_linkelement" name="linkelement" title="" class=""<?php if (count($linkelements)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($linkelements,$linkelement,0,0) ?><?php if (count($linkelements)==0) { ?><input type="hidden" name="linkelement" value="" /><?php } ?><?php if (count($linkelements)==1) { ?><input type="hidden" name="linkelement" value="<?php echo array_keys($linkelements)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + <?php } ?> + <?php $if4=(!empty($name)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('ELEMENT_NAME')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_name" name="name" title="" class=""<?php if (count($names)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($names,$name,0,0) ?><?php if (count($names)==0) { ?><input type="hidden" name="name" value="" /><?php } ?><?php if (count($names)==1) { ?><input type="hidden" name="name" value="<?php echo array_keys($names)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + <?php } ?> + <?php $if4=(!empty($folderobjectid)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_DEFAULT_FOLDEROBJECT')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_folderobjectid" name="folderobjectid" title="" class=""<?php if (count($folders)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($folders,$folderobjectid,0,0) ?><?php if (count($folders)==0) { ?><input type="hidden" name="folderobjectid" value="" /><?php } ?><?php if (count($folders)==1) { ?><input type="hidden" name="folderobjectid" value="<?php echo array_keys($folders)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + <?php } ?> + <?php $if4=(!empty($default_objectid)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_DEFAULT_OBJECT')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_default_objectid" name="default_objectid" title="" class=""<?php if (count($objects)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($objects,$default_objectid,1,0) ?><?php if (count($objects)==0) { ?><input type="hidden" name="default_objectid" value="" /><?php } ?><?php if (count($objects)==1) { ?><input type="hidden" name="default_objectid" value="<?php echo array_keys($objects)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + <?php } ?> + <?php $if4=(!empty($code)); if($if4){?> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('EL_PROP_code')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><textarea class="inputarea" name="code"><?php echo Text::encodeHtml($code) ?></textarea></div> + + </div> + </div> + <?php } ?> + </div></fieldset> + <div class="bottom"><div class="command "><input type="button" class="submit ok" value="OK" /></div></div></form> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/folder/create.php b/modules/cms-ui/themes/default/html/views/folder/create.php @@ -0,0 +1,107 @@ + + + <div class="headermenu"><div class="toolbar-icon clickable"><a href="javascript:void(0);" title="<?php echo lang('MENU_CREATEFOLDER') ?>" data-type="dialog" data-name="<?php echo lang('MENU_CREATEFOLDER') ?>" data-method="createfolder"><img src="./themes/default/images/icon/action/createfolder.svg" title="<?php echo lang('MENU_createfolder_DESC') ?>" /><?php echo lang('MENU_createfolder') ?></a></div><div class="toolbar-icon clickable"><a href="javascript:void(0);" title="<?php echo lang('MENU_CREATELINK') ?>" data-type="dialog" data-name="<?php echo lang('MENU_CREATELINK') ?>" data-method="createlink"><img src="./themes/default/images/icon/action/createlink.svg" title="<?php echo lang('MENU_createlink_DESC') ?>" /><?php echo lang('MENU_createlink') ?></a></div><div class="toolbar-icon clickable"><a href="javascript:void(0);" title="<?php echo lang('MENU_CREATEPAGE') ?>" data-type="dialog" data-name="<?php echo lang('MENU_CREATEPAGE') ?>" data-method="createpage"><img src="./themes/default/images/icon/action/createpage.svg" title="<?php echo lang('MENU_createpage_DESC') ?>" /><?php echo lang('MENU_createpage') ?></a></div><div class="toolbar-icon clickable"><a href="javascript:void(0);" title="<?php echo lang('MENU_CREATEFILE') ?>" data-type="dialog" data-name="<?php echo lang('MENU_CREATEFILE') ?>" data-method="createfile"><img src="./themes/default/images/icon/action/createfile.svg" title="<?php echo lang('MENU_createfile_DESC') ?>" /><?php echo lang('MENU_createfile') ?></a></div><div class="toolbar-icon clickable"><a href="javascript:void(0);" title="<?php echo lang('MENU_CREATEURL') ?>" data-type="dialog" data-name="<?php echo lang('MENU_CREATEURL') ?>" data-method="createurl"><img src="./themes/default/images/icon/action/createurl.svg" title="<?php echo lang('MENU_createurl_DESC') ?>" /><?php echo lang('MENU_createurl') ?></a></div></div> + + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="POST" enctype="multipart/form-data" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('folder') ?></legend><div> + <div class="line"> + <div class="label"> + <input class="radio" type="radio" id="<?php echo REQUEST_ID ?>_type_folder" name="type" value="folder"<?php if('folder'==@$type)echo ' checked="checked"' ?> /> + + <label for="<?php echo REQUEST_ID ?>_type_folder" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('global_folder')))); ?></span> + + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_folder_name" name="folder_name<?php if ('') echo '_disabled' ?>" type="text" maxlength="250" class="name" value="<?php echo Text::encodeHtml('') ?>" /><?php if ('') { ?><input type="hidden" name="folder_name" value="<?php '' ?>"/><?php } ?></div> + + </div> + </div> + </div></fieldset> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('file') ?></legend><div> + <div class="line"> + <div class="label"> + <input class="radio" type="radio" id="<?php echo REQUEST_ID ?>_type_file" name="type" value="file"<?php if('file'==@$type)echo ' checked="checked"' ?> /> + + <label for="<?php echo REQUEST_ID ?>_type_file" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('global_FILE')))); ?></span> + + </label> + </div> + <div class="input"> + <input size="30" id="req15173468271183810521_file" type="file" maxlength="<?php echo $maxlength ?>" name="file" class="upload" /> + + <br/> + + <span class="help"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'file_max_size'.'')))); ?></span> + + <span class="text"><?php echo nl2br('&nbsp;'); ?></span> + + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($max_size))); ?></span> + + </div> + </div> + </div></fieldset> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('page') ?></legend><div> + <div class="line"> + <div class="label"> + <input class="radio" type="radio" id="<?php echo REQUEST_ID ?>_type_page" name="type" value="page"<?php if('page'==@$type)echo ' checked="checked"' ?> /> + + <label for="<?php echo REQUEST_ID ?>_type_page" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('global_TEMPLATE')))); ?></span> + + </label> + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_page_templateid" name="page_templateid" title="" class=""<?php if (count($templates)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($templates,'',0,0) ?><?php if (count($templates)==0) { ?><input type="hidden" name="page_templateid" value="" /><?php } ?><?php if (count($templates)==1) { ?><input type="hidden" name="page_templateid" value="<?php echo array_keys($templates)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_type_page" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('global_NAME')))); ?></span> + + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_page_name" name="page_name<?php if ('') echo '_disabled' ?>" type="text" maxlength="250" class="name" value="<?php echo Text::encodeHtml(@$page_name) ?>" /><?php if ('') { ?><input type="hidden" name="page_name" value="<?php $page_name ?>"/><?php } ?></div> + + </div> + </div> + </div></fieldset> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('link') ?></legend><div> + <div class="line"> + <div class="label"> + <input class="radio" type="radio" id="<?php echo REQUEST_ID ?>_type_link" name="type" value="link"<?php if('link'==@$type)echo ' checked="checked"' ?> /> + + <label for="<?php echo REQUEST_ID ?>_type_link" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('global_NAME')))); ?></span> + + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_link_name" name="link_name<?php if ('') echo '_disabled' ?>" type="text" maxlength="250" class="name" value="<?php echo Text::encodeHtml(@$link_name) ?>" /><?php if ('') { ?><input type="hidden" name="link_name" value="<?php $link_name ?>"/><?php } ?></div> + + </div> + </div> + </div></fieldset> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('url') ?></legend><div> + <div class="line"> + <div class="label"> + <input class="radio" type="radio" id="<?php echo REQUEST_ID ?>_type_url" name="type" value="url"<?php if('url'==@$type)echo ' checked="checked"' ?> /> + + <label for="<?php echo REQUEST_ID ?>_type_link" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('url')))); ?></span> + + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_url" name="url<?php if ('') echo '_disabled' ?>" type="text" maxlength="250" class="name" value="<?php echo Text::encodeHtml(@$url) ?>" /><?php if ('') { ?><input type="hidden" name="url" value="<?php $url ?>"/><?php } ?></div> + + </div> + </div> + </div></fieldset> + <div class="bottom"><div class="command "><input type="button" class="submit ok" value="OK" /></div></div></form> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/folder/createfolder.php b/modules/cms-ui/themes/default/html/views/folder/createfolder.php @@ -0,0 +1,27 @@ + + + + + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="POST" enctype="application/x-www-form-urlencoded" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('global_FOLDER')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_name" name="name<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="text" value="<?php echo Text::encodeHtml('') ?>" /><?php if ('') { ?><input type="hidden" name="name" value="<?php '' ?>"/><?php } ?></div> + + </div> + </div> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('global_DESCRIPTION')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><textarea class="inputarea" name="description"><?php echo Text::encodeHtml('') ?></textarea></div> + + </div> + </div> + <div class="bottom"><div class="command 1"><input type="button" class="submit ok" value="<?php echo lang('add') ?>" /></div></div></form> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/folder/info.php b/modules/cms-ui/themes/default/html/views/folder/info.php @@ -0,0 +1,97 @@ + + + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="POST" enctype="application/x-www-form-urlencoded" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('GLOBAL_PROP') ?></legend><div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_name" class="label"><?php echo lang('global_name') ?> + </label> + </div> + <div class="input"> + <span class="name,focus"><?php echo nl2br(encodeHtml(htmlentities($name))); ?></span> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_filename" class="label"><?php echo lang('global_filename') ?> + </label> + </div> + <div class="input"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($filename))); ?></span> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_description" class="label"><?php echo lang('global_description') ?> + </label> + </div> + <div class="input"> + <span class="description"><?php echo nl2br(encodeHtml(htmlentities($description))); ?></span> + + </div> + </div> + </div></fieldset> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('additional_info') ?></legend><div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_full_filename" class="label"><?php echo lang('FULL_FILENAME') ?> + </label> + </div> + <div class="input"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($full_filename))); ?></span> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_objectid" class="label"><?php echo lang('id') ?> + </label> + </div> + <div class="input"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($objectid))); ?></span> + + </div> + </div> + </div></fieldset> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('PROP_USERINFO') ?></legend><div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_create_user" class="label"><?php echo lang('global_created') ?> + </label> + </div> + <div class="input"> + <img class="image-icon image-icon--element" title="" src="./modules/cms-ui/themes/default/images/icon/element/date.svg" /> + + <?php include_once( 'modules/template-engine/components/html/date/component-date.php') ?><?php component_date($create_date) ?> + + <br/> + + <img class="image-icon image-icon--action" title="" src="./modules/cms-ui/themes/default/images/icon/action/user.svg" /> + + <?php include_once( 'modules/template-engine/components/html/user/component-user.php') ?><?php component_user($create_user) ?> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_lastchange_user" class="label"><?php echo lang('global_lastchange') ?> + </label> + </div> + <div class="input"> + <img class="image-icon image-icon--element" title="" src="./modules/cms-ui/themes/default/images/icon/element/date.svg" /> + + <?php include_once( 'modules/template-engine/components/html/date/component-date.php') ?><?php component_date($lastchange_date) ?> + + <br/> + + <img class="image-icon image-icon--action" title="" src="./modules/cms-ui/themes/default/images/icon/action/user.svg" /> + + <?php include_once( 'modules/template-engine/components/html/user/component-user.php') ?><?php component_user($lastchange_user) ?> + + </div> + </div> + </div></fieldset> + <div class="bottom"><div class="command "><input type="button" class="submit ok" value="OK" /></div></div></form> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/folder/order.php b/modules/cms-ui/themes/default/html/views/folder/order.php @@ -0,0 +1,57 @@ + + + + + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_token" name="token<?php if ('') echo '_disabled' ?>" type="hidden" maxlength="256" class="text" value="<?php echo Text::encodeHtml($token) ?>" /><?php if ('') { ?><input type="hidden" name="token" value="<?php $token ?>"/><?php } ?></div> + + <table class="sortable" width="100%"> + <tr class="headline"> + <td class="help clickable"> + <a title="<?php echo lang('FOLDER_FLIP') ?>" target="_self" data-type="post" data-action="folder" data-method="reorder" data-id="<?php echo OR_ID ?>" data-data="{&quot;action&quot;:&quot;folder&quot;,&quot;subaction&quot;:&quot;reorder&quot;,&quot;id&quot;:&quot;<?php echo OR_ID ?>&quot;,&quot;token&quot;:&quot;<?php echo token() ?>&quot;,&quot;var1&quot;:&quot;flip&quot;,&quot;none&quot;:&quot;0&quot;}"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'FOLDER_ORDER'.'')))); ?></span> + + </a> + + </td> + <td class="help clickable"> + <a title="<?php echo lang('FOLDER_ORDERBYTYPE') ?>" target="_self" data-type="post" data-action="folder" data-method="reorder" data-id="<?php echo OR_ID ?>" data-data="{&quot;action&quot;:&quot;folder&quot;,&quot;subaction&quot;:&quot;reorder&quot;,&quot;id&quot;:&quot;<?php echo OR_ID ?>&quot;,&quot;token&quot;:&quot;<?php echo token() ?>&quot;,&quot;var1&quot;:&quot;type&quot;,&quot;none&quot;:&quot;0&quot;}"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'GLOBAL_TYPE'.'')))); ?></span> + + </a> + + </td> + <td class="help clickable"> + <a title="<?php echo lang('FOLDER_ORDERBYNAME') ?>" target="_self" data-type="post" data-action="folder" data-method="reorder" data-id="<?php echo OR_ID ?>" data-data="{&quot;action&quot;:&quot;folder&quot;,&quot;subaction&quot;:&quot;reorder&quot;,&quot;id&quot;:&quot;<?php echo OR_ID ?>&quot;,&quot;token&quot;:&quot;<?php echo token() ?>&quot;,&quot;var1&quot;:&quot;name&quot;,&quot;none&quot;:&quot;0&quot;}"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'GLOBAL_NAME'.'')))); ?></span> + + </a> + + </td> + <td class="help clickable"> + <a title="<?php echo lang('FOLDER_ORDERBYLASTCHANGE') ?>" target="_self" data-type="post" data-action="folder" data-method="reorder" data-id="<?php echo OR_ID ?>" data-data="{&quot;action&quot;:&quot;folder&quot;,&quot;subaction&quot;:&quot;reorder&quot;,&quot;id&quot;:&quot;<?php echo OR_ID ?>&quot;,&quot;token&quot;:&quot;<?php echo token() ?>&quot;,&quot;var1&quot;:&quot;lastchange&quot;,&quot;none&quot;:&quot;0&quot;}"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'GLOBAL_LASTCHANGE'.'')))); ?></span> + + </a> + + </td> + </tr> + <?php foreach($object as $list_key=>$list_value){ ?><?php extract($list_value) ?> + <tr class="data" data-id="<?php echo $id ?>"> + <td> + <span class="text"><?php echo nl2br('&nbsp;'); ?></span> + + </td> + <td colspan="2"> + <img class="" title="" src="./modules/cms-ui/themes/default/images/icon_<?php echo $icon ?>.png" /> + + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($name))); ?></span> + + </td> + <td> + <?php include_once( 'modules/template-engine/components/html/date/component-date.php') ?><?php component_date($date) ?> + + </td> + </tr> + <?php } ?> + </table> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/folder/prop.php b/modules/cms-ui/themes/default/html/views/folder/prop.php @@ -0,0 +1,39 @@ + + + + + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="POST" enctype="application/x-www-form-urlencoded" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <input type="hidden" name="languageid" value="<?php echo $languageid ?>"/> + + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_name" class="label"><?php echo lang('global_name') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_name" name="name<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="name,focus" value="<?php echo Text::encodeHtml(@$name) ?>" /><?php if ('') { ?><input type="hidden" name="name" value="<?php $name ?>"/><?php } ?></div> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_filename" class="label"><?php echo lang('global_filename') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_filename" name="filename<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="filename" value="<?php echo Text::encodeHtml(@$filename) ?>" /><?php if ('') { ?><input type="hidden" name="filename" value="<?php $filename ?>"/><?php } ?></div> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_description" class="label"><?php echo lang('global_description') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><textarea class="description" name="description"><?php echo Text::encodeHtml($description) ?></textarea></div> + + </div> + </div> + <div class="bottom"><div class="command "><input type="button" class="submit ok" value="<?php echo lang('global_save') ?>" /></div></div></form> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/folder/structure.php b/modules/cms-ui/themes/default/html/views/folder/structure.php @@ -0,0 +1,7 @@ + + + <div class="structure tree"> + <?php include_once( 'modules/template-engine/components/html/tree/component-tree.php') ?><?php component_tree($outline) ?> + + </div> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/group/edit.php b/modules/cms-ui/themes/default/html/views/group/edit.php @@ -0,0 +1,19 @@ + + + <div class="headermenu"><div class="toolbar-icon clickable"><a href="javascript:void(0);" title="<?php echo lang('MENU_REMOVE') ?>" data-type="dialog" data-name="<?php echo lang('MENU_REMOVE') ?>" data-method="remove"><img src="./themes/default/images/icon/action/remove.svg" title="<?php echo lang('MENU_remove_DESC') ?>" /><?php echo lang('MENU_remove') ?></a></div></div> + + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="post" enctype="application/x-www-form-urlencoded" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_name" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('GLOBAL_NAME')))); ?></span> + + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_name" name="name<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="focus" value="<?php echo Text::encodeHtml(@$name) ?>" /><?php if ('') { ?><input type="hidden" name="name" value="<?php $name ?>"/><?php } ?></div> + + </div> + </div> + <div class="bottom"><div class="command "><input type="button" class="submit ok" value="OK" /></div></div></form> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/image/edit.php b/modules/cms-ui/themes/default/html/views/image/edit.php @@ -0,0 +1,21 @@ + + + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="POST" enctype="application/x-www-form-urlencoded" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <div class="headermenu"><div class="toolbar-icon clickable"><a href="javascript:void(0);" title="<?php echo lang('MENU_VALUE') ?>" data-type="dialog" data-name="<?php echo lang('MENU_VALUE') ?>" data-method="value"><img src="./themes/default/images/icon/action/value.svg" title="<?php echo lang('MENU_value_DESC') ?>" /><?php echo lang('MENU_value') ?></a></div></div> + + <div class="label"> + </div> + <div class="line"> + <div class="input"> + <br/> + + <input size="40" id="req15182104901341960142_file" type="file" name="file" class="upload" /> + + <br/> + + <br/> + + </div> + </div> + <div class="bottom"><div class="command "><input type="button" class="submit ok" value="OK" /></div></div></form> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/page/edit.php b/modules/cms-ui/themes/default/html/views/page/edit.php @@ -1,7 +1,7 @@ <table width="100%"> - <?php $if3=(empty($el)); if($if3){?> + <?php $if3=!(empty($el)); if($if3){?> <tr class="headline"> <td class="help"> <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('PAGE_ELEMENT_NAME')))); ?></span> diff --git a/modules/cms-ui/themes/default/html/views/page/src.tpl.src.xml b/modules/cms-ui/themes/default/html/views/page/src.tpl.src.xml @@ -1,3 +1,11 @@ <output xmlns="http://www.openrat.de/template" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.openrat.de/template ../template.xsd"><dummy><window icon="page"><row><column><text var="src" escape="false" type="preformatted"></text></column></row></window></dummy></output>- \ No newline at end of file + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.openrat.de/template ../template.xsd"> + <hidden name="templateid" /> + <hidden name="modelid" /> + <row> + <column> + <text var="src" escape="false" type="preformatted"></text> + </column> + </row> +</output>+ \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/profile/edit.php b/modules/cms-ui/themes/default/html/views/profile/edit.php @@ -0,0 +1,168 @@ + + + <div class="headermenu"><div class="toolbar-icon clickable"><a href="javascript:void(0);" title="<?php echo lang('MENU_MAIL') ?>" data-type="dialog" data-name="<?php echo lang('MENU_MAIL') ?>" data-method="mail"><img src="./themes/default/images/icon/action/mail.svg" title="<?php echo lang('MENU_mail_DESC') ?>" /><?php echo lang('MENU_mail') ?></a></div></div> + + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="POST" enctype="application/x-www-form-urlencoded" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('name') ?></legend><div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_name" class="label"><?php echo lang('user_username') ?> + </label> + </div> + <div class="input"> + <span class="name"><?php echo nl2br(encodeHtml(htmlentities($name))); ?></span> + + </div> + </div> + </div></fieldset> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('MENU_PROFILE_MAIL') ?></legend><div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_mail" class="label"><?php echo lang('user_mail') ?> + </label> + </div> + <div class="input"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($mail))); ?></span> + + <br/> + + <br/> + + <div class="clickable"> + <a class="action" target="_self" date-name="<?php echo lang('mail') ?>" name="<?php echo lang('mail') ?>" data-type="dialog" data-action="profile" data-method="mail" data-id="<?php echo OR_ID ?>" href="javascript:void(0);"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'edit'.'')))); ?></span> + + </a> + + </div> + </div> + </div> + </div></fieldset> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('GLOBAL_PROP') ?></legend><div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_fullname" class="label"><?php echo lang('user_fullname') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_fullname" name="fullname<?php if ('') echo '_disabled' ?>" type="text" maxlength="128" class="text" value="<?php echo Text::encodeHtml(@$fullname) ?>" /><?php if ('') { ?><input type="hidden" name="fullname" value="<?php $fullname ?>"/><?php } ?></div> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_tel" class="label"><?php echo lang('user_tel') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_tel" name="tel<?php if ('') echo '_disabled' ?>" type="text" maxlength="128" class="text" value="<?php echo Text::encodeHtml(@$tel) ?>" /><?php if ('') { ?><input type="hidden" name="tel" value="<?php $tel ?>"/><?php } ?></div> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_desc" class="label"><?php echo lang('user_desc') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_desc" name="desc<?php if ('') echo '_disabled' ?>" type="text" maxlength="128" class="text" value="<?php echo Text::encodeHtml(@$desc) ?>" /><?php if ('') { ?><input type="hidden" name="desc" value="<?php $desc ?>"/><?php } ?></div> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_style" class="label"><?php echo lang('user_style') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_style" name="style" title="" class=""<?php if (count($allstyles)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($allstyles,config('interface','style','default'),0,0) ?><?php if (count($allstyles)==0) { ?><input type="hidden" name="style" value="" /><?php } ?><?php if (count($allstyles)==1) { ?><input type="hidden" name="style" value="<?php echo array_keys($allstyles)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_timezone_offset" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'timezone'.'')))); ?></span> + + </label> + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_timezone" name="timezone" title="" class=""<?php if (count($timezone_list)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($timezone_list,$timezone,1,0) ?><?php if (count($timezone_list)==0) { ?><input type="hidden" name="timezone" value="" /><?php } ?><?php if (count($timezone_list)==1) { ?><input type="hidden" name="timezone" value="<?php echo array_keys($timezone_list)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + <div class="line"> + <div class="label"> + <label class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'language'.'')))); ?></span> + + </label> + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_language" name="language" title="" class=""<?php if (count($language_list)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($language_list,$language,1,0) ?><?php if (count($language_list)==0) { ?><input type="hidden" name="language" value="" /><?php } ?><?php if (count($language_list)==1) { ?><input type="hidden" name="language" value="<?php echo array_keys($language_list)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + </div></fieldset> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('security') ?></legend><div> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('user_password_expires')))); ?></span> + + </div> + <div class="input"> + <?php include_once( 'modules/template-engine/components/html/date/component-date.php') ?><?php component_date($passwordExpires) ?> + + </div> + </div> + <div class="line"> + <div class="label"> + </div> + <div class="input"> + <?php { $tmpname = 'totp';$default = '';$readonly = ''; + if ( isset($$tmpname) ) + $checked = $$tmpname; + else + $checked = $default; + + ?><input class="checkbox" type="checkbox" id="<?php echo REQUEST_ID ?>_<?php echo $tmpname ?>" name="<?php echo $tmpname ?>" <?php if ($readonly) echo ' disabled="disabled"' ?> value="1" <?php if( $checked ) echo 'checked="checked"' ?> /><?php + + if ( $readonly && $checked ) + { + ?><input type="hidden" name="<?php echo $tmpname ?>" value="1" /><?php + } + } ?> + + <label for="<?php echo REQUEST_ID ?>_totp" class="label"><?php echo lang('user_totp') ?> + </label> + <div class="qrcode" data-qrcode="<?php echo $totpSecretUrl ?>" title="<?php echo $totpSecretUrl ?>"></div> + + </div> + </div> + <div class="line"> + <div class="label"> + </div> + <div class="input"> + <?php { $tmpname = 'hotp';$default = '';$readonly = ''; + if ( isset($$tmpname) ) + $checked = $$tmpname; + else + $checked = $default; + + ?><input class="checkbox" type="checkbox" id="<?php echo REQUEST_ID ?>_<?php echo $tmpname ?>" name="<?php echo $tmpname ?>" <?php if ($readonly) echo ' disabled="disabled"' ?> value="1" <?php if( $checked ) echo 'checked="checked"' ?> /><?php + + if ( $readonly && $checked ) + { + ?><input type="hidden" name="<?php echo $tmpname ?>" value="1" /><?php + } + } ?> + + <label for="<?php echo REQUEST_ID ?>_hotp" class="label"><?php echo lang('user_hotp') ?> + </label> + <div class="qrcode" data-qrcode="<?php echo $hotpSecretUrl ?>" title="<?php echo $hotpSecretUrl ?>"></div> + + </div> + </div> + </div></fieldset> + <div class="bottom"><div class="command "><input type="button" class="submit ok" value="<?php echo lang('global_save') ?>" /></div></div></form> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/project/export.php b/modules/cms-ui/themes/default/html/views/project/export.php @@ -0,0 +1,15 @@ + + + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="POST" enctype="application/x-www-form-urlencoded" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'GLOBAL_DATABASE'.'')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_dbid" name="dbid" title="" class=""<?php if (count($dbids)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($dbids,'actdbid',0,0) ?><?php if (count($dbids)==0) { ?><input type="hidden" name="dbid" value="" /><?php } ?><?php if (count($dbids)==1) { ?><input type="hidden" name="dbid" value="<?php echo array_keys($dbids)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + <div class="bottom"><div class="command "><input type="button" class="submit ok" value="OK" /></div></div></form> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/template/prop.php b/modules/cms-ui/themes/default/html/views/template/prop.php @@ -0,0 +1,56 @@ + + + <div class="headermenu"><div class="toolbar-icon clickable"><a href="javascript:void(0);" title="<?php echo lang('MENU_EXTENSION') ?>" data-type="dialog" data-name="<?php echo lang('MENU_EXTENSION') ?>" data-method="extension"><img src="./themes/default/images/icon/action/extension.svg" title="<?php echo lang('MENU_extension_DESC') ?>" /><?php echo lang('MENU_extension') ?></a></div></div> + + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="POST" enctype="application/x-www-form-urlencoded" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('TEMPLATE_NAME')))); ?></span> + + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_name" name="name<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="text" value="<?php echo Text::encodeHtml(@$name) ?>" /><?php if ('') { ?><input type="hidden" name="name" value="<?php $name ?>"/><?php } ?></div> + + </div> + </div> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><div> + </div></fieldset> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'file_extension'.'')))); ?></span> + + </div> + <div class="input"> + <a target="_self" data-type="view" data-action="" data-method="extension" data-id="<?php echo OR_ID ?>" href="javascript:void(0);"> + <div class="inputholder"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($extension))); ?></span> + + </div> + </a> + + <div class="clickable"> + <a class="action" target="_self" data-type="view" data-action="" data-method="extension" data-id="<?php echo OR_ID ?>" href="javascript:void(0);"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'edit'.'')))); ?></span> + + </a> + + </div> + </div> + </div> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'file_mimetype'.'')))); ?></span> + + </div> + <div class="input"> + <a target="_self" data-action="template" data-method="extension" data-id="<?php echo OR_ID ?>" href="javascript:void(0);"> + <div class="inputholder"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($mime_type))); ?></span> + + </div> + </a> + + </div> + </div> + <div class="bottom"><div class="command "><input type="button" class="submit ok" value="OK" /></div></div></form> + + \ No newline at end of file diff --git a/modules/cms-ui/themes/default/html/views/user/edit.php b/modules/cms-ui/themes/default/html/views/user/edit.php @@ -0,0 +1,219 @@ + + + <div class="headermenu"><div class="toolbar-icon clickable"><a href="javascript:void(0);" title="<?php echo lang('MENU_PW') ?>" data-type="dialog" data-name="<?php echo lang('MENU_PW') ?>" data-method="pw"><img src="./themes/default/images/icon/action/pw.svg" title="<?php echo lang('MENU_pw_DESC') ?>" /><?php echo lang('MENU_pw') ?></a></div><div class="toolbar-icon clickable"><a href="javascript:void(0);" title="<?php echo lang('MENU_REMOVE') ?>" data-type="dialog" data-name="<?php echo lang('MENU_REMOVE') ?>" data-method="remove"><img src="./themes/default/images/icon/action/remove.svg" title="<?php echo lang('MENU_remove_DESC') ?>" /><?php echo lang('MENU_remove') ?></a></div></div> + + <form name="" target="_self" action="<?php echo OR_ACTION ?>" data-method="<?php echo OR_METHOD ?>" data-action="<?php echo OR_ACTION ?>" data-id="<?php echo OR_ID ?>" method="POST" enctype="application/x-www-form-urlencoded" class="<?php echo OR_ACTION ?>" data-async="" data-autosave=""><input type="submit" class="invisible" /><input type="hidden" name="<?php echo REQ_PARAM_TOKEN ?>" value="<?php echo token() ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ACTION ?>" value="<?php echo OR_ACTION ?>" /><input type="hidden" name="<?php echo REQ_PARAM_SUBACTION ?>" value="<?php echo OR_METHOD ?>" /><input type="hidden" name="<?php echo REQ_PARAM_ID ?>" value="<?php echo OR_ID ?>" /> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_name" class="label"><?php echo lang('user_username') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_name" name="name<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="name,focus" value="<?php echo Text::encodeHtml(@$name) ?>" /><?php if ('') { ?><input type="hidden" name="name" value="<?php $name ?>"/><?php } ?></div> + + </div> + </div> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('ADDITIONAL_INFO') ?></legend><div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_fullname" class="label"><?php echo lang('user_fullname') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_fullname" name="fullname<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="text" value="<?php echo Text::encodeHtml(@$fullname) ?>" /><?php if ('') { ?><input type="hidden" name="fullname" value="<?php $fullname ?>"/><?php } ?></div> + + </div> + </div> + <?php $if4=(config('security','user','show_admin_mail')); if($if4){?> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_mail" class="label"><?php echo lang('user_mail') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_mail" name="mail<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="text" value="<?php echo Text::encodeHtml(@$mail) ?>" /><?php if ('') { ?><input type="hidden" name="mail" value="<?php $mail ?>"/><?php } ?></div> + + <div class="qrcode" data-qrcode="<?php echo 'mailto:'.$mail.'' ?>" title="<?php echo 'mailto:'.$mail.'' ?>"></div> + + </div> + </div> + <?php } ?> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_desc" class="label"><?php echo lang('user_desc') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_desc" name="desc<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="text" value="<?php echo Text::encodeHtml(@$desc) ?>" /><?php if ('') { ?><input type="hidden" name="desc" value="<?php $desc ?>"/><?php } ?></div> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_tel" class="label"><?php echo lang('user_tel') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_tel" name="tel<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="text" value="<?php echo Text::encodeHtml(@$tel) ?>" /><?php if ('') { ?><input type="hidden" name="tel" value="<?php $tel ?>"/><?php } ?></div> + + <div class="qrcode" data-qrcode="<?php echo 'tel:'.$tel.'' ?>" title="<?php echo 'tel:'.$tel.'' ?>"></div> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_timezone_offset" class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'timezone'.'')))); ?></span> + + </label> + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_timezone" name="timezone" title="" class=""<?php if (count($timezone_list)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($timezone_list,$timezone,1,0) ?><?php if (count($timezone_list)==0) { ?><input type="hidden" name="timezone" value="" /><?php } ?><?php if (count($timezone_list)==1) { ?><input type="hidden" name="timezone" value="<?php echo array_keys($timezone_list)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + <div class="line"> + <div class="label"> + <label class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang(''.'language'.'')))); ?></span> + + </label> + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_language" name="language" title="" class=""<?php if (count($language_list)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($language_list,$language,1,0) ?><?php if (count($language_list)==0) { ?><input type="hidden" name="language" value="" /><?php } ?><?php if (count($language_list)==1) { ?><input type="hidden" name="language" value="<?php echo array_keys($language_list)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + </div></fieldset> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('options') ?></legend><div> + <div class="line"> + <div class="label"> + </div> + <div class="input"> + <?php { $tmpname = 'is_admin';$default = '';$readonly = ''; + if ( isset($$tmpname) ) + $checked = $$tmpname; + else + $checked = $default; + + ?><input class="checkbox" type="checkbox" id="<?php echo REQUEST_ID ?>_<?php echo $tmpname ?>" name="<?php echo $tmpname ?>" <?php if ($readonly) echo ' disabled="disabled"' ?> value="1" <?php if( $checked ) echo 'checked="checked"' ?> /><?php + + if ( $readonly && $checked ) + { + ?><input type="hidden" name="<?php echo $tmpname ?>" value="1" /><?php + } + } ?> + + <label for="<?php echo REQUEST_ID ?>_is_admin" class="label"><?php echo lang('user_admin') ?> + </label> + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_ldap_dn" class="label"><?php echo lang('user_ldapdn') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><input<?php if ('') echo ' disabled="true"' ?> id="<?php echo REQUEST_ID ?>_ldap_dn" name="ldap_dn<?php if ('') echo '_disabled' ?>" type="text" maxlength="256" class="text" value="<?php echo Text::encodeHtml(@$ldap_dn) ?>" /><?php if ('') { ?><input type="hidden" name="ldap_dn" value="<?php $ldap_dn ?>"/><?php } ?></div> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_style" class="label"><?php echo lang('user_style') ?> + </label> + </div> + <div class="input"> + <div class="inputholder"><select id="<?php echo REQUEST_ID ?>_style" name="style" title="" class=""<?php if (count($allstyles)<=1) echo ' disabled="disabled"'; ?> size=1"><?php include_once( 'modules/template-engine/components/html/selectbox/component-select-box.php') ?><?php component_select_option_list($allstyles,config('interface','style','default'),0,0) ?><?php if (count($allstyles)==0) { ?><input type="hidden" name="style" value="" /><?php } ?><?php if (count($allstyles)==1) { ?><input type="hidden" name="style" value="<?php echo array_keys($allstyles)[0] ?>" /><?php } ?> + </select></div> + </div> + </div> + </div></fieldset> + <fieldset class="<?php echo '1'?" open":"" ?><?php echo '1'?" show":"" ?>"><legend><div class="arrow-right closed" /><div class="arrow-down open" /><?php echo lang('security') ?></legend><div> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('user_password_expires')))); ?></span> + + </div> + <div class="input"> + <?php include_once( 'modules/template-engine/components/html/date/component-date.php') ?><?php component_date($passwordExpires) ?> + + </div> + </div> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('user_last_login')))); ?></span> + + </div> + <div class="input"> + <?php include_once( 'modules/template-engine/components/html/date/component-date.php') ?><?php component_date($lastLogin) ?> + + </div> + </div> + <div class="line"> + <div class="label"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities(lang('token')))); ?></span> + + </div> + <div class="input"> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($totpToken))); ?></span> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_otpsecret" class="label"><?php echo lang('user_totp') ?> + </label> + </div> + <div class="input"> + <?php { $tmpname = 'totp';$default = '';$readonly = ''; + if ( isset($$tmpname) ) + $checked = $$tmpname; + else + $checked = $default; + + ?><input class="checkbox" type="checkbox" id="<?php echo REQUEST_ID ?>_<?php echo $tmpname ?>" name="<?php echo $tmpname ?>" <?php if ($readonly) echo ' disabled="disabled"' ?> value="1" <?php if( $checked ) echo 'checked="checked"' ?> /><?php + + if ( $readonly && $checked ) + { + ?><input type="hidden" name="<?php echo $tmpname ?>" value="1" /><?php + } + } ?> + + <label for="<?php echo REQUEST_ID ?>_totp" class="label"><?php echo lang('user_totp') ?> + </label> + <span class="text"><?php echo nl2br(encodeHtml(htmlentities($otpSecret))); ?></span> + + <div class="qrcode" data-qrcode="<?php echo $totpSecretUrl ?>" title="<?php echo $totpSecretUrl ?>"></div> + + </div> + </div> + <div class="line"> + <div class="label"> + <label for="<?php echo REQUEST_ID ?>_otpsecret" class="label"><?php echo lang('user_hotp') ?> + </label> + </div> + <div class="input"> + <?php { $tmpname = 'hotp';$default = '';$readonly = ''; + if ( isset($$tmpname) ) + $checked = $$tmpname; + else + $checked = $default; + + ?><input class="checkbox" type="checkbox" id="<?php echo REQUEST_ID ?>_<?php echo $tmpname ?>" name="<?php echo $tmpname ?>" <?php if ($readonly) echo ' disabled="disabled"' ?> value="1" <?php if( $checked ) echo 'checked="checked"' ?> /><?php + + if ( $readonly && $checked ) + { + ?><input type="hidden" name="<?php echo $tmpname ?>" value="1" /><?php + } + } ?> + + <label for="<?php echo REQUEST_ID ?>_hotp" class="label"><?php echo lang('user_hotp') ?> + </label> + <div class="qrcode" data-qrcode="<?php echo $hotpSecretUrl ?>" title="<?php echo $hotpSecretUrl ?>"></div> + + </div> + </div> + </div></fieldset> + <div class="bottom"><div class="command "><input type="button" class="submit ok" value="OK" /></div></div></form> + + \ No newline at end of file diff --git a/modules/editor/codemirror/.gitattributes b/modules/editor/codemirror/.gitattributes @@ -0,0 +1,8 @@ +*.txt text eol=lf +*.js text eol=lf +*.html text eol=lf +*.md text eol=lf +*.json text eol=lf +*.yml text eol=lf +*.css text eol=lf +*.svg text eol=lf diff --git a/modules/editor/codemirror/CHANGELOG.md b/modules/editor/codemirror/CHANGELOG.md @@ -0,0 +1,1272 @@ +## 5.32.0 (2017-11-22) + +### Bug fixes + +Increase contrast on default bracket-matching colors. + +[javascript mode](http://codemirror.net/mode/javascript/): Recognize TypeScript type parameters for calls, type guards, and type parameter defaults. Improve handling of `enum` and `module` keywords. + +[comment addon](http://codemirror.net/doc/manual.html#addon_comment): Fix bug when uncommenting a comment that spans all but the last selected line. + +[searchcursor addon](http://codemirror.net/doc/manual.html#addon_searchcursor): Fix bug in case folding. + +[emacs bindings](http://codemirror.net/demo/emacs.html): Prevent single-character deletions from resetting the kill ring. + +[closebrackets addon](http://codemirror.net/doc/manual.html#addon_closebrackets): Tweak quote matching behavior. + +### New features + +[continuelist addon](http://codemirror.net/doc/manual.html#addon_continuelist): Increment ordered list numbers when adding one. + +## 5.31.0 (2017-10-20) + +### Bug fixes + +Further improve selection drawing and cursor motion in right-to-left documents. + +[vim bindings](http://codemirror.net/demo/vim.html): Fix ctrl-w behavior, support quote-dot and backtick-dot marks, make the wide cursor visible in contentEditable [input mode](http://codemirror.net/doc/manual.html#option_contentEditable). + +[continuecomment addon](http://codemirror.net/doc/manual.html#addon_continuecomment): Fix bug when pressing enter after a single-line block comment. + +[markdown mode](http://codemirror.net/mode/markdown/): Fix issue with leaving indented fenced code blocks. + +[javascript mode](http://codemirror.net/mode/javascript/): Fix bad parsing of operators without spaces between them. Fix some corner cases around semicolon insertion and regexps. + +### New features + +Modes added with [`addOverlay`](http://codemirror.net/doc/manual.html#addOverlay) now have access to a [`baseToken`](http://codemirror.net/doc/manual.html#baseToken) method on their input stream, giving access to the tokens of the underlying mode. + +## 5.30.0 (2017-09-20) + +### Bug fixes + +Fixed a number of issues with drawing right-to-left selections and mouse selection in bidirectional text. + +[search addon](http://codemirror.net/demo/search/): Fix crash when restarting search after doing empty search. + +[mark-selection addon](http://cm/doc/manual.html#addon_mark-selection): Fix off-by-one bug. + +[tern addon](http://codemirror.net/demo/tern.html): Fix bad request made when editing at the bottom of a large document. + +[javascript mode](http://codemirror.net/mode/javascript/): Improve parsing in a number of corner cases. + +[markdown mode](http://codemirror.net/mode/markdown/): Fix crash when a sub-mode doesn't support indentation, allow uppercase X in task lists. + +[gfm mode](http://codemirror.net/mode/gfm/): Don't highlight SHA1 'hashes' without numbers to avoid false positives. + +[soy mode](http://codemirror.net/mode/soy/): Support injected data and `@param` in comments. + +### New features + +[simple mode addon](http://codemirror.net/demo/simplemode.html): Allow groups in regexps when `token` isn't an array. + +## 5.29.0 (2017-08-24) + +### Bug fixes + +Fix crash in contentEditable input style when editing near a bookmark. + +Make sure change origins are preserved when splitting changes on [read-only marks](http://codemirror.net/doc/manual.html#mark_readOnly). + +[javascript mode](http://codemirror.net/mode/javascript/): More support for TypeScript syntax. + +[d mode](http://codemirror.net/mode/d/): Support nested comments. + +[python mode](http://codemirror.net/mode/python/): Improve tokenizing of operators. + +[markdown mode](http://codemirror.net/mode/markdown/): Further improve CommonMark conformance. + +[css mode](http://codemirror.net/mode/css/): Don't run comment tokens through the mode's state machine. + +[shell mode](http://codemirror.net/mode/shell/): Allow strings to span lines. + +[search addon](http://codemirror.net/demo/search/): Fix crash in persistent search when `extraKeys` is null. + +## 5.28.0 (2017-07-21) + +### Bug fixes + +Fix copying of, or replacing editor content with, a single dash character when copying a big selection in some corner cases. + +Make [`"goLineLeft"`](http://codemirror.net/doc/manual.html#command_goLineLeft)/`"goLineRight"` behave better on wrapped lines. + +[sql mode](http://codemirror.net/mode/sql/): Fix tokenizing of multi-dot operator and allow digits in subfield names. + +[searchcursor addon](http://codemirror.net/doc/manual.html#addon_searchcursor): Fix infinite loop on some composed character inputs. + +[markdown mode](http://codemirror.net/mode/markdown/): Make list parsing more CommonMark-compliant. + +[gfm mode](http://codemirror.net/mode/gfm/): Highlight colon syntax for emoji. + +### New features + +Expose [`startOperation`](http://codemirror.net/doc/manual.html#startOperation) and `endOperation` for explicit operation management. + +[sublime bindings](http://codemirror.net/demo/sublime.html): Add extend-selection (Ctrl-Alt- or Cmd-Shift-Up/Down). + +## 5.27.4 (2017-06-29) + +### Bug fixes + +Fix crash when using mode lookahead. + +[markdown mode](http://codemirror.net/mode/markdown/): Don't block inner mode's indentation support. + +## 5.27.2 (2017-06-22) + +### Bug fixes + +Fix crash in the [simple mode](http://codemirror.net/demo/simplemode.html)< addon. + +## 5.27.0 (2017-06-22) + +### Bug fixes + +Fix infinite loop in forced display update. + +Properly disable the hidden textarea when `readOnly` is `"nocursor"`. + +Calling the `Doc` constructor without `new` works again. + +[sql mode](http://codemirror.net/mode/sql/): Handle nested comments. + +[javascript mode](http://codemirror.net/mode/javascript/): Improve support for TypeScript syntax. + +[markdown mode](http://codemirror.net/mode/markdown/): Fix bug where markup was ignored on indented paragraph lines. + +[vim bindings](http://codemirror.net/demo/vim.html): Referencing invalid registers no longer causes an uncaught exception. + +[rust mode](http://codemirror.net/mode/rust/): Add the correct MIME type. + +[matchbrackets addon](http://codemirror.net/doc/manual.html#addon_matchbrackets): Document options. + +### New features + +Mouse button clicks can now be bound in keymaps by using names like `"LeftClick"` or `"Ctrl-Alt-MiddleTripleClick"`. When bound to a function, that function will be passed the position of the click as second argument. + +The behavior of mouse selection and dragging can now be customized with the [`configureMouse`](http://codemirror.net/doc/manual.html#option_configureMouse) option. + +Modes can now look ahead across line boundaries with the [`StringStream`](http://codemirror.net/doc/manual.html#StringStream)`.lookahead` method. + +Introduces a `"type"` token type, makes modes that recognize types output it, and add styling for it to the themes. + +New [`pasteLinesPerSelection`](http://codemirror.net/doc/manual.html#option_pasteLinesPerSelection) option to control the behavior of pasting multiple lines into multiple selections. + +[searchcursor addon](http://codemirror.net/doc/manual.html#addon_searchcursor): Support multi-line regular expression matches, and normalize strings when matching. + +## 5.26.0 (2017-05-22) + +### Bug fixes + +In textarea-mode, don't reset the input field during composition. + +More careful restoration of selections in widgets, during editor redraw. + +[javascript mode](http://codemirror.net/mode/javascript/): More TypeScript parsing fixes. + +[julia mode](http://codemirror.net/mode/julia/): Fix issue where the mode gets stuck. + +[markdown mode](http://codemirror.net/mode/markdown/): Understand cross-line links, parse all bracketed things as links. + +[soy mode](http://codemirror.net/mode/soy/): Support single-quoted strings. + +[go mode](http://codemirror.net/mode/go/): Don't try to indent inside strings or comments. + +### New features + +[vim bindings](http://codemirror.net/demo/vim.html): Parse line offsets in line or range specs. + +## 5.25.2 (2017-04-20) + +### Bug fixes + +Better handling of selections that cover the whole viewport in contentEditable-mode. + +No longer accidentally scroll the editor into view when calling `setValue`. + +Work around Chrome Android bug when converting screen coordinates to editor positions. + +Make sure long-clicking a selection sets a cursor and doesn't show the editor losing focus. + +Fix issue where pointer events were incorrectly disabled on Chrome's overlay scrollbars. + +[javascript mode](http://codemirror.net/mode/javascript/): Recognize annotations and TypeScript-style type parameters. + +[shell mode](http://codemirror.net/mode/shell/): Handle nested braces. + +[markdown mode](http://codemirror.net/mode/markdown/): Make parsing of strong/em delimiters CommonMark-compliant. + +## 5.25.0 (2017-03-20) + +### Bug fixes + +In contentEditable-mode, properly locate changes that repeat a character when inserted with IME. + +Fix handling of selections bigger than the viewport in contentEditable mode. + +Improve handling of changes that insert or delete lines in contentEditable mode. + +Count Unicode control characters 0x80 to 0x9F as special (non-printing) chars. + +Fix handling of shadow DOM roots when finding the active element. + +Add `role=presentation` to more DOM elements to improve screen reader support. + +[merge addon](http://codemirror.net/doc/manual.html#addon_merge): Make aligning of unchanged chunks more robust. + +[comment addon](http://codemirror.net/doc/manual.html#addon_comment): Fix comment-toggling on a block of text that starts and ends in a (differnet) block comment. + +[javascript mode](http://codemirror.net/mode/javascript/): Improve support for TypeScript syntax. + +[r mode](http://codemirror.net/mode/r/): Fix indentation after semicolon-less statements. + +[shell mode](http://codemirror.net/mode/shell/): Properly handle escaped parentheses in parenthesized expressions. + +[markdown mode](http://codemirror.net/mode/markdown/): Fix a few bugs around leaving fenced code blocks. + +[soy mode](http://codemirror.net/mode/soy/): Improve indentation. + +### New features + +[lint addon](http://codemirror.net/doc/manual.html#addon_lint): Support asynchronous linters that return promises. + +[continuelist addon](http://codemirror.net/doc/manual.html#addon_continuelist): Support continuing task lists. + +[vim bindings](http://codemirror.net/demo/vim.html): Make Y behave like yy. + +[sql mode](http://codemirror.net/mode/sql/): Support sqlite dialect. + +## 5.24.2 (2017-02-22) + +### Bug fixes + +[javascript mode](http://codemirror.net/mode/javascript/): Support computed class method names. + +[merge addon](http://codemirror.net/doc/manual.html#addon_merge): Improve aligning of unchanged code in the presence of marks and line widgets. + +## 5.24.0 (2017-02-20) + +### Bug fixes + +A cursor directly before a line-wrapping break is now drawn before or after the line break depending on which direction you arrived from. + +Visual cursor motion in line-wrapped right-to-left text should be much more correct. + +Fix bug in handling of read-only marked text. + +[shell mode](http://codemirror.net/mode/shell/): Properly tokenize nested parentheses. + +[python mode](http://codemirror.net/mode/python/): Support underscores in number literals. + +[sass mode](http://codemirror.net/mode/sass/): Uses the full list of CSS properties and keywords from the CSS mode, rather than defining its own incomplete subset. + +[css mode](http://codemirror.net/mode/css/): Expose `lineComment` property for LESS and SCSS dialects. Recognize vendor prefixes on pseudo-elements. + +[julia mode](http://codemirror.net/mode/julia/): Properly indent `elseif` lines. + +[markdown mode](http://codemirror.net/mode/markdown/): Properly recognize the end of fenced code blocks when inside other markup. + +[scala mode](http://codemirror.net/mode/clike/): Improve handling of operators containing <code>#</code>, <code>@</code>, and <code>:</code> chars. + +[xml mode](http://codemirror.net/mode/xml/): Allow dashes in HTML tag names. + +[javascript mode](http://codemirror.net/mode/javascript/): Improve parsing of async methods, TypeScript-style comma-separated superclass lists. + +[indent-fold addon](http://codemirror.net/demo/folding.html): Ignore comment lines. + +### New features + +Positions now support a `sticky` property which determines whether they should be associated with the character before (value `"before"`) or after (value `"after"`) them. + +[vim bindings](http://codemirror.net/demo/vim.html): Make it possible to remove built-in bindings through the API. + +[comment addon](http://codemirror.net/doc/manual.html#addon_comment): Support a per-mode <code>useInnerComments</code> option to optionally suppress descending to the inner modes to get comment strings. + +### Breaking changes + +The [sass mode](http://codemirror.net/mode/sass/) now depends on the [css mode](http://codemirror.net/mode/css/). + +## 5.23.0 (2017-01-19) + +### Bug fixes + +Presentation-related elements DOM elements are now marked as such to help screen readers. + +[markdown mode](http://codemirror.net/mode/markdown/): Be more picky about what HTML tags look like to avoid false positives. + +### New features + +`findModeByMIME` now understands `+json` and `+xml` MIME suffixes. + +[closebrackets addon](http://codemirror.net/doc/manual.html#addon_closebrackets): Add support for an `override` option to ignore language-specific defaults. + +[panel addon](http://codemirror.net/doc/manual.html#addon_panel): Add a `stable` option that auto-scrolls the content to keep it in the same place when inserting/removing a panel. + +## 5.22.2 (2017-01-12) + +### Bug fixes + +Include rollup.config.js in NPM package, so that it can be used to build from source. + +## 5.22.0 (2016-12-20) + +### Bug fixes + +[sublime bindings](http://codemirror.net/demo/sublime.html): Make `selectBetweenBrackets` work with multiple cursors. + +[javascript mode](http://codemirror.net/mode/javascript/): Fix issues with parsing complex TypeScript types, imports, and exports. + +A contentEditable editor instance with autofocus enabled no longer crashes during initializing. + +### New features + +[emacs bindings](http://codemirror.net/demo/emacs.html): Export `CodeMirror.emacs` to allow other addons to hook into Emacs-style functionality. + +[active-line addon](http://codemirror.net/doc/manual.html#addon_active-line): Add `nonEmpty` option. + +New event: [`optionChange`](http://codemirror.net/doc/manual.html#event_optionChange). + +## 5.21.0 (2016-11-21) + +### Bug fixes + +Tapping/clicking the editor in [contentEditable mode](http://codemirror.net/doc/manual.html#option_inputStyle) on Chrome now puts the cursor at the tapped position. + +Fix various crashes and misbehaviors when reading composition events in [contentEditable mode](http://codemirror.net/doc/manual.html#option_inputStyle). + +Catches and ignores an IE 'Unspecified Error' when creating an editor in an iframe before there is a `<body>`. + +[merge addon](http://codemirror.net/doc/manual.html#addon_merge): Fix several issues in the chunk-aligning feature. + +[verilog mode](http://codemirror.net/mode/verilog): Rewritten to address various issues. + +[julia mode](http://codemirror.net/mode/julia): Recognize Julia 0.5 syntax. + +[swift mode](http://codemirror.net/mode/swift): Various fixes and adjustments to current syntax. + +[markdown mode](http://codemirror.net/mode/markdown): Allow lists without a blank line above them. + +### New features + +The [`setGutterMarker`](http://codemirror.net/doc/manual.html#setGutterMarker), [`clearGutter`](http://codemirror.net/doc/manual.html#clearGutter), and [`lineInfo`](http://codemirror.net/doc/manual.html#lineInfo) methods are now available on `Doc` objects. + +The [`heightAtLine`](http://codemirror.net/doc/manual.html#heightAtLine) method now takes an extra argument to allow finding the height at the top of the line's line widgets. + +[ruby mode](http://codemirror.net/mode/ruby): `else` and `elsif` are now immediately indented. + +[vim bindings](http://codemirror.net/demo/vim.html): Bind Ctrl-T and Ctrl-D to in- and dedent in insert mode. + +## 5.20.2 (2016-10-21) + +### Bug fixes + +Fix `CodeMirror.version` returning the wrong version number. + +## 5.20.0 (2016-10-20) + +### Bug fixes + +Make `newlineAndIndent` command work with multiple cursors on the same line. + +Make sure keypress events for backspace are ignored. + +Tokens styled with overlays no longer get a nonsense `cm-cm-overlay` class. + +Line endings for pasted content are now normalized to the editor's [preferred ending](http://codemirror.net/doc/manual.html#option_lineSeparator). + +[javascript mode](http://codemirror.net/mode/javascript): Improve support for class expressions. Support TypeScript optional class properties, the `abstract` keyword, and return type declarations for arrow functions. + +[css mode](http://codemirror.net/mode/css): Fix highlighting of mixed-case keywords. + +[closebrackets addon](http://codemirror.net/doc/manual.html#addon_closebrackets): Improve behavior when typing a quote before a string. + +### New features + +The core is now maintained as a number of small files, using ES6 syntax and modules, under the `src/` directory. A git checkout no longer contains a working `codemirror.js` until you `npm build` (but when installing from NPM, it is included). + +The [`refresh`](http://codemirror.net/doc/manual.html#event_refresh) event is now documented and stable. + +## 5.19.0 (2016-09-20) + +### Bugfixes + +[erlang mode](http://codemirror.net/mode/erlang): Fix mode crash when trying to read an empty context. + +[comment addon](http://codemirror.net/doc/manual.html#addon_comment): Fix broken behavior when toggling comments inside a comment. + +xml-fold addon: Fix a null-dereference bug. + +Page up and page down now do something even in single-line documents. + +Fix an issue where the cursor position could be off in really long (~8000 character) tokens. + +### New features + +[javascript mode](http://codemirror.net/mode/javascript): Better indentation when semicolons are missing. Better support for TypeScript classes, optional parameters, and the `type` keyword. + +The [`blur`](http://codemirror.net/doc/manual.html#event_blur) and [`focus`](http://codemirror.net/doc/manual.html#event_focus) events now pass the DOM event to their handlers. + +## 5.18.2 (2016-08-23) + +### Bugfixes + +[vue mode](http://codemirror.net/mode/vue): Fix outdated references to renamed Pug mode dependency. + +## 5.18.0 (2016-08-22) + +### Bugfixes + +Make sure [gutter backgrounds](http://codemirror.net/doc/manual.html#addLineClass) stick to the rest of the gutter during horizontal scrolling. + +The contenteditable [`inputStyle`](http://codemirror.net/doc/manual.html#option_inputStyle) now properly supports pasting on pre-Edge IE versions. + +[javascript mode](http://codemirror.net/mode/javascript): Fix some small parsing bugs and improve TypeScript support. + +[matchbrackets addon](http://codemirror.net/doc/manual.html#addon_matchbrackets): Fix bug where active highlighting was left in editor when the addon was disabled. + +[match-highlighter addon](http://codemirror.net/doc/manual.html#addon_match-highlighter): Only start highlighting things when the editor gains focus. + +[javascript-hint addon](http://codemirror.net/doc/manual.html#addon_javascript-hint): Also complete non-enumerable properties. + +### New features + +The [`addOverlay`](http://codemirror.net/doc/manual.html#addOverlay) method now supports a `priority` option to control the order in which overlays are applied. + +MIME types that end in `+json` now default to the JSON mode when the MIME itself is not defined. + +### Breaking changes + +The mode formerly known as Jade was renamed to [Pug](http://codemirror.net/mode/pug). + +The [Python mode](http://codemirror.net/mode/python) now defaults to Python 3 (rather than 2) syntax. + +## 5.17.0 (2016-07-19) + +### Bugfixes + +Fix problem with wrapped trailing whitespace displaying incorrectly. + +Prevent IME dialog from overlapping typed content in Chrome. + +Improve measuring of characters near a line wrap. + +[javascript mode](http://codemirror.net/mode/javascript): Improve support for `async`, allow trailing commas in `import` lists. + +[vim bindings](http://codemirror.net/demo/vim.html): Fix backspace in replace mode. + +[sublime bindings](http://codemirror.net/demo/sublime.html): Fix some key bindings on OS X to match Sublime Text. + +### New features + +[markdown mode](http://codemirror.net/mode/markdown): Add more classes to image links in highlight-formatting mode. + +## 5.16.0 (2016-06-20) + +### Bugfixes + +Fix glitches when dragging content caused by the drop indicator receiving mouse events. + +Make Control-drag work on Firefox. + +Make clicking or selection-dragging at the end of a wrapped line select the right position. + +[show-hint addon](http://codemirror.net/doc/manual.html#addon_show-hint): Prevent widget scrollbar from hiding part of the hint text. + +[rulers addon](http://codemirror.net/doc/manual.html#addon_rulers): Prevent rulers from forcing a horizontal editor scrollbar. + +### New features + +[search addon](http://codemirror.net/doc/manual.html#addon_search): Automatically bind search-related keys in persistent dialog. + +[sublime keymap](http://codemirror.net/demo/sublime.html): Add a multi-cursor aware smart backspace binding. + +## 5.15.2 (2016-05-20) + +### Bugfixes + +Fix a critical document corruption bug that occurs when a document is gradually grown. + +## 5.15.0 (2016-05-20) + +### Bugfixes + +Fix bug that caused the selection to reset when focusing the editor in contentEditable input mode. + +Fix issue where not all ASCII control characters were being replaced by placeholders. + +Remove the assumption that all modes have a `startState` method from several wrapping modes. + +Fix issue where the editor would complain about overlapping collapsed ranges when there weren't any. + +Optimize document tree building when loading or pasting huge chunks of content. + +[markdown mode](http://codemirror.net/mode/markdown/): Fix several issues in matching link targets. + +[clike mode](http://codemirror.net/mode/clike/): Improve indentation of C++ template declarations. + +### New features + +Explicitly bind Ctrl-O on OS X to make that binding (“open line”) act as expected. + +Pasting [linewise-copied](http://codemirror.net/doc/manual.html#option_lineWiseCopyCut) content when there is no selection now inserts the lines above the current line. + +[javascript mode](http://codemirror.net/mode/javascript/): Support `async`/`await` and improve support for TypeScript type syntax. + +## 5.14.2 (2016-04-20) + +### Bugfixes + +Push a new package to NPM due to an [NPM bug](https://github.com/npm/npm/issues/5082) omitting the LICENSE file in 5.14.0. + +Set `dataTransfer.effectAllowed` in `dragstart` handler to help browsers use the right drag icon. + +Add the [mbox mode](http://codemirror.net/mode/mbox/index.html) to `mode/meta.js`. + +## 5.14.0 (2016-04-20) + +### Bugfixes + +[`posFromIndex`](http://codemirror.net/doc/manual.html#posFromIndex) and [`indexFromPos`](http://codemirror.net/doc/manual.html#indexFromPos) now take [`lineSeparator`](http://codemirror.net/doc/manual.html#option_lineSeparator) into account. + +[vim bindings](http://codemirror.net/demo/vim.html): Only call `.save()` when it is actually available. + +[comment addon](http://codemirror.net/doc/manual.html#addon_comment): Be careful not to mangle multi-line strings. + +[Python mode](http://codemirror.net/mode/python/index.html): Improve distinguishing of decorators from `@` operators. + +[`findMarks`](http://codemirror.net/doc/manual.html#findMarks): No longer return marks that touch but don't overlap given range. + +### New features + +[vim bindings](http://codemirror.net/demo/vim.html): Add yank command. + +[match-highlighter addon](http://codemirror.net/doc/manual.html#addon_match-highlighter): Add `trim` option to disable ignoring of whitespace. + +[PowerShell mode](http://codemirror.net/mode/powershell/index.html): Added. + +[Yacas mode](http://codemirror.net/mode/yacas/index.html): Added. + +[Web IDL mode](http://codemirror.net/mode/webidl/index.html): Added. + +[SAS mode](http://codemirror.net/mode/sas/index.html): Added. + +[mbox mode](http://codemirror.net/mode/mbox/index.html): Added. + +## 5.13.2 (2016-03-23) + +### Bugfixes + +Solves a problem where the gutter would sometimes not extend all the way to the end of the document. + +## 5.13.0 (2016-03-21) + +### New features + +New DOM event forwarded: [`"dragleave"`](http://codemirror.net/doc/manual.html#event_dom). + +[protobuf mode](http://codemirror.net/mode/protobuf/index.html): Newly added. + +### Bugfixes + +Fix problem where [`findMarks`](http://codemirror.net/doc/manual.html#findMarks) sometimes failed to find multi-line marks. + +Fix crash that showed up when atomic ranges and bidi text were combined. + +[show-hint addon](http://codemirror.net/demo/complete.html): Completion widgets no longer close when the line indented or dedented. + +[merge addon](http://codemirror.net/demo/merge.html): Fix bug when merging chunks at the end of the file. + +[placeholder addon](http://codemirror.net/doc/manual.html#addon_placeholder): No longer gets confused by [`swapDoc`](http://codemirror.net/doc/manual.html#swapDoc). + +[simplescrollbars addon](http://codemirror.net/doc/manual.html#addon_simplescrollbars): Fix invalid state when deleting at end of document. + +[clike mode](http://codemirror.net/mode/clike/index.html): No longer gets confused when a comment starts after an operator. + +[markdown mode](http://codemirror.net/mode/markdown/index.html): Now supports CommonMark-style flexible list indentation. + +[dylan mode](http://codemirror.net/mode/dylan/index.html): Several improvements and fixes. + +## 5.12.0 (2016-02-19) + +### New features + +[Vim bindings](http://codemirror.net/demo/vim.html): Ctrl-Q is now an alias for Ctrl-V. + +[Vim bindings](http://codemirror.net/demo/vim.html): The Vim API now exposes an `unmap` method to unmap bindings. + +[active-line addon](http://codemirror.net/demo/activeline.html): This addon can now style the active line's gutter. + +[FCL mode](http://codemirror.net/mode/fcl/): Newly added. + +[SQL mode](http://codemirror.net/mode/sql/): Now has a Postgresql dialect. + +### Bugfixes + +Fix [issue](https://github.com/codemirror/CodeMirror/issues/3781) where trying to scroll to a horizontal position outside of the document's width could cause the gutter to be positioned incorrectly. + +Use absolute, rather than fixed positioning in the context-menu intercept hack, to work around a [problem](https://github.com/codemirror/CodeMirror/issues/3238) when the editor is inside a transformed parent container. + +Solve a [problem](https://github.com/codemirror/CodeMirror/issues/3821) where the horizontal scrollbar could hide text in Firefox. + +Fix a [bug](https://github.com/codemirror/CodeMirror/issues/3834) that caused phantom scroll space under the text in some situations. + +[Sublime Text bindings](http://codemirror.net/demo/sublime.html): Bind delete-line to Shift-Ctrl-K on OS X. + +[Markdown mode](http://codemirror.net/mode/markdown/): Fix [issue](https://github.com/codemirror/CodeMirror/issues/3787) where the mode would keep state related to fenced code blocks in an unsafe way, leading to occasional corrupted parses. + +[Markdown mode](http://codemirror.net/mode/markdown/): Ignore backslashes in code fragments. + +[Markdown mode](http://codemirror.net/mode/markdown/): Use whichever mode is registered as `text/html` to parse HTML. + +[Clike mode](http://codemirror.net/mode/clike/): Improve indentation of Scala `=>` functions. + +[Python mode](http://codemirror.net/mode/python/): Improve indentation of bracketed code. + +[HTMLMixed mode](http://codemirror.net/mode/htmlmixed/): Support multi-line opening tags for sub-languages (`<script>`, `<style>`, etc). + +[Spreadsheet mode](http://codemirror.net/mode/spreadsheet/): Fix bug where the mode did not advance the stream when finding a backslash. + +[XML mode](http://codemirror.net/mode/xml/): The mode now takes a `matchClosing` option to configure whether mismatched closing tags should be highlighted as errors. + +## 5.11.0 (2016-01-20) + +* New modes: [JSX](http://codemirror.net/mode/jsx/index.html), [literate Haskell](http://codemirror.net/mode/haskell-literate/index.html) +* The editor now forwards more [DOM events](http://codemirror.net/doc/manual.html#event_dom): `cut`, `copy`, `paste`, and `touchstart`. It will also forward `mousedown` for drag events +* Fixes a bug where bookmarks next to collapsed spans were not rendered +* The [Swift](http://codemirror.net/mode/swift/index.html) mode now supports auto-indentation +* Frontmatters in the [YAML frontmatter](http://codemirror.net/mode/yaml-frontmatter/index.html) mode are now optional as intended + +## 5.10.0 (2015-12-21) + +* Modify the way [atomic ranges](http://codemirror.net/doc/manual.html#mark_atomic) are skipped by selection to try and make it less surprising. +* The [Swift mode](http://codemirror.net/mode/swift/index.html) was rewritten. +* New addon: [jump-to-line](http://codemirror.net/doc/manual.html#addon_jump-to-line). +* New method: [`isReadOnly`](http://codemirror.net/doc/manual.html#isReadOnly). +* The [show-hint addon](http://codemirror.net/doc/manual.html#addon_show-hint) now defaults to picking completions on single click. +* The object passed to [`"beforeSelectionChange"`](http://codemirror.net/doc/manual.html#event_beforeSelectionChange) events now has an `origin` property. +* New mode: [Crystal](http://codemirror.net/mode/crystal/index.html). + +## 5.9.0 (2015-11-23) + +* Improve the way overlay (OS X-style) scrollbars are handled +* Make [annotatescrollbar](http://codemirror.net/doc/manual.html#addon_annotatescrollbar) and scrollpastend addons work properly together +* Make [show-hint](http://codemirror.net/doc/manual.html#addon_show-hint) addon select options on single click by default, move selection to hovered item +* Properly fold comments that include block-comment-start markers +* Many small language mode fixes + +## 5.8.0 (2015-10-20) + +* Fixes an infinite loop in the [hardwrap addon](http://codemirror.net/doc/manual.html#addon_hardwrap) +* New modes: [NSIS](http://codemirror.net/mode/nsis/index.html), [Ceylon](http://codemirror.net/mode/clike/index.html) +* The Kotlin mode is now a [clike](http://codemirror.net/mode/clike/index.html) dialect, rather than a stand-alone mode +* New option: [`allowDropFileTypes`](http://codemirror.net/doc/manual.html#option_allowDropFileTypes). Binary files can no longer be dropped into CodeMirror +* New themes: [bespin](http://codemirror.net/demo/theme.html#bespin), [hopscotch](http://codemirror.net/demo/theme.html#hopscotch), [isotope](http://codemirror.net/demo/theme.html#isotope), [railscasts](http://codemirror.net/demo/theme.html#railscasts) + +## 5.7.0 (2015-09-21) + +* New modes: [Vue](http://codemirror.net/mode/vue/index.html), [Oz](http://codemirror.net/mode/oz/index.html), [MscGen](http://codemirror.net/mode/mscgen/index.html) (and dialects), [Closure Stylesheets](http://codemirror.net/mode/css/gss.html) +* Implement [CommonMark](http://commonmark.org)-style flexible list indent and cross-line code spans in [Markdown](http://codemirror.net/mode/markdown/index.html) mode +* Add a replace-all button to the [search addon](http://codemirror.net/doc/manual.html#addon_search), and make the persistent search dialog transparent when it obscures the match +* Handle `acync`/`await` and ocal and binary numbers in [JavaScript mode](http://codemirror.net/mode/javascript/index.html) +* Fix various issues with the [Haxe mode](http://codemirror.net/mode/haxe/index.html) +* Make the [closebrackets addon](http://codemirror.net/doc/manual.html#addon_closebrackets) select only the wrapped text when wrapping selection in brackets +* Tokenize properties as properties in the [CoffeeScript mode](http://codemirror.net/mode/coffeescript/index.html) +* The [placeholder addon](http://codemirror.net/doc/manual.html#addon_placeholder) now accepts a DOM node as well as a string placeholder + +## 5.6.0 (2015-08-20) + +* Fix bug where you could paste into a `readOnly` editor +* Show a cursor at the drop location when dragging over the editor +* The [Rust mode](http://codemirror.net/mode/rust/index.html) was rewritten to handle modern Rust +* The editor and theme CSS was cleaned up. Some selectors are now less specific than before +* New theme: [abcdef](http://codemirror.net/demo/theme.html#abcdef) +* Lines longer than [`maxHighlightLength`](http://codemirror.net/doc/manual.html#option_maxHighlightLength) are now less likely to mess up indentation +* New addons: [`autorefresh`](http://codemirror.net/doc/manual.html#addon_autorefresh) for refreshing an editor the first time it becomes visible, and `html-lint` for using [HTMLHint](http://htmlhint.com/) +* The [`search`](http://codemirror.net/doc/manual.html#addon_search) addon now recognizes `\r` and `\n` in pattern and replacement input + +## 5.5.0 (2015-07-20) + +* New option: [`lineSeparator`](http://codemirror.net/doc/manual.html#option_lineSeparator) (with corresponding [method](http://codemirror.net/doc/manual.html#lineSeparator)) +* New themes: [dracula](http://codemirror.net/demo/theme.html#dracula), [seti](http://codemirror.net/demo/theme.html#seti), [yeti](http://codemirror.net/demo/theme.html#yeti), [material](http://codemirror.net/demo/theme.html#material), and [icecoder](http://codemirror.net/demo/theme.html#icecoder) +* New modes: [Brainfuck](http://codemirror.net/mode/brainfuck/index.html), [VHDL](http://codemirror.net/mode/vhdl/index.html), Squirrel ([clike](http://codemirror.net/mode/clike/index.html) dialect) +* Define a `findPersistent` command in the [search](http://codemirror.net/demo/search.html) addon, for a dialog that stays open as you cycle through matches +* From this release on, the NPM module doesn't include documentation and demos +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.4.0...5.5.0) + +## 5.4.0 (2015-06-25) + +* New modes: [Twig](http://codemirror.net/mode/twig/index.html), [Elm](http://codemirror.net/mode/elm/index.html), [Factor](http://codemirror.net/mode/factor/index.html), [Swift](http://codemirror.net/mode/swift/index.html) +* Prefer clipboard API (if available) when pasting +* Refined definition highlighting in [clike](http://codemirror.net/mode/clike/index.html) mode +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.3.0...5.4.0) + +## 5.3.0 (2015-05-20) + +* Fix several regressions in the [`show-hint`](http://codemirror.net/doc/manual.html#addon_show-hint) addon (`completeSingle` option, `"shown"` and `"close"` events) +* The [vim mode](http://codemirror.net/demo/vim.html) API was [documented](http://codemirror.net/doc/manual.html#vimapi) +* New modes: [ASN.1](http://codemirror.net/mode/asn.1/index.html), [TTCN](http://codemirror.net/mode/ttcn/index.html), and [TTCN-CFG](http://codemirror.net/mode/ttcn-cfg/index.html) +* The [clike](http://codemirror.net/mode/clike/index.html) mode can now deep-indent `switch` statements, and roughly recognizes types and defined identifiers +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.2.0...5.3.0) + +## 5.2.0 (2015-04-20) + +* Fix several race conditions in [`show-hint`](http://codemirror.net/doc/manual.html#addon_show-hint)'s asynchronous mode +* Fix backspace binding in [Sublime bindings](http://codemirror.net/demo/sublime.html) +* Change the way IME is handled in the `"textarea"` [input style](http://codemirror.net/doc/manual.html#option_inputStyle) +* New modes: [MUMPS](http://codemirror.net/mode/mumps/index.html), [Handlebars](http://codemirror.net/mode/handlebars/index.html) +* Rewritten modes: [Django](http://codemirror.net/mode/django/index.html), [Z80](http://codemirror.net/mode/z80/index.html) +* New theme: [Liquibyte](http://codemirror.net/demo/theme.html#liquibyte) +* New option: [`lineWiseCopyCut`](http://codemirror.net/doc/manual.html#option_lineWiseCopyCut) +* The [Vim mode](http://codemirror.net/demo/vim.html) now supports buffer-local options and the `filetype` setting +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.1.0...5.2.0) + +## 5.1.0 (2015-03-23) + +* New modes: [ASCII armor](http://codemirror.net/mode/asciiarmor/index.html) (PGP data), [Troff](http://codemirror.net/mode/troff/index.html), and [CMake](http://codemirror.net/mode/cmake/index.html). +* Remove SmartyMixed mode, rewrite [Smarty](http://codemirror.net/mode/smarty/index.html) mode to supersede it. +* New commands in the [merge addon](http://codemirror.net/doc/manual.html#addon_merge): `goNextDiff` and `goPrevDiff`. +* The [closebrackets addon](http://codemirror.net/doc/manual.html#addon_closebrackets) can now be configured per mode. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.0.0...5.1.0). + +## 5.0.0 (2015-02-20) + +* Experimental mobile support (tested on iOS, Android Chrome, stock Android browser) +* New option [`inputStyle`](http://codemirror.net/doc/manual.html#option_inputStyle) to switch between hidden textarea and contenteditable input. +* The [`getInputField`](http://codemirror.net/doc/manual.html#getInputField) method is no longer guaranteed to return a textarea. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.13.0...5.0.0). + +## 4.13.0 (2015-02-20) + +* Fix the way the [`closetag`](http://codemirror.net/demo/closetag.html) demo handles the slash character. +* New modes: [Forth](http://codemirror.net/mode/forth/index.html), [Stylus](http://codemirror.net/mode/stylus/index.html). +* Make the [CSS mode](http://codemirror.net/mode/css/index.html) understand some modern CSS extensions. +* Have the [Scala mode](http://codemirror.net/mode/clike/index.html) handle symbols and triple-quoted strings. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.12.0...4.13.0). + +## 4.12.0 (2015-01-22) + +* The [`closetag`](http://codemirror.net/doc/manual.html#addon_closetag) addon now defines a `"closeTag"` command. +* Adds a `findModeByFileName` to the [mode metadata](http://codemirror.net/doc/manual.html#addon_meta) addon. +* [Simple mode](http://codemirror.net/demo/simplemode.html) rules can now contain a `sol` property to only match at the start of a line. +* New addon: [`selection-pointer`](http://codemirror.net/doc/manual.html#addon_selection-pointer) to style the mouse cursor over the selection. +* Improvements to the [Sass mode](http://codemirror.net/mode/sass/index.html)'s indentation. +* The [Vim keymap](http://codemirror.net/demo/vim.html)'s search functionality now supports [scrollbar annotation](http://codemirror.net/doc/manual.html#addon_matchesonscrollbar). +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.11.0...4.12.0). + +## 4.11.0 (2015-01-09) + +Unfortunately, 4.10 did not take care of the Firefox scrolling issue entirely. This release adds two more patches to address that. + +## 4.10.0 (2014-12-29) + +Emergency single-patch update to 4.9\. Fixes Firefox-specific problem where the cursor could end up behind the horizontal scrollbar. + +## 4.9.0 (2014-12-23) + +* Overhauled scroll bar handling. Add pluggable [scrollbar implementations](http://codemirror.net/demo/simplescrollbars.html). +* Tweaked behavior for the [completion addons](http://codemirror.net/doc/manual.html#addon_show-hint) to not take text after cursor into account. +* Two new optional features in the [merge addon](http://codemirror.net/doc/manual.html#addon_merge): aligning editors, and folding unchanged text. +* New modes: [Dart](http://codemirror.net/mode/dart/index.html), [EBNF](http://codemirror.net/mode/ebnf/index.html), [spreadsheet](http://codemirror.net/mode/spreadsheet/index.html), and [Soy](http://codemirror.net/mode/soy/index.html). +* New [addon](http://codemirror.net/demo/panel.html) to show persistent panels below/above an editor. +* New themes: [zenburn](http://codemirror.net/demo/theme.html#zenburn) and [tomorrow night bright](http://codemirror.net/demo/theme.html#tomorrow-night-bright). +* Allow ctrl-click to clear existing cursors. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.8.0...4.9.0). + +## 4.8.0 (2014-11-22) + +* Built-in support for [multi-stroke key bindings](http://codemirror.net/doc/manual.html#normalizeKeyMap). +* New method: [`getLineTokens`](http://codemirror.net/doc/manual.html#getLineTokens). +* New modes: [dockerfile](http://codemirror.net/mode/dockerfile/index.html), [IDL](http://codemirror.net/mode/idl/index.html), [Objective C](http://codemirror.net/mode/clike/index.html) (crude). +* Support styling of gutter backgrounds, allow `"gutter"` styles in [`addLineClass`](http://codemirror.net/doc/manual.html#addLineClass). +* Many improvements to the [Vim mode](http://codemirror.net/demo/vim.html), rewritten visual mode. +* Improvements to modes: [gfm](http://codemirror.net/mode/gfm/index.html) (strikethrough), [SPARQL](http://codemirror.net/mode/sparql/index.html) (version 1.1 support), and [sTeX](http://codemirror.net/mode/stex/index.html) (no more runaway math mode). +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.7.0...4.8.0). + +## 4.7.0 (2014-10-20) + +* **Incompatible**: The [lint addon](http://codemirror.net/demo/lint.html) now passes the editor's value as first argument to asynchronous lint functions, for consistency. The editor is still passed, as fourth argument. +* Improved handling of unicode identifiers in modes for languages that support them. +* More mode improvements: [CoffeeScript](http://codemirror.net/mode/coffeescript/index.html) (indentation), [Verilog](http://codemirror.net/mode/verilog/index.html) (indentation), [Scala](http://codemirror.net/mode/clike/index.html) (indentation, triple-quoted strings), and [PHP](http://codemirror.net/mode/php/index.html) (interpolated variables in heredoc strings). +* New modes: [Textile](http://codemirror.net/mode/textile/index.html) and [Tornado templates](http://codemirror.net/mode/tornado/index.html). +* Experimental new [way to define modes](http://codemirror.net/demo/simplemode.html). +* Improvements to the [Vim bindings](http://codemirror.net/demo/vim.html): Arbitrary insert mode key mappings are now possible, and text objects are supported in visual mode. +* The mode [meta-information file](http://codemirror.net/mode/meta.js) now includes information about file extensions, and [helper functions](http://codemirror.net/doc/manual.html#addon_meta) `findModeByMIME` and `findModeByExtension`. +* New logo! +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.6.0...4.7.0). + +## 4.6.0 (2014-09-19) + +* New mode: [Modelica](http://codemirror.net/mode/modelica/index.html) +* New method: [`findWordAt`](http://codemirror.net/doc/manual.html#findWordAt) +* Make it easier to [use text background styling](http://codemirror.net/demo/markselection.html) +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.5.0...4.6.0). + +## 4.5.0 (2014-08-21) + +* Fix several serious bugs with horizontal scrolling +* New mode: [Slim](http://codemirror.net/mode/slim/index.html) +* New command: [`goLineLeftSmart`](http://codemirror.net/doc/manual.html#command_goLineLeftSmart) +* More fixes and extensions for the [Vim](http://codemirror.net/demo/vim.html) visual block mode +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.4.0...4.5.0). + +## 4.4.0 (2014-07-21) + +* **Note:** Some events might now fire in slightly different order (`"change"` is still guaranteed to fire before `"cursorActivity"`) +* Nested operations in multiple editors are now synced (complete at same time, reducing DOM reflows) +* Visual block mode for [vim](http://codemirror.net/demo/vim.html) (<C-v>) is nearly complete +* New mode: [Kotlin](http://codemirror.net/mode/kotlin/index.html) +* Better multi-selection paste for text copied from multiple CodeMirror selections +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.3.0...4.4.0). + +## 4.3.0 (2014-06-23) + +* Several [vim bindings](http://codemirror.net/demo/vim.html) improvements: search and exCommand history, global flag for `:substitute`, `:global` command. +* Allow hiding the cursor by setting [`cursorBlinkRate`](http://codemirror.net/doc/manual.html#option_cursorBlinkRate) to a negative value. +* Make gutter markers themeable, use this in foldgutter. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.2.0...4.3.0). + +## 4.2.0 (2014-05-19) + +* Fix problem where some modes were broken by the fact that empty tokens were forbidden. +* Several fixes to context menu handling. +* On undo, scroll _change_, not cursor, into view. +* Rewritten [Jade](http://codemirror.net/mode/jade/index.html) mode. +* Various improvements to [Shell](http://codemirror.net/mode/shell/index.html) (support for more syntax) and [Python](http://codemirror.net/mode/python/index.html) (better indentation) modes. +* New mode: [Cypher](http://codemirror.net/mode/cypher/index.html). +* New theme: [Neo](http://codemirror.net/demo/theme.html#neo). +* Support direct styling options (color, line style, width) in the [rulers](http://codemirror.net/doc/manual.html#addon_rulers) addon. +* Recognize per-editor configuration for the [show-hint](http://codemirror.net/doc/manual.html#addon_show-hint) and [foldcode](http://codemirror.net/doc/manual.html#addon_foldcode) addons. +* More intelligent scanning for existing close tags in [closetag](http://codemirror.net/doc/manual.html#addon_closetag) addon. +* In the [Vim bindings](http://codemirror.net/demo/vim.html): Fix bracket matching, support case conversion in visual mode, visual paste, append action. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.1.0...4.2.0). + +## 4.1.0 (2014-04-22) + +* _Slightly incompatible_: The [`"cursorActivity"`](http://codemirror.net/doc/manual.html#event_cursorActivity) event now fires after all other events for the operation (and only for handlers that were actually registered at the time the activity happened). +* New command: [`insertSoftTab`](http://codemirror.net/doc/manual.html#command_insertSoftTab). +* New mode: [Django](http://codemirror.net/mode/django/index.html). +* Improved modes: [Verilog](http://codemirror.net/mode/verilog/index.html) (rewritten), [Jinja2](http://codemirror.net/mode/jinja2/index.html), [Haxe](http://codemirror.net/mode/haxe/index.html), [PHP](http://codemirror.net/mode/php/index.html) (string interpolation highlighted), [JavaScript](http://codemirror.net/mode/javascript/index.html) (indentation of trailing else, template strings), [LiveScript](http://codemirror.net/mode/livescript/index.html) (multi-line strings). +* Many small issues from the 3.x→4.x transition were found and fixed. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.0.3...4.1.0). + +## 3.24.0 (2014-04-22) + +Merges the improvements from 4.1 that could easily be applied to the 3.x code. Also improves the way the editor size is updated when line widgets change. + +## 3.23.0 (2014-03-20) + +* In the [XML mode](http://codemirror.net/mode/xml/index.html), add `brackets` style to angle brackets, fix case-sensitivity of tags for HTML. +* New mode: [Dylan](http://codemirror.net/mode/dylan/index.html). +* Many improvements to the [Vim bindings](http://codemirror.net/demo/vim.html). + +## 3.22.0 (2014-02-21) + +* Adds the [`findMarks`](http://codemirror.net/doc/manual.html#findMarks) method. +* New addons: [rulers](http://codemirror.net/doc/manual.html#addon_rulers), markdown-fold, yaml-lint. +* New theme: [mdn-like](http://codemirror.net/demo/theme.html#mdn-like). +* New mode: [Solr](http://codemirror.net/mode/solr/index.html). +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.21.0...3.22.0). + +## 3.21.0 (2014-01-16) + +* Auto-indenting a block will no longer add trailing whitespace to blank lines. +* Marking text has a new option [`clearWhenEmpty`](http://codemirror.net/doc/manual.html#markText) to control auto-removal. +* Several bugfixes in the handling of bidirectional text. +* The [XML](http://codemirror.net/mode/xml/index.html) and [CSS](http://codemirror.net/mode/css/index.html) modes were largely rewritten. [LESS](http://codemirror.net/mode/css/less.html) support was added to the CSS mode. +* The OCaml mode was moved to an [mllike](http://codemirror.net/mode/mllike/index.html) mode, F# support added. +* Make it possible to fetch multiple applicable helper values with [`getHelpers`](http://codemirror.net/doc/manual.html#getHelpers), and to register helpers matched on predicates with [`registerGlobalHelper`](http://codemirror.net/doc/manual.html#registerGlobalHelper). +* New theme [pastel-on-dark](http://codemirror.net/demo/theme.html#pastel-on-dark). +* Better ECMAScript 6 support in [JavaScript](http://codemirror.net/mode/javascript/index.html) mode. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.20.0...3.21.0). + +## 3.20.0 (2013-11-21) + +* New modes: [Julia](http://codemirror.net/mode/julia/index.html) and [PEG.js](http://codemirror.net/mode/pegjs/index.html). +* Support ECMAScript 6 in the [JavaScript mode](http://codemirror.net/mode/javascript/index.html). +* Improved indentation for the [CoffeeScript mode](http://codemirror.net/mode/coffeescript/index.html). +* Make non-printable-character representation [configurable](http://codemirror.net/doc/manual.html#option_specialChars). +* Add ‘notification’ functionality to [dialog](http://codemirror.net/doc/manual.html#addon_dialog) addon. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.19.0...3.20.0). + +## 3.19.0 (2013-10-21) + +* New modes: [Eiffel](http://codemirror.net/mode/eiffel/index.html), [Gherkin](http://codemirror.net/mode/gherkin/index.html), [MSSQL dialect](http://codemirror.net/mode/sql/?mime=text/x-mssql). +* New addons: [hardwrap](http://codemirror.net/doc/manual.html#addon_hardwrap), [sql-hint](http://codemirror.net/doc/manual.html#addon_sql-hint). +* New theme: [MBO](http://codemirror.net/demo/theme.html#mbo). +* Add [support](http://codemirror.net/doc/manual.html#token_style_line) for line-level styling from mode tokenizers. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.18.0...3.19.0). + +## 3.18.0 (2013-09-23) + +Emergency release to fix a problem in 3.17 where `.setOption("lineNumbers", false)` would raise an error. + +## 3.17.0 (2013-09-23) + +* New modes: [Fortran](http://codemirror.net/mode/fortran/index.html), [Octave](http://codemirror.net/mode/octave/index.html) (Matlab), [TOML](http://codemirror.net/mode/toml/index.html), and [DTD](http://codemirror.net/mode/dtd/index.html). +* New addons: [`css-lint`](http://codemirror.net/addon/lint/css-lint.js), [`css-hint`](http://codemirror.net/doc/manual.html#addon_css-hint). +* Improve resilience to CSS 'frameworks' that globally mess up `box-sizing`. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.16.0...3.17.0). + +## 3.16.0 (2013-08-21) + +* The whole codebase is now under a single [license](http://codemirror.net/LICENSE) file. +* The project page was overhauled and redesigned. +* New themes: [Paraiso](http://codemirror.net/demo/theme.html#paraiso-dark) ([light](http://codemirror.net/demo/theme.html#paraiso-light)), [The Matrix](http://codemirror.net/demo/theme.html#the-matrix). +* Improved interaction between themes and [active-line](http://codemirror.net/doc/manual.html#addon_active-line)/[matchbrackets](http://codemirror.net/doc/manual.html#addon_matchbrackets) addons. +* New [folding](http://codemirror.net/doc/manual.html#addon_foldcode) function `CodeMirror.fold.comment`. +* Added [fullscreen](http://codemirror.net/doc/manual.html#addon_fullscreen) addon. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.15.0...3.16.0). + +## 3.15.0 (2013-07-29) + +* New modes: [Jade](http://codemirror.net/mode/jade/index.html), [Nginx](http://codemirror.net/mode/nginx/index.html). +* New addons: [Tern](http://codemirror.net/demo/tern.html), [matchtags](http://codemirror.net/doc/manual.html#addon_matchtags), and [foldgutter](http://codemirror.net/doc/manual.html#addon_foldgutter). +* Introduced [_helper_](http://codemirror.net/doc/manual.html#getHelper) concept ([context](https://groups.google.com/forum/#!msg/codemirror/cOc0xvUUEUU/nLrX1-qnidgJ)). +* New method: [`getModeAt`](http://codemirror.net/doc/manual.html#getModeAt). +* New themes: base16 [dark](http://codemirror.net/demo/theme.html#base16-dark)/[light](http://codemirror.net/demo/theme.html#base16-light), 3024 [dark](http://codemirror.net/demo/theme.html#3024-night)/[light](http://codemirror.net/demo/theme.html#3024-day), [tomorrow-night](http://codemirror.net/demo/theme.html#tomorrow-night-eighties). +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.14.0...3.15.0). + +## 3.14.0 (2013-06-20) + +* New addons: [trailing space highlight](http://codemirror.net/doc/manual.html#addon_trailingspace), [XML completion](http://codemirror.net/doc/manual.html#addon_xml-hint) (rewritten), and [diff merging](http://codemirror.net/doc/manual.html#addon_merge). +* [`markText`](http://codemirror.net/doc/manual.html#markText) and [`addLineWidget`](http://codemirror.net/doc/manual.html#addLineWidget) now take a `handleMouseEvents` option. +* New methods: [`lineAtHeight`](http://codemirror.net/doc/manual.html#lineAtHeight), [`getTokenTypeAt`](http://codemirror.net/doc/manual.html#getTokenTypeAt). +* More precise cleanness-tracking using [`changeGeneration`](http://codemirror.net/doc/manual.html#changeGeneration) and [`isClean`](http://codemirror.net/doc/manual.html#isClean). +* Many extensions to [Emacs](http://codemirror.net/demo/emacs.html) mode (prefixes, more navigation units, and more). +* New events [`"keyHandled"`](http://codemirror.net/doc/manual.html#event_keyHandled) and [`"inputRead"`](http://codemirror.net/doc/manual.html#event_inputRead). +* Various improvements to [Ruby](http://codemirror.net/mode/ruby/index.html), [Smarty](http://codemirror.net/mode/smarty/index.html), [SQL](http://codemirror.net/mode/sql/index.html), and [Vim](http://codemirror.net/demo/vim.html) modes. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.13.0...3.14.0). + +## 3.13.0 (2013-05-20) + +* New modes: [COBOL](http://codemirror.net/mode/cobol/index.html) and [HAML](http://codemirror.net/mode/haml/index.html). +* New options: [`cursorScrollMargin`](http://codemirror.net/doc/manual.html#option_cursorScrollMargin) and [`coverGutterNextToScrollbar`](http://codemirror.net/doc/manual.html#option_coverGutterNextToScrollbar). +* New addon: [commenting](http://codemirror.net/doc/manual.html#addon_comment). +* More features added to the [Vim keymap](http://codemirror.net/demo/vim.html). +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.12...3.13.0). + +## 3.12.0 (2013-04-19) + +* New mode: [GNU assembler](http://codemirror.net/mode/gas/index.html). +* New options: [`maxHighlightLength`](http://codemirror.net/doc/manual.html#option_maxHighlightLength) and [`historyEventDelay`](http://codemirror.net/doc/manual.html#option_historyEventDelay). +* Added [`addToHistory`](http://codemirror.net/doc/manual.html#mark_addToHistory) option for `markText`. +* Various fixes to JavaScript tokenization and indentation corner cases. +* Further improvements to the vim mode. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.11...v3.12). + +## 3.11.0 (2013-03-20) + +* **Removed code:** `collapserange`, `formatting`, and `simple-hint` addons. `plsql` and `mysql` modes (use [`sql`](http://codemirror.net/mode/sql/index.html) mode). +* **Moved code:** the range-finding functions for folding now have [their own files](http://codemirror.net/addon/fold/). +* **Changed interface:** the [`continuecomment`](http://codemirror.net/doc/manual.html#addon_continuecomment) addon now exposes an option, rather than a command. +* New modes: [SCSS](http://codemirror.net/mode/css/scss.html), [Tcl](http://codemirror.net/mode/tcl/index.html), [LiveScript](http://codemirror.net/mode/livescript/index.html), and [mIRC](http://codemirror.net/mode/mirc/index.html). +* New addons: [`placeholder`](http://codemirror.net/demo/placeholder.html), [HTML completion](http://codemirror.net/demo/html5complete.html). +* New methods: [`hasFocus`](http://codemirror.net/doc/manual.html#hasFocus), [`defaultCharWidth`](http://codemirror.net/doc/manual.html#defaultCharWidth). +* New events: [`beforeCursorEnter`](http://codemirror.net/doc/manual.html#event_beforeCursorEnter), [`renderLine`](http://codemirror.net/doc/manual.html#event_renderLine). +* Many improvements to the [`show-hint`](http://codemirror.net/doc/manual.html#addon_show-hint) completion dialog addon. +* Tweak behavior of by-word cursor motion. +* Further improvements to the [vim mode](http://codemirror.net/demo/vim.html). +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.1...v3.11). + +## 3.02.0 (2013-01-25) + +Single-bugfix release. Fixes a problem that prevents CodeMirror instances from being garbage-collected after they become unused. + +## 3.01.0 (2013-01-21) + +* Move all add-ons into an organized directory structure under [`/addon`](http://codemirror.net/addon/). **You might have to adjust your paths.** +* New modes: [D](http://codemirror.net/mode/d/index.html), [Sass](http://codemirror.net/mode/sass/index.html), [APL](http://codemirror.net/mode/apl/index.html), [SQL](http://codemirror.net/mode/sql/index.html) (configurable), and [Asterisk](http://codemirror.net/mode/asterisk/index.html). +* Several bugfixes in right-to-left text support. +* Add [`rtlMoveVisually`](http://codemirror.net/doc/manual.html#option_rtlMoveVisually) option. +* Improvements to vim keymap. +* Add built-in (lightweight) [overlay mode](http://codemirror.net/doc/manual.html#addOverlay) support. +* Support `showIfHidden` option for [line widgets](http://codemirror.net/doc/manual.html#addLineWidget). +* Add simple [Python hinter](http://codemirror.net/doc/manual.html#addon_python-hint). +* Bring back the [`fixedGutter`](http://codemirror.net/doc/manual.html#option_fixedGutter) option. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.0...v3.01). + +## 3.1.0 (2013-02-21) + +* **Incompatible:** key handlers may now _return_, rather than _throw_ `CodeMirror.Pass` to signal they didn't handle the key. +* Make documents a [first-class construct](http://codemirror.net/doc/manual.html#api_doc), support split views and subviews. +* Add a [new module](http://codemirror.net/doc/manual.html#addon_show-hint) for showing completion hints. Deprecate `simple-hint.js`. +* Extend [htmlmixed mode](http://codemirror.net/mode/htmlmixed/index.html) to allow custom handling of script types. +* Support an `insertLeft` option to [`setBookmark`](http://codemirror.net/doc/manual.html#setBookmark). +* Add an [`eachLine`](http://codemirror.net/doc/manual.html#eachLine) method to iterate over a document. +* New addon modules: [selection marking](http://codemirror.net/demo/markselection.html), [linting](http://codemirror.net/demo/lint.html), and [automatic bracket closing](http://codemirror.net/demo/closebrackets.html). +* Add [`"beforeChange"`](http://codemirror.net/doc/manual.html#event_beforeChange) and [`"beforeSelectionChange"`](http://codemirror.net/doc/manual.html#event_beforeSelectionChange) events. +* Add [`"hide"`](http://codemirror.net/doc/manual.html#event_hide) and [`"unhide"`](http://codemirror.net/doc/manual.html#event_unhide) events to marked ranges. +* Fix [`coordsChar`](http://codemirror.net/doc/manual.html#coordsChar)'s interpretation of its argument to match the documentation. +* New modes: [Turtle](http://codemirror.net/mode/turtle/index.html) and [Q](http://codemirror.net/mode/q/index.html). +* Further improvements to the [vim mode](http://codemirror.net/demo/vim.html). +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.01...v3.1). + +## 3.0.0 (2012-12-10) + +**New major version**. Only partially backwards-compatible. See the [upgrading guide](http://codemirror.net/doc/upgrade_v3.html) for more information. Changes since release candidate 2: + +* Rewritten VIM mode. +* Fix a few minor scrolling and sizing issues. +* Work around Safari segfault when dragging. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.0rc2...v3.0). + +## 2.38.0 (2013-01-21) + +Integrate some bugfixes, enhancements to the vim keymap, and new modes ([D](http://codemirror.net/mode/d/index.html), [Sass](http://codemirror.net/mode/sass/index.html), [APL](http://codemirror.net/mode/apl/index.html)) from the v3 branch. + +## 2.37.0 (2012-12-20) + +* New mode: [SQL](http://codemirror.net/mode/sql/index.html) (will replace [plsql](http://codemirror.net/mode/plsql/index.html) and [mysql](http://codemirror.net/mode/mysql/index.html) modes). +* Further work on the new VIM mode. +* Fix Cmd/Ctrl keys on recent Operas on OS X. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v2.36...v2.37). + +## 2.36.0 (2012-11-20) + +* New mode: [Z80 assembly](http://codemirror.net/mode/z80/index.html). +* New theme: [Twilight](http://codemirror.net/demo/theme.html#twilight). +* Add command-line compression helper. +* Make [`scrollIntoView`](http://codemirror.net/doc/manual.html#scrollIntoView) public. +* Add [`defaultTextHeight`](http://codemirror.net/doc/manual.html#defaultTextHeight) method. +* Various extensions to the vim keymap. +* Make [PHP mode](http://codemirror.net/mode/php/index.html) build on [mixed HTML mode](http://codemirror.net/mode/htmlmixed/index.html). +* Add [comment-continuing](http://codemirror.net/doc/manual.html#addon_continuecomment) add-on. +* Full [list of patches](http://codemirror.net/https://github.com/codemirror/CodeMirror/compare/v2.35...v2.36). + +## 2.35.0 (2012-10-22) + +* New (sub) mode: [TypeScript](http://codemirror.net/mode/javascript/typescript.html). +* Don't overwrite (insert key) when pasting. +* Fix several bugs in [`markText`](http://codemirror.net/doc/manual.html#markText)/undo interaction. +* Better indentation of JavaScript code without semicolons. +* Add [`defineInitHook`](http://codemirror.net/doc/manual.html#defineInitHook) function. +* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v2.34...v2.35). + +## 2.34.0 (2012-09-19) + +* New mode: [Common Lisp](http://codemirror.net/mode/commonlisp/index.html). +* Fix right-click select-all on most browsers. +* Change the way highlighting happens: + Saves memory and CPU cycles. + `compareStates` is no longer needed. + `onHighlightComplete` no longer works. +* Integrate mode (Markdown, XQuery, CSS, sTex) tests in central testsuite. +* Add a [`CodeMirror.version`](http://codemirror.net/doc/manual.html#version) property. +* More robust handling of nested modes in [formatting](http://codemirror.net/demo/formatting.html) and [closetag](http://codemirror.net/demo/closetag.html) plug-ins. +* Un/redo now preserves [marked text](http://codemirror.net/doc/manual.html#markText) and bookmarks. +* [Full list](https://github.com/codemirror/CodeMirror/compare/v2.33...v2.34) of patches. + +## 2.33.0 (2012-08-23) + +* New mode: [Sieve](http://codemirror.net/mode/sieve/index.html). +* New [`getViewPort`](http://codemirror.net/doc/manual.html#getViewport) and [`onViewportChange`](http://codemirror.net/doc/manual.html#option_onViewportChange) API. +* [Configurable](http://codemirror.net/doc/manual.html#option_cursorBlinkRate) cursor blink rate. +* Make binding a key to `false` disabling handling (again). +* Show non-printing characters as red dots. +* More tweaks to the scrolling model. +* Expanded testsuite. Basic linter added. +* Remove most uses of `innerHTML`. Remove `CodeMirror.htmlEscape`. +* [Full list](https://github.com/codemirror/CodeMirror/compare/v2.32...v2.33) of patches. + +## 2.32.0 (2012-07-23) + +Emergency fix for a bug where an editor with line wrapping on IE will break when there is _no_ scrollbar. + +## 2.31.0 (2012-07-20) + +* New modes: [OCaml](http://codemirror.net/mode/ocaml/index.html), [Haxe](http://codemirror.net/mode/haxe/index.html), and [VB.NET](http://codemirror.net/mode/vb/index.html). +* Several fixes to the new scrolling model. +* Add a [`setSize`](http://codemirror.net/doc/manual.html#setSize) method for programmatic resizing. +* Add [`getHistory`](http://codemirror.net/doc/manual.html#getHistory) and [`setHistory`](http://codemirror.net/doc/manual.html#setHistory) methods. +* Allow custom line separator string in [`getValue`](http://codemirror.net/doc/manual.html#getValue) and [`getRange`](http://codemirror.net/doc/manual.html#getRange). +* Support double- and triple-click drag, double-clicking whitespace. +* And more... [(all patches)](https://github.com/codemirror/CodeMirror/compare/v2.3...v2.31) + +## 2.30.0 (2012-06-22) + +* **New scrollbar implementation**. Should flicker less. Changes DOM structure of the editor. +* New theme: [vibrant-ink](http://codemirror.net/demo/theme.html#vibrant-ink). +* Many extensions to the VIM keymap (including text objects). +* Add [mode-multiplexing](http://codemirror.net/demo/multiplex.html) utility script. +* Fix bug where right-click paste works in read-only mode. +* Add a [`getScrollInfo`](http://codemirror.net/doc/manual.html#getScrollInfo) method. +* Lots of other [fixes](https://github.com/codemirror/CodeMirror/compare/v2.25...v2.3). + +## 2.25.0 (2012-05-23) + +* New mode: [Erlang](http://codemirror.net/mode/erlang/index.html). +* **Remove xmlpure mode** (use [xml.js](http://codemirror.net/mode/xml/index.html)). +* Fix line-wrapping in Opera. +* Fix X Windows middle-click paste in Chrome. +* Fix bug that broke pasting of huge documents. +* Fix backspace and tab key repeat in Opera. + +## 2.24.0 (2012-04-23) + +* **Drop support for Internet Explorer 6**. +* New modes: [Shell](http://codemirror.net/mode/shell/index.html), [Tiki wiki](http://codemirror.net/mode/tiki/index.html), [Pig Latin](http://codemirror.net/mode/pig/index.html). +* New themes: [Ambiance](http://codemirror.net/demo/theme.html#ambiance), [Blackboard](http://codemirror.net/demo/theme.html#blackboard). +* More control over drag/drop with [`dragDrop`](http://codemirror.net/doc/manual.html#option_dragDrop) and [`onDragEvent`](http://codemirror.net/doc/manual.html#option_onDragEvent) options. +* Make HTML mode a bit less pedantic. +* Add [`compoundChange`](http://codemirror.net/doc/manual.html#compoundChange) API method. +* Several fixes in undo history and line hiding. +* Remove (broken) support for `catchall` in key maps, add `nofallthrough` boolean field instead. + +## 2.23.0 (2012-03-26) + +* Change **default binding for tab**. Starting in 2.23, these bindings are default: + * Tab: Insert tab character + * Shift-tab: Reset line indentation to default + * Ctrl/Cmd-[: Reduce line indentation (old tab behaviour) + * Ctrl/Cmd-]: Increase line indentation (old shift-tab behaviour) +* New modes: [XQuery](http://codemirror.net/mode/xquery/index.html) and [VBScript](http://codemirror.net/mode/vbscript/index.html). +* Two new themes: [lesser-dark](http://codemirror.net/mode/less/index.html) and [xq-dark](http://codemirror.net/mode/xquery/index.html). +* Differentiate between background and text styles in [`setLineClass`](http://codemirror.net/doc/manual.html#setLineClass). +* Fix drag-and-drop in IE9+. +* Extend [`charCoords`](http://codemirror.net/doc/manual.html#charCoords) and [`cursorCoords`](http://codemirror.net/doc/manual.html#cursorCoords) with a `mode` argument. +* Add [`autofocus`](http://codemirror.net/doc/manual.html#option_autofocus) option. +* Add [`findMarksAt`](http://codemirror.net/doc/manual.html#findMarksAt) method. + +## 2.22.0 (2012-02-27) + +* Allow [key handlers](http://codemirror.net/doc/manual.html#keymaps) to pass up events, allow binding characters. +* Add [`autoClearEmptyLines`](http://codemirror.net/doc/manual.html#option_autoClearEmptyLines) option. +* Properly use tab stops when rendering tabs. +* Make PHP mode more robust. +* Support indentation blocks in [code folder](http://codemirror.net/doc/manual.html#addon_foldcode). +* Add a script for [highlighting instances of the selection](http://codemirror.net/doc/manual.html#addon_match-highlighter). +* New [.properties](http://codemirror.net/mode/properties/index.html) mode. +* Fix many bugs. + +## 2.21.0 (2012-01-27) + +* Added [LESS](http://codemirror.net/mode/less/index.html), [MySQL](http://codemirror.net/mode/mysql/index.html), [Go](http://codemirror.net/mode/go/index.html), and [Verilog](http://codemirror.net/mode/verilog/index.html) modes. +* Add [`smartIndent`](http://codemirror.net/doc/manual.html#option_smartIndent) option. +* Support a cursor in [`readOnly`](http://codemirror.net/doc/manual.html#option_readOnly)-mode. +* Support assigning multiple styles to a token. +* Use a new approach to drawing the selection. +* Add [`scrollTo`](http://codemirror.net/doc/manual.html#scrollTo) method. +* Allow undo/redo events to span non-adjacent lines. +* Lots and lots of bugfixes. + +## 2.20.0 (2011-12-20) + +* Slightly incompatible API changes. Read [this](http://codemirror.net/doc/upgrade_v2.2.html). +* New approach to [binding](http://codemirror.net/doc/manual.html#option_extraKeys) keys, support for [custom bindings](http://codemirror.net/doc/manual.html#option_keyMap). +* Support for overwrite (insert). +* [Custom-width](http://codemirror.net/doc/manual.html#option_tabSize) and [stylable](http://codemirror.net/demo/visibletabs.html) tabs. +* Moved more code into [add-on scripts](http://codemirror.net/doc/manual.html#addons). +* Support for sane vertical cursor movement in wrapped lines. +* More reliable handling of editing [marked text](http://codemirror.net/doc/manual.html#markText). +* Add minimal [emacs](http://codemirror.net/demo/emacs.html) and [vim](http://codemirror.net/demo/vim.html) bindings. +* Rename `coordsFromIndex` to [`posFromIndex`](http://codemirror.net/doc/manual.html#posFromIndex), add [`indexFromPos`](http://codemirror.net/doc/manual.html#indexFromPos) method. + +## 2.18.0 (2011-11-21) + +Fixes `TextMarker.clear`, which is broken in 2.17. + +## 2.17.0 (2011-11-21) + +* Add support for [line wrapping](http://codemirror.net/doc/manual.html#option_lineWrapping) and [code folding](http://codemirror.net/doc/manual.html#hideLine). +* Add [Github-style Markdown](http://codemirror.net/mode/gfm/index.html) mode. +* Add [Monokai](http://codemirror.net/theme/monokai.css) and [Rubyblue](http://codemirror.net/theme/rubyblue.css) themes. +* Add [`setBookmark`](http://codemirror.net/doc/manual.html#setBookmark) method. +* Move some of the demo code into reusable components under [`lib/util`](http://codemirror.net/addon/). +* Make screen-coord-finding code faster and more reliable. +* Fix drag-and-drop in Firefox. +* Improve support for IME. +* Speed up content rendering. +* Fix browser's built-in search in Webkit. +* Make double- and triple-click work in IE. +* Various fixes to modes. + +## 2.16.0 (2011-10-27) + +* Add [Perl](http://codemirror.net/mode/perl/index.html), [Rust](http://codemirror.net/mode/rust/index.html), [TiddlyWiki](http://codemirror.net/mode/tiddlywiki/index.html), and [Groovy](http://codemirror.net/mode/groovy/index.html) modes. +* Dragging text inside the editor now moves, rather than copies. +* Add a [`coordsFromIndex`](http://codemirror.net/doc/manual.html#coordsFromIndex) method. +* **API change**: `setValue` now no longer clears history. Use [`clearHistory`](http://codemirror.net/doc/manual.html#clearHistory) for that. +* **API change**: [`markText`](http://codemirror.net/doc/manual.html#markText) now returns an object with `clear` and `find` methods. Marked text is now more robust when edited. +* Fix editing code with tabs in Internet Explorer. + +## 2.15.0 (2011-09-26) + +Fix bug that snuck into 2.14: Clicking the character that currently has the cursor didn't re-focus the editor. + +## 2.14.0 (2011-09-26) + +* Add [Clojure](http://codemirror.net/mode/clojure/index.html), [Pascal](http://codemirror.net/mode/pascal/index.html), [NTriples](http://codemirror.net/mode/ntriples/index.html), [Jinja2](http://codemirror.net/mode/jinja2/index.html), and [Markdown](http://codemirror.net/mode/markdown/index.html) modes. +* Add [Cobalt](http://codemirror.net/theme/cobalt.css) and [Eclipse](http://codemirror.net/theme/eclipse.css) themes. +* Add a [`fixedGutter`](http://codemirror.net/doc/manual.html#option_fixedGutter) option. +* Fix bug with `setValue` breaking cursor movement. +* Make gutter updates much more efficient. +* Allow dragging of text out of the editor (on modern browsers). + +## 2.13.0 (2011-08-23) + +* Add [Ruby](http://codemirror.net/mode/ruby/index.html), [R](http://codemirror.net/mode/r/index.html), [CoffeeScript](http://codemirror.net/mode/coffeescript/index.html), and [Velocity](http://codemirror.net/mode/velocity/index.html) modes. +* Add [`getGutterElement`](http://codemirror.net/doc/manual.html#getGutterElement) to API. +* Several fixes to scrolling and positioning. +* Add [`smartHome`](http://codemirror.net/doc/manual.html#option_smartHome) option. +* Add an experimental [pure XML](http://codemirror.net/mode/xmlpure/index.html) mode. + +## 2.12.0 (2011-07-25) + +* Add a [SPARQL](http://codemirror.net/mode/sparql/index.html) mode. +* Fix bug with cursor jumping around in an unfocused editor in IE. +* Allow key and mouse events to bubble out of the editor. Ignore widget clicks. +* Solve cursor flakiness after undo/redo. +* Fix block-reindent ignoring the last few lines. +* Fix parsing of multi-line attrs in XML mode. +* Use `innerHTML` for HTML-escaping. +* Some fixes to indentation in C-like mode. +* Shrink horiz scrollbars when long lines removed. +* Fix width feedback loop bug that caused the width of an inner DIV to shrink. + +## 2.11.0 (2011-07-04) + +* Add a [Scheme mode](http://codemirror.net/mode/scheme/index.html). +* Add a `replace` method to search cursors, for cursor-preserving replacements. +* Make the [C-like mode](http://codemirror.net/mode/clike/index.html) mode more customizable. +* Update XML mode to spot mismatched tags. +* Add `getStateAfter` API and `compareState` mode API methods for finer-grained mode magic. +* Add a `getScrollerElement` API method to manipulate the scrolling DIV. +* Fix drag-and-drop for Firefox. +* Add a C# configuration for the [C-like mode](http://codemirror.net/mode/clike/index.html). +* Add [full-screen editing](http://codemirror.net/demo/fullscreen.html) and [mode-changing](http://codemirror.net/demo/changemode.html) demos. + +## 2.10.0 (2011-06-07) + +Add a [theme](http://codemirror.net/doc/manual.html#option_theme) system ([demo](http://codemirror.net/demo/theme.html)). Note that this is not backwards-compatible—you'll have to update your styles and modes! + +## 2.2.0 (2011-06-07) + +* Add a [Lua mode](http://codemirror.net/mode/lua/index.html). +* Fix reverse-searching for a regexp. +* Empty lines can no longer break highlighting. +* Rework scrolling model (the outer wrapper no longer does the scrolling). +* Solve horizontal jittering on long lines. +* Add [runmode.js](http://codemirror.net/demo/runmode.html). +* Immediately re-highlight text when typing. +* Fix problem with 'sticking' horizontal scrollbar. + +## 2.1.0 (2011-05-26) + +* Add a [Smalltalk mode](http://codemirror.net/mode/smalltalk/index.html). +* Add a [reStructuredText mode](http://codemirror.net/mode/rst/index.html). +* Add a [Python mode](http://codemirror.net/mode/python/index.html). +* Add a [PL/SQL mode](http://codemirror.net/mode/plsql/index.html). +* `coordsChar` now works +* Fix a problem where `onCursorActivity` interfered with `onChange`. +* Fix a number of scrolling and mouse-click-position glitches. +* Pass information about the changed lines to `onChange`. +* Support cmd-up/down on OS X. +* Add triple-click line selection. +* Don't handle shift when changing the selection through the API. +* Support `"nocursor"` mode for `readOnly` option. +* Add an `onHighlightComplete` option. +* Fix the context menu for Firefox. + +## 2.0.0 (2011-03-28) + +CodeMirror 2 is a complete rewrite that's faster, smaller, simpler to use, and less dependent on browser quirks. See [this](http://codemirror.net/doc/internals.html) and [this](http://groups.google.com/group/codemirror/browse_thread/thread/5a8e894024a9f580) for more information. diff --git a/modules/editor/codemirror/README.md b/modules/editor/codemirror/README.md @@ -0,0 +1,35 @@ +# CodeMirror +[![Build Status](https://travis-ci.org/codemirror/CodeMirror.svg)](https://travis-ci.org/codemirror/CodeMirror) +[![NPM version](https://img.shields.io/npm/v/codemirror.svg)](https://www.npmjs.org/package/codemirror) +[![Join the chat at https://gitter.im/codemirror/CodeMirror](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/codemirror/CodeMirror) +[Funding status: ![maintainer happiness](https://marijnhaverbeke.nl/fund/status_s.png?again)](https://marijnhaverbeke.nl/fund/) + +CodeMirror is a versatile text editor implemented in JavaScript for +the browser. It is specialized for editing code, and comes with over +100 language modes and various addons that implement more advanced +editing functionality. Every language comes with fully-featured code +and syntax highlighting to help with reading and editing complex code. + +A rich programming API and a CSS theming system are available for +customizing CodeMirror to fit your application, and extending it with +new functionality. + +You can find more information (and the +[manual](http://codemirror.net/doc/manual.html)) on the [project +page](http://codemirror.net). For questions and discussion, use the +[discussion forum](https://discuss.codemirror.net/). + +See +[CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md) +for contributing guidelines. + +The CodeMirror community aims to be welcoming to everybody. We use the +[Contributor Covenant +(1.1)](http://contributor-covenant.org/version/1/1/0/) as our code of +conduct. + +### Quickstart + +To build the project, make sure you have Node.js installed (at least version 6) +and then `npm install`. To run, just open `index.html` in your +browser (you don't need to run a webserver). Run the tests with `npm test`. diff --git a/modules/editor/codemirror/addon/comment/comment.js b/modules/editor/codemirror/addon/comment/comment.js @@ -0,0 +1,209 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var noOptions = {}; + var nonWS = /[^\s\u00a0]/; + var Pos = CodeMirror.Pos; + + function firstNonWS(str) { + var found = str.search(nonWS); + return found == -1 ? 0 : found; + } + + CodeMirror.commands.toggleComment = function(cm) { + cm.toggleComment(); + }; + + CodeMirror.defineExtension("toggleComment", function(options) { + if (!options) options = noOptions; + var cm = this; + var minLine = Infinity, ranges = this.listSelections(), mode = null; + for (var i = ranges.length - 1; i >= 0; i--) { + var from = ranges[i].from(), to = ranges[i].to(); + if (from.line >= minLine) continue; + if (to.line >= minLine) to = Pos(minLine, 0); + minLine = from.line; + if (mode == null) { + if (cm.uncomment(from, to, options)) mode = "un"; + else { cm.lineComment(from, to, options); mode = "line"; } + } else if (mode == "un") { + cm.uncomment(from, to, options); + } else { + cm.lineComment(from, to, options); + } + } + }); + + // Rough heuristic to try and detect lines that are part of multi-line string + function probablyInsideString(cm, pos, line) { + return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"\`]/.test(line) + } + + function getMode(cm, pos) { + var mode = cm.getMode() + return mode.useInnerComments === false || !mode.innerMode ? mode : cm.getModeAt(pos) + } + + CodeMirror.defineExtension("lineComment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = getMode(self, from); + var firstLine = self.getLine(from.line); + if (firstLine == null || probablyInsideString(self, from, firstLine)) return; + + var commentString = options.lineComment || mode.lineComment; + if (!commentString) { + if (options.blockCommentStart || mode.blockCommentStart) { + options.fullLines = true; + self.blockComment(from, to, options); + } + return; + } + + var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1); + var pad = options.padding == null ? " " : options.padding; + var blankLines = options.commentBlankLines || from.line == to.line; + + self.operation(function() { + if (options.indent) { + var baseString = null; + for (var i = from.line; i < end; ++i) { + var line = self.getLine(i); + var whitespace = line.slice(0, firstNonWS(line)); + if (baseString == null || baseString.length > whitespace.length) { + baseString = whitespace; + } + } + for (var i = from.line; i < end; ++i) { + var line = self.getLine(i), cut = baseString.length; + if (!blankLines && !nonWS.test(line)) continue; + if (line.slice(0, cut) != baseString) cut = firstNonWS(line); + self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut)); + } + } else { + for (var i = from.line; i < end; ++i) { + if (blankLines || nonWS.test(self.getLine(i))) + self.replaceRange(commentString + pad, Pos(i, 0)); + } + } + }); + }); + + CodeMirror.defineExtension("blockComment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = getMode(self, from); + var startString = options.blockCommentStart || mode.blockCommentStart; + var endString = options.blockCommentEnd || mode.blockCommentEnd; + if (!startString || !endString) { + if ((options.lineComment || mode.lineComment) && options.fullLines != false) + self.lineComment(from, to, options); + return; + } + if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return + + var end = Math.min(to.line, self.lastLine()); + if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end; + + var pad = options.padding == null ? " " : options.padding; + if (from.line > end) return; + + self.operation(function() { + if (options.fullLines != false) { + var lastLineHasText = nonWS.test(self.getLine(end)); + self.replaceRange(pad + endString, Pos(end)); + self.replaceRange(startString + pad, Pos(from.line, 0)); + var lead = options.blockCommentLead || mode.blockCommentLead; + if (lead != null) for (var i = from.line + 1; i <= end; ++i) + if (i != end || lastLineHasText) + self.replaceRange(lead + pad, Pos(i, 0)); + } else { + self.replaceRange(endString, to); + self.replaceRange(startString, from); + } + }); + }); + + CodeMirror.defineExtension("uncomment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = getMode(self, from); + var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end); + + // Try finding line comments + var lineString = options.lineComment || mode.lineComment, lines = []; + var pad = options.padding == null ? " " : options.padding, didSomething; + lineComment: { + if (!lineString) break lineComment; + for (var i = start; i <= end; ++i) { + var line = self.getLine(i); + var found = line.indexOf(lineString); + if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1; + if (found == -1 && nonWS.test(line)) break lineComment; + if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment; + lines.push(line); + } + self.operation(function() { + for (var i = start; i <= end; ++i) { + var line = lines[i - start]; + var pos = line.indexOf(lineString), endPos = pos + lineString.length; + if (pos < 0) continue; + if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length; + didSomething = true; + self.replaceRange("", Pos(i, pos), Pos(i, endPos)); + } + }); + if (didSomething) return true; + } + + // Try block comments + var startString = options.blockCommentStart || mode.blockCommentStart; + var endString = options.blockCommentEnd || mode.blockCommentEnd; + if (!startString || !endString) return false; + var lead = options.blockCommentLead || mode.blockCommentLead; + var startLine = self.getLine(start), open = startLine.indexOf(startString) + if (open == -1) return false + var endLine = end == start ? startLine : self.getLine(end) + var close = endLine.indexOf(endString, end == start ? open + startString.length : 0); + var insideStart = Pos(start, open + 1), insideEnd = Pos(end, close + 1) + if (close == -1 || + !/comment/.test(self.getTokenTypeAt(insideStart)) || + !/comment/.test(self.getTokenTypeAt(insideEnd)) || + self.getRange(insideStart, insideEnd, "\n").indexOf(endString) > -1) + return false; + + // Avoid killing block comments completely outside the selection. + // Positions of the last startString before the start of the selection, and the first endString after it. + var lastStart = startLine.lastIndexOf(startString, from.ch); + var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length); + if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false; + // Positions of the first endString after the end of the selection, and the last startString before it. + firstEnd = endLine.indexOf(endString, to.ch); + var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch); + lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart; + if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false; + + self.operation(function() { + self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)), + Pos(end, close + endString.length)); + var openEnd = open + startString.length; + if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length; + self.replaceRange("", Pos(start, open), Pos(start, openEnd)); + if (lead) for (var i = start + 1; i <= end; ++i) { + var line = self.getLine(i), found = line.indexOf(lead); + if (found == -1 || nonWS.test(line.slice(0, found))) continue; + var foundEnd = found + lead.length; + if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length; + self.replaceRange("", Pos(i, found), Pos(i, foundEnd)); + } + }); + return true; + }); +}); diff --git a/modules/editor/codemirror/addon/comment/comment.min.js b/modules/editor/codemirror/addon/comment/comment.min.js @@ -0,0 +1,209 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var noOptions = {}; + var nonWS = /[^\s\u00a0]/; + var Pos = CodeMirror.Pos; + + function firstNonWS(str) { + var found = str.search(nonWS); + return found == -1 ? 0 : found; + } + + CodeMirror.commands.toggleComment = function(cm) { + cm.toggleComment(); + }; + + CodeMirror.defineExtension("toggleComment", function(options) { + if (!options) options = noOptions; + var cm = this; + var minLine = Infinity, ranges = this.listSelections(), mode = null; + for (var i = ranges.length - 1; i >= 0; i--) { + var from = ranges[i].from(), to = ranges[i].to(); + if (from.line >= minLine) continue; + if (to.line >= minLine) to = Pos(minLine, 0); + minLine = from.line; + if (mode == null) { + if (cm.uncomment(from, to, options)) mode = "un"; + else { cm.lineComment(from, to, options); mode = "line"; } + } else if (mode == "un") { + cm.uncomment(from, to, options); + } else { + cm.lineComment(from, to, options); + } + } + }); + + // Rough heuristic to try and detect lines that are part of multi-line string + function probablyInsideString(cm, pos, line) { + return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"\`]/.test(line) + } + + function getMode(cm, pos) { + var mode = cm.getMode() + return mode.useInnerComments === false || !mode.innerMode ? mode : cm.getModeAt(pos) + } + + CodeMirror.defineExtension("lineComment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = getMode(self, from); + var firstLine = self.getLine(from.line); + if (firstLine == null || probablyInsideString(self, from, firstLine)) return; + + var commentString = options.lineComment || mode.lineComment; + if (!commentString) { + if (options.blockCommentStart || mode.blockCommentStart) { + options.fullLines = true; + self.blockComment(from, to, options); + } + return; + } + + var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1); + var pad = options.padding == null ? " " : options.padding; + var blankLines = options.commentBlankLines || from.line == to.line; + + self.operation(function() { + if (options.indent) { + var baseString = null; + for (var i = from.line; i < end; ++i) { + var line = self.getLine(i); + var whitespace = line.slice(0, firstNonWS(line)); + if (baseString == null || baseString.length > whitespace.length) { + baseString = whitespace; + } + } + for (var i = from.line; i < end; ++i) { + var line = self.getLine(i), cut = baseString.length; + if (!blankLines && !nonWS.test(line)) continue; + if (line.slice(0, cut) != baseString) cut = firstNonWS(line); + self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut)); + } + } else { + for (var i = from.line; i < end; ++i) { + if (blankLines || nonWS.test(self.getLine(i))) + self.replaceRange(commentString + pad, Pos(i, 0)); + } + } + }); + }); + + CodeMirror.defineExtension("blockComment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = getMode(self, from); + var startString = options.blockCommentStart || mode.blockCommentStart; + var endString = options.blockCommentEnd || mode.blockCommentEnd; + if (!startString || !endString) { + if ((options.lineComment || mode.lineComment) && options.fullLines != false) + self.lineComment(from, to, options); + return; + } + if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return + + var end = Math.min(to.line, self.lastLine()); + if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end; + + var pad = options.padding == null ? " " : options.padding; + if (from.line > end) return; + + self.operation(function() { + if (options.fullLines != false) { + var lastLineHasText = nonWS.test(self.getLine(end)); + self.replaceRange(pad + endString, Pos(end)); + self.replaceRange(startString + pad, Pos(from.line, 0)); + var lead = options.blockCommentLead || mode.blockCommentLead; + if (lead != null) for (var i = from.line + 1; i <= end; ++i) + if (i != end || lastLineHasText) + self.replaceRange(lead + pad, Pos(i, 0)); + } else { + self.replaceRange(endString, to); + self.replaceRange(startString, from); + } + }); + }); + + CodeMirror.defineExtension("uncomment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = getMode(self, from); + var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end); + + // Try finding line comments + var lineString = options.lineComment || mode.lineComment, lines = []; + var pad = options.padding == null ? " " : options.padding, didSomething; + lineComment: { + if (!lineString) break lineComment; + for (var i = start; i <= end; ++i) { + var line = self.getLine(i); + var found = line.indexOf(lineString); + if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1; + if (found == -1 && nonWS.test(line)) break lineComment; + if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment; + lines.push(line); + } + self.operation(function() { + for (var i = start; i <= end; ++i) { + var line = lines[i - start]; + var pos = line.indexOf(lineString), endPos = pos + lineString.length; + if (pos < 0) continue; + if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length; + didSomething = true; + self.replaceRange("", Pos(i, pos), Pos(i, endPos)); + } + }); + if (didSomething) return true; + } + + // Try block comments + var startString = options.blockCommentStart || mode.blockCommentStart; + var endString = options.blockCommentEnd || mode.blockCommentEnd; + if (!startString || !endString) return false; + var lead = options.blockCommentLead || mode.blockCommentLead; + var startLine = self.getLine(start), open = startLine.indexOf(startString) + if (open == -1) return false + var endLine = end == start ? startLine : self.getLine(end) + var close = endLine.indexOf(endString, end == start ? open + startString.length : 0); + var insideStart = Pos(start, open + 1), insideEnd = Pos(end, close + 1) + if (close == -1 || + !/comment/.test(self.getTokenTypeAt(insideStart)) || + !/comment/.test(self.getTokenTypeAt(insideEnd)) || + self.getRange(insideStart, insideEnd, "\n").indexOf(endString) > -1) + return false; + + // Avoid killing block comments completely outside the selection. + // Positions of the last startString before the start of the selection, and the first endString after it. + var lastStart = startLine.lastIndexOf(startString, from.ch); + var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length); + if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false; + // Positions of the first endString after the end of the selection, and the last startString before it. + firstEnd = endLine.indexOf(endString, to.ch); + var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch); + lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart; + if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false; + + self.operation(function() { + self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)), + Pos(end, close + endString.length)); + var openEnd = open + startString.length; + if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length; + self.replaceRange("", Pos(start, open), Pos(start, openEnd)); + if (lead) for (var i = start + 1; i <= end; ++i) { + var line = self.getLine(i), found = line.indexOf(lead); + if (found == -1 || nonWS.test(line.slice(0, found))) continue; + var foundEnd = found + lead.length; + if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length; + self.replaceRange("", Pos(i, found), Pos(i, foundEnd)); + } + }); + return true; + }); +}); diff --git a/modules/editor/codemirror/addon/dialog/dialog.js b/modules/editor/codemirror/addon/dialog/dialog.js @@ -0,0 +1,157 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Open simple dialogs on top of an editor. Relies on dialog.css. + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + function dialogDiv(cm, template, bottom) { + var wrap = cm.getWrapperElement(); + var dialog; + dialog = wrap.appendChild(document.createElement("div")); + if (bottom) + dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom"; + else + dialog.className = "CodeMirror-dialog CodeMirror-dialog-top"; + + if (typeof template == "string") { + dialog.innerHTML = template; + } else { // Assuming it's a detached DOM element. + dialog.appendChild(template); + } + return dialog; + } + + function closeNotification(cm, newVal) { + if (cm.state.currentNotificationClose) + cm.state.currentNotificationClose(); + cm.state.currentNotificationClose = newVal; + } + + CodeMirror.defineExtension("openDialog", function(template, callback, options) { + if (!options) options = {}; + + closeNotification(this, null); + + var dialog = dialogDiv(this, template, options.bottom); + var closed = false, me = this; + function close(newVal) { + if (typeof newVal == 'string') { + inp.value = newVal; + } else { + if (closed) return; + closed = true; + dialog.parentNode.removeChild(dialog); + me.focus(); + + if (options.onClose) options.onClose(dialog); + } + } + + var inp = dialog.getElementsByTagName("input")[0], button; + if (inp) { + inp.focus(); + + if (options.value) { + inp.value = options.value; + if (options.selectValueOnOpen !== false) { + inp.select(); + } + } + + if (options.onInput) + CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);}); + if (options.onKeyUp) + CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);}); + + CodeMirror.on(inp, "keydown", function(e) { + if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; } + if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) { + inp.blur(); + CodeMirror.e_stop(e); + close(); + } + if (e.keyCode == 13) callback(inp.value, e); + }); + + if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close); + } else if (button = dialog.getElementsByTagName("button")[0]) { + CodeMirror.on(button, "click", function() { + close(); + me.focus(); + }); + + if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close); + + button.focus(); + } + return close; + }); + + CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) { + closeNotification(this, null); + var dialog = dialogDiv(this, template, options && options.bottom); + var buttons = dialog.getElementsByTagName("button"); + var closed = false, me = this, blurring = 1; + function close() { + if (closed) return; + closed = true; + dialog.parentNode.removeChild(dialog); + me.focus(); + } + buttons[0].focus(); + for (var i = 0; i < buttons.length; ++i) { + var b = buttons[i]; + (function(callback) { + CodeMirror.on(b, "click", function(e) { + CodeMirror.e_preventDefault(e); + close(); + if (callback) callback(me); + }); + })(callbacks[i]); + CodeMirror.on(b, "blur", function() { + --blurring; + setTimeout(function() { if (blurring <= 0) close(); }, 200); + }); + CodeMirror.on(b, "focus", function() { ++blurring; }); + } + }); + + /* + * openNotification + * Opens a notification, that can be closed with an optional timer + * (default 5000ms timer) and always closes on click. + * + * If a notification is opened while another is opened, it will close the + * currently opened one and open the new one immediately. + */ + CodeMirror.defineExtension("openNotification", function(template, options) { + closeNotification(this, close); + var dialog = dialogDiv(this, template, options && options.bottom); + var closed = false, doneTimer; + var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000; + + function close() { + if (closed) return; + closed = true; + clearTimeout(doneTimer); + dialog.parentNode.removeChild(dialog); + } + + CodeMirror.on(dialog, 'click', function(e) { + CodeMirror.e_preventDefault(e); + close(); + }); + + if (duration) + doneTimer = setTimeout(close, duration); + + return close; + }); +}); diff --git a/modules/editor/codemirror/addon/display/panel.js b/modules/editor/codemirror/addon/display/panel.js @@ -0,0 +1,123 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + CodeMirror.defineExtension("addPanel", function(node, options) { + options = options || {}; + + if (!this.state.panels) initPanels(this); + + var info = this.state.panels; + var wrapper = info.wrapper; + var cmWrapper = this.getWrapperElement(); + + if (options.after instanceof Panel && !options.after.cleared) { + wrapper.insertBefore(node, options.before.node.nextSibling); + } else if (options.before instanceof Panel && !options.before.cleared) { + wrapper.insertBefore(node, options.before.node); + } else if (options.replace instanceof Panel && !options.replace.cleared) { + wrapper.insertBefore(node, options.replace.node); + options.replace.clear(); + } else if (options.position == "bottom") { + wrapper.appendChild(node); + } else if (options.position == "before-bottom") { + wrapper.insertBefore(node, cmWrapper.nextSibling); + } else if (options.position == "after-top") { + wrapper.insertBefore(node, cmWrapper); + } else { + wrapper.insertBefore(node, wrapper.firstChild); + } + + var height = (options && options.height) || node.offsetHeight; + this._setSize(null, info.heightLeft -= height); + info.panels++; + if (options.stable && isAtTop(this, node)) + this.scrollTo(null, this.getScrollInfo().top + height) + + return new Panel(this, node, options, height); + }); + + function Panel(cm, node, options, height) { + this.cm = cm; + this.node = node; + this.options = options; + this.height = height; + this.cleared = false; + } + + Panel.prototype.clear = function() { + if (this.cleared) return; + this.cleared = true; + var info = this.cm.state.panels; + this.cm._setSize(null, info.heightLeft += this.height); + if (this.options.stable && isAtTop(this.cm, this.node)) + this.cm.scrollTo(null, this.cm.getScrollInfo().top - this.height) + info.wrapper.removeChild(this.node); + if (--info.panels == 0) removePanels(this.cm); + }; + + Panel.prototype.changed = function(height) { + var newHeight = height == null ? this.node.offsetHeight : height; + var info = this.cm.state.panels; + this.cm._setSize(null, info.heightLeft -= (newHeight - this.height)); + this.height = newHeight; + }; + + function initPanels(cm) { + var wrap = cm.getWrapperElement(); + var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle; + var height = parseInt(style.height); + var info = cm.state.panels = { + setHeight: wrap.style.height, + heightLeft: height, + panels: 0, + wrapper: document.createElement("div") + }; + wrap.parentNode.insertBefore(info.wrapper, wrap); + var hasFocus = cm.hasFocus(); + info.wrapper.appendChild(wrap); + if (hasFocus) cm.focus(); + + cm._setSize = cm.setSize; + if (height != null) cm.setSize = function(width, newHeight) { + if (newHeight == null) return this._setSize(width, newHeight); + info.setHeight = newHeight; + if (typeof newHeight != "number") { + var px = /^(\d+\.?\d*)px$/.exec(newHeight); + if (px) { + newHeight = Number(px[1]); + } else { + info.wrapper.style.height = newHeight; + newHeight = info.wrapper.offsetHeight; + info.wrapper.style.height = ""; + } + } + cm._setSize(width, info.heightLeft += (newHeight - height)); + height = newHeight; + }; + } + + function removePanels(cm) { + var info = cm.state.panels; + cm.state.panels = null; + + var wrap = cm.getWrapperElement(); + info.wrapper.parentNode.replaceChild(wrap, info.wrapper); + wrap.style.height = info.setHeight; + cm.setSize = cm._setSize; + cm.setSize(); + } + + function isAtTop(cm, dom) { + for (var sibling = dom.nextSibling; sibling; sibling = sibling.nextSibling) + if (sibling == cm.getWrapperElement()) return true + return false + } +}); diff --git a/modules/editor/codemirror/addon/display/placeholder.min.js b/modules/editor/codemirror/addon/display/placeholder.min.js @@ -0,0 +1,62 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + CodeMirror.defineOption("placeholder", "", function(cm, val, old) { + var prev = old && old != CodeMirror.Init; + if (val && !prev) { + cm.on("blur", onBlur); + cm.on("change", onChange); + cm.on("swapDoc", onChange); + onChange(cm); + } else if (!val && prev) { + cm.off("blur", onBlur); + cm.off("change", onChange); + cm.off("swapDoc", onChange); + clearPlaceholder(cm); + var wrapper = cm.getWrapperElement(); + wrapper.className = wrapper.className.replace(" CodeMirror-empty", ""); + } + + if (val && !cm.hasFocus()) onBlur(cm); + }); + + function clearPlaceholder(cm) { + if (cm.state.placeholder) { + cm.state.placeholder.parentNode.removeChild(cm.state.placeholder); + cm.state.placeholder = null; + } + } + function setPlaceholder(cm) { + clearPlaceholder(cm); + var elt = cm.state.placeholder = document.createElement("pre"); + elt.style.cssText = "height: 0; overflow: visible"; + elt.className = "CodeMirror-placeholder"; + var placeHolder = cm.getOption("placeholder") + if (typeof placeHolder == "string") placeHolder = document.createTextNode(placeHolder) + elt.appendChild(placeHolder) + cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild); + } + + function onBlur(cm) { + if (isEmpty(cm)) setPlaceholder(cm); + } + function onChange(cm) { + var wrapper = cm.getWrapperElement(), empty = isEmpty(cm); + wrapper.className = wrapper.className.replace(" CodeMirror-empty", "") + (empty ? " CodeMirror-empty" : ""); + + if (empty) setPlaceholder(cm); + else clearPlaceholder(cm); + } + + function isEmpty(cm) { + return (cm.lineCount() === 1) && (cm.getLine(0) === ""); + } +}); diff --git a/modules/editor/codemirror/addon/edit/closebrackets.min.js b/modules/editor/codemirror/addon/edit/closebrackets.min.js @@ -0,0 +1,194 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + var defaults = { + pairs: "()[]{}''\"\"", + triples: "", + explode: "[]{}" + }; + + var Pos = CodeMirror.Pos; + + CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.removeKeyMap(keyMap); + cm.state.closeBrackets = null; + } + if (val) { + ensureBound(getOption(val, "pairs")) + cm.state.closeBrackets = val; + cm.addKeyMap(keyMap); + } + }); + + function getOption(conf, name) { + if (name == "pairs" && typeof conf == "string") return conf; + if (typeof conf == "object" && conf[name] != null) return conf[name]; + return defaults[name]; + } + + var keyMap = {Backspace: handleBackspace, Enter: handleEnter}; + function ensureBound(chars) { + for (var i = 0; i < chars.length; i++) { + var ch = chars.charAt(i), key = "'" + ch + "'" + if (!keyMap[key]) keyMap[key] = handler(ch) + } + } + ensureBound(defaults.pairs + "`") + + function handler(ch) { + return function(cm) { return handleChar(cm, ch); }; + } + + function getConfig(cm) { + var deflt = cm.state.closeBrackets; + if (!deflt || deflt.override) return deflt; + var mode = cm.getModeAt(cm.getCursor()); + return mode.closeBrackets || deflt; + } + + function handleBackspace(cm) { + var conf = getConfig(cm); + if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; + + var pairs = getOption(conf, "pairs"); + var ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) return CodeMirror.Pass; + var around = charsAround(cm, ranges[i].head); + if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass; + } + for (var i = ranges.length - 1; i >= 0; i--) { + var cur = ranges[i].head; + cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete"); + } + } + + function handleEnter(cm) { + var conf = getConfig(cm); + var explode = conf && getOption(conf, "explode"); + if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass; + + var ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) return CodeMirror.Pass; + var around = charsAround(cm, ranges[i].head); + if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass; + } + cm.operation(function() { + var linesep = cm.lineSeparator() || "\n"; + cm.replaceSelection(linesep + linesep, null); + cm.execCommand("goCharLeft"); + ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + var line = ranges[i].head.line; + cm.indentLine(line, null, true); + cm.indentLine(line + 1, null, true); + } + }); + } + + function contractSelection(sel) { + var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0; + return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)), + head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))}; + } + + function handleChar(cm, ch) { + var conf = getConfig(cm); + if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass; + + var pairs = getOption(conf, "pairs"); + var pos = pairs.indexOf(ch); + if (pos == -1) return CodeMirror.Pass; + var triples = getOption(conf, "triples"); + + var identical = pairs.charAt(pos + 1) == ch; + var ranges = cm.listSelections(); + var opening = pos % 2 == 0; + + var type; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i], cur = range.head, curType; + var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1)); + if (opening && !range.empty()) { + curType = "surround"; + } else if ((identical || !opening) && next == ch) { + if (identical && stringStartsAfter(cm, cur)) + curType = "both"; + else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch) + curType = "skipThree"; + else + curType = "skip"; + } else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 && + cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch && + (cur.ch <= 2 || cm.getRange(Pos(cur.line, cur.ch - 3), Pos(cur.line, cur.ch - 2)) != ch)) { + curType = "addFour"; + } else if (identical) { + var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur) + if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both"; + else return CodeMirror.Pass; + } else if (opening && (cm.getLine(cur.line).length == cur.ch || + isClosingBracket(next, pairs) || + /\s/.test(next))) { + curType = "both"; + } else { + return CodeMirror.Pass; + } + if (!type) type = curType; + else if (type != curType) return CodeMirror.Pass; + } + + var left = pos % 2 ? pairs.charAt(pos - 1) : ch; + var right = pos % 2 ? ch : pairs.charAt(pos + 1); + cm.operation(function() { + if (type == "skip") { + cm.execCommand("goCharRight"); + } else if (type == "skipThree") { + for (var i = 0; i < 3; i++) + cm.execCommand("goCharRight"); + } else if (type == "surround") { + var sels = cm.getSelections(); + for (var i = 0; i < sels.length; i++) + sels[i] = left + sels[i] + right; + cm.replaceSelections(sels, "around"); + sels = cm.listSelections().slice(); + for (var i = 0; i < sels.length; i++) + sels[i] = contractSelection(sels[i]); + cm.setSelections(sels); + } else if (type == "both") { + cm.replaceSelection(left + right, null); + cm.triggerElectric(left + right); + cm.execCommand("goCharLeft"); + } else if (type == "addFour") { + cm.replaceSelection(left + left + left + left, "before"); + cm.execCommand("goCharRight"); + } + }); + } + + function isClosingBracket(ch, pairs) { + var pos = pairs.lastIndexOf(ch); + return pos > -1 && pos % 2 == 1; + } + + function charsAround(cm, pos) { + var str = cm.getRange(Pos(pos.line, pos.ch - 1), + Pos(pos.line, pos.ch + 1)); + return str.length == 2 ? str : null; + } + + function stringStartsAfter(cm, pos) { + var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1)) + return /\bstring/.test(token.type) && token.start == pos.ch && + (pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos))) + } +}); diff --git a/modules/editor/codemirror/addon/edit/closetag.min.js b/modules/editor/codemirror/addon/edit/closetag.min.js @@ -0,0 +1,169 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +/** + * Tag-closer extension for CodeMirror. + * + * This extension adds an "autoCloseTags" option that can be set to + * either true to get the default behavior, or an object to further + * configure its behavior. + * + * These are supported options: + * + * `whenClosing` (default true) + * Whether to autoclose when the '/' of a closing tag is typed. + * `whenOpening` (default true) + * Whether to autoclose the tag when the final '>' of an opening + * tag is typed. + * `dontCloseTags` (default is empty tags for HTML, none for XML) + * An array of tag names that should not be autoclosed. + * `indentTags` (default is block tags for HTML, none for XML) + * An array of tag names that should, when opened, cause a + * blank line to be added inside the tag, and the blank line and + * closing line to be indented. + * + * See demos/closetag.html for a usage example. + */ + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../fold/xml-fold")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../fold/xml-fold"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) { + if (old != CodeMirror.Init && old) + cm.removeKeyMap("autoCloseTags"); + if (!val) return; + var map = {name: "autoCloseTags"}; + if (typeof val != "object" || val.whenClosing) + map["'/'"] = function(cm) { return autoCloseSlash(cm); }; + if (typeof val != "object" || val.whenOpening) + map["'>'"] = function(cm) { return autoCloseGT(cm); }; + cm.addKeyMap(map); + }); + + var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", + "source", "track", "wbr"]; + var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4", + "h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"]; + + function autoCloseGT(cm) { + if (cm.getOption("disableInput")) return CodeMirror.Pass; + var ranges = cm.listSelections(), replacements = []; + for (var i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) return CodeMirror.Pass; + var pos = ranges[i].head, tok = cm.getTokenAt(pos); + var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state; + if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass; + + var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html"; + var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose); + var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent); + + var tagName = state.tagName; + if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch); + var lowerTagName = tagName.toLowerCase(); + // Don't process the '>' at the end of an end-tag or self-closing tag + if (!tagName || + tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) || + tok.type == "tag" && state.type == "closeTag" || + tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName /> + dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 || + closingTagExists(cm, tagName, pos, state, true)) + return CodeMirror.Pass; + + var indent = indentTags && indexOf(indentTags, lowerTagName) > -1; + replacements[i] = {indent: indent, + text: ">" + (indent ? "\n\n" : "") + "</" + tagName + ">", + newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)}; + } + + for (var i = ranges.length - 1; i >= 0; i--) { + var info = replacements[i]; + cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert"); + var sel = cm.listSelections().slice(0); + sel[i] = {head: info.newPos, anchor: info.newPos}; + cm.setSelections(sel); + if (info.indent) { + cm.indentLine(info.newPos.line, null, true); + cm.indentLine(info.newPos.line + 1, null, true); + } + } + } + + function autoCloseCurrent(cm, typingSlash) { + var ranges = cm.listSelections(), replacements = []; + var head = typingSlash ? "/" : "</"; + for (var i = 0; i < ranges.length; i++) { + if (!ranges[i].empty()) return CodeMirror.Pass; + var pos = ranges[i].head, tok = cm.getTokenAt(pos); + var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state; + if (typingSlash && (tok.type == "string" || tok.string.charAt(0) != "<" || + tok.start != pos.ch - 1)) + return CodeMirror.Pass; + // Kludge to get around the fact that we are not in XML mode + // when completing in JS/CSS snippet in htmlmixed mode. Does not + // work for other XML embedded languages (there is no general + // way to go from a mixed mode to its current XML state). + var replacement; + if (inner.mode.name != "xml") { + if (cm.getMode().name == "htmlmixed" && inner.mode.name == "javascript") + replacement = head + "script"; + else if (cm.getMode().name == "htmlmixed" && inner.mode.name == "css") + replacement = head + "style"; + else + return CodeMirror.Pass; + } else { + if (!state.context || !state.context.tagName || + closingTagExists(cm, state.context.tagName, pos, state)) + return CodeMirror.Pass; + replacement = head + state.context.tagName; + } + if (cm.getLine(pos.line).charAt(tok.end) != ">") replacement += ">"; + replacements[i] = replacement; + } + cm.replaceSelections(replacements); + ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) + if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line) + cm.indentLine(ranges[i].head.line); + } + + function autoCloseSlash(cm) { + if (cm.getOption("disableInput")) return CodeMirror.Pass; + return autoCloseCurrent(cm, true); + } + + CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); }; + + function indexOf(collection, elt) { + if (collection.indexOf) return collection.indexOf(elt); + for (var i = 0, e = collection.length; i < e; ++i) + if (collection[i] == elt) return i; + return -1; + } + + // If xml-fold is loaded, we use its functionality to try and verify + // whether a given tag is actually unclosed. + function closingTagExists(cm, tagName, pos, state, newTag) { + if (!CodeMirror.scanForClosingTag) return false; + var end = Math.min(cm.lastLine() + 1, pos.line + 500); + var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end); + if (!nextClose || nextClose.tag != tagName) return false; + var cx = state.context; + // If the immediate wrapping context contains onCx instances of + // the same tag, a closing tag only exists if there are at least + // that many closing tags of that type following. + for (var onCx = newTag ? 1 : 0; cx && cx.tagName == tagName; cx = cx.prev) ++onCx; + pos = nextClose.to; + for (var i = 1; i < onCx; i++) { + var next = CodeMirror.scanForClosingTag(cm, pos, null, end); + if (!next || next.tag != tagName) return false; + pos = next.to; + } + return true; + } +}); diff --git a/modules/editor/codemirror/addon/edit/continuelist.min.js b/modules/editor/codemirror/addon/edit/continuelist.min.js @@ -0,0 +1,91 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var listRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/, + emptyListRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/, + unorderedListRE = /[*+-]\s/; + + CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) { + if (cm.getOption("disableInput")) return CodeMirror.Pass; + var ranges = cm.listSelections(), replacements = []; + for (var i = 0; i < ranges.length; i++) { + var pos = ranges[i].head; + var eolState = cm.getStateAfter(pos.line); + var inList = eolState.list !== false; + var inQuote = eolState.quote !== 0; + + var line = cm.getLine(pos.line), match = listRE.exec(line); + var cursorBeforeBullet = /^\s*$/.test(line.slice(0, pos.ch)); + if (!ranges[i].empty() || (!inList && !inQuote) || !match || cursorBeforeBullet) { + cm.execCommand("newlineAndIndent"); + return; + } + if (emptyListRE.test(line)) { + if (!/>\s*$/.test(line)) cm.replaceRange("", { + line: pos.line, ch: 0 + }, { + line: pos.line, ch: pos.ch + 1 + }); + replacements[i] = "\n"; + } else { + var indent = match[1], after = match[5]; + var bullet = unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0 + ? match[2].replace("x", " ") + : (parseInt(match[3], 10) + 1) + match[4]; + + replacements[i] = "\n" + indent + bullet + after; + + incrementRemainingMarkdownListNumbers(cm, pos); + } + } + + cm.replaceSelections(replacements); + }; + + // Auto-updating Markdown list numbers when a new item is added to the + // middle of a list + function incrementRemainingMarkdownListNumbers(cm, pos) { + var startLine = pos.line, lookAhead = 0, skipCount = 0; + var startItem = listRE.exec(cm.getLine(startLine)), startIndent = startItem[1]; + + do { + lookAhead += 1; + var nextLineNumber = startLine + lookAhead; + var nextLine = cm.getLine(nextLineNumber), nextItem = listRE.exec(nextLine); + + if (nextItem) { + var nextIndent = nextItem[1]; + var newNumber = (parseInt(startItem[3], 10) + lookAhead - skipCount); + var nextNumber = (parseInt(nextItem[3], 10)), itemNumber = nextNumber; + + if (startIndent === nextIndent) { + if (newNumber === nextNumber) itemNumber = nextNumber + 1; + if (newNumber > nextNumber) itemNumber = newNumber + 1; + cm.replaceRange( + nextLine.replace(listRE, nextIndent + itemNumber + nextItem[4] + nextItem[5]), + { + line: nextLineNumber, ch: 0 + }, { + line: nextLineNumber, ch: nextLine.length + }); + } else { + if (startIndent.length > nextIndent.length) return; + // This doesn't run if the next line immediatley indents, as it is + // not clear of the users intention (new indented item or same level) + if ((startIndent.length < nextIndent.length) && (lookAhead === 1)) return; + skipCount += 1; + } + } + } while (nextItem); + } +}); diff --git a/modules/editor/codemirror/addon/edit/matchbrackets.js b/modules/editor/codemirror/addon/edit/matchbrackets.js @@ -0,0 +1,140 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + var ie_lt8 = /MSIE \d/.test(navigator.userAgent) && + (document.documentMode == null || document.documentMode < 8); + + var Pos = CodeMirror.Pos; + + var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; + + function findMatchingBracket(cm, where, config) { + var line = cm.getLineHandle(where.line), pos = where.ch - 1; + var afterCursor = config && config.afterCursor + if (afterCursor == null) + afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className) + + // A cursor is defined as between two characters, but in in vim command mode + // (i.e. not insert mode), the cursor is visually represented as a + // highlighted box on top of the 2nd character. Otherwise, we allow matches + // from before or after the cursor. + var match = (!afterCursor && pos >= 0 && matching[line.text.charAt(pos)]) || + matching[line.text.charAt(++pos)]; + if (!match) return null; + var dir = match.charAt(1) == ">" ? 1 : -1; + if (config && config.strict && (dir > 0) != (pos == where.ch)) return null; + var style = cm.getTokenTypeAt(Pos(where.line, pos + 1)); + + var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config); + if (found == null) return null; + return {from: Pos(where.line, pos), to: found && found.pos, + match: found && found.ch == match.charAt(0), forward: dir > 0}; + } + + // bracketRegex is used to specify which type of bracket to scan + // should be a regexp, e.g. /[[\]]/ + // + // Note: If "where" is on an open bracket, then this bracket is ignored. + // + // Returns false when no bracket was found, null when it reached + // maxScanLines and gave up + function scanForBracket(cm, where, dir, style, config) { + var maxScanLen = (config && config.maxScanLineLength) || 10000; + var maxScanLines = (config && config.maxScanLines) || 1000; + + var stack = []; + var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/; + var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) + : Math.max(cm.firstLine() - 1, where.line - maxScanLines); + for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { + var line = cm.getLine(lineNo); + if (!line) continue; + var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1; + if (line.length > maxScanLen) continue; + if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0); + for (; pos != end; pos += dir) { + var ch = line.charAt(pos); + if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) { + var match = matching[ch]; + if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch); + else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch}; + else stack.pop(); + } + } + } + return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; + } + + function matchBrackets(cm, autoclear, config) { + // Disable brace matching in long lines, since it'll cause hugely slow updates + var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000; + var marks = [], ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config); + if (match && cm.getLine(match.from.line).length <= maxHighlightLen) { + var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; + marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style})); + if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen) + marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style})); + } + } + + if (marks.length) { + // Kludge to work around the IE bug from issue #1193, where text + // input stops going to the textare whever this fires. + if (ie_lt8 && cm.state.focused) cm.focus(); + + var clear = function() { + cm.operation(function() { + for (var i = 0; i < marks.length; i++) marks[i].clear(); + }); + }; + if (autoclear) setTimeout(clear, 800); + else return clear; + } + } + + var currentlyHighlighted = null; + function doMatchBrackets(cm) { + cm.operation(function() { + if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} + currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets); + }); + } + + CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.off("cursorActivity", doMatchBrackets); + if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} + } + if (val) { + cm.state.matchBrackets = typeof val == "object" ? val : {}; + cm.on("cursorActivity", doMatchBrackets); + } + }); + + CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); + CodeMirror.defineExtension("findMatchingBracket", function(pos, config, oldConfig){ + // Backwards-compatibility kludge + if (oldConfig || typeof config == "boolean") { + if (!oldConfig) { + config = config ? {strict: true} : null + } else { + oldConfig.strict = config + config = oldConfig + } + } + return findMatchingBracket(this, pos, config) + }); + CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){ + return scanForBracket(this, pos, dir, style, config); + }); +}); diff --git a/modules/editor/codemirror/addon/edit/matchbrackets.min.js b/modules/editor/codemirror/addon/edit/matchbrackets.min.js @@ -0,0 +1,140 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + var ie_lt8 = /MSIE \d/.test(navigator.userAgent) && + (document.documentMode == null || document.documentMode < 8); + + var Pos = CodeMirror.Pos; + + var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; + + function findMatchingBracket(cm, where, config) { + var line = cm.getLineHandle(where.line), pos = where.ch - 1; + var afterCursor = config && config.afterCursor + if (afterCursor == null) + afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className) + + // A cursor is defined as between two characters, but in in vim command mode + // (i.e. not insert mode), the cursor is visually represented as a + // highlighted box on top of the 2nd character. Otherwise, we allow matches + // from before or after the cursor. + var match = (!afterCursor && pos >= 0 && matching[line.text.charAt(pos)]) || + matching[line.text.charAt(++pos)]; + if (!match) return null; + var dir = match.charAt(1) == ">" ? 1 : -1; + if (config && config.strict && (dir > 0) != (pos == where.ch)) return null; + var style = cm.getTokenTypeAt(Pos(where.line, pos + 1)); + + var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config); + if (found == null) return null; + return {from: Pos(where.line, pos), to: found && found.pos, + match: found && found.ch == match.charAt(0), forward: dir > 0}; + } + + // bracketRegex is used to specify which type of bracket to scan + // should be a regexp, e.g. /[[\]]/ + // + // Note: If "where" is on an open bracket, then this bracket is ignored. + // + // Returns false when no bracket was found, null when it reached + // maxScanLines and gave up + function scanForBracket(cm, where, dir, style, config) { + var maxScanLen = (config && config.maxScanLineLength) || 10000; + var maxScanLines = (config && config.maxScanLines) || 1000; + + var stack = []; + var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/; + var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) + : Math.max(cm.firstLine() - 1, where.line - maxScanLines); + for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { + var line = cm.getLine(lineNo); + if (!line) continue; + var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1; + if (line.length > maxScanLen) continue; + if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0); + for (; pos != end; pos += dir) { + var ch = line.charAt(pos); + if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) { + var match = matching[ch]; + if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch); + else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch}; + else stack.pop(); + } + } + } + return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; + } + + function matchBrackets(cm, autoclear, config) { + // Disable brace matching in long lines, since it'll cause hugely slow updates + var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000; + var marks = [], ranges = cm.listSelections(); + for (var i = 0; i < ranges.length; i++) { + var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config); + if (match && cm.getLine(match.from.line).length <= maxHighlightLen) { + var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; + marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style})); + if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen) + marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style})); + } + } + + if (marks.length) { + // Kludge to work around the IE bug from issue #1193, where text + // input stops going to the textare whever this fires. + if (ie_lt8 && cm.state.focused) cm.focus(); + + var clear = function() { + cm.operation(function() { + for (var i = 0; i < marks.length; i++) marks[i].clear(); + }); + }; + if (autoclear) setTimeout(clear, 800); + else return clear; + } + } + + var currentlyHighlighted = null; + function doMatchBrackets(cm) { + cm.operation(function() { + if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} + currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets); + }); + } + + CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.off("cursorActivity", doMatchBrackets); + if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;} + } + if (val) { + cm.state.matchBrackets = typeof val == "object" ? val : {}; + cm.on("cursorActivity", doMatchBrackets); + } + }); + + CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); + CodeMirror.defineExtension("findMatchingBracket", function(pos, config, oldConfig){ + // Backwards-compatibility kludge + if (oldConfig || typeof config == "boolean") { + if (!oldConfig) { + config = config ? {strict: true} : null + } else { + oldConfig.strict = config + config = oldConfig + } + } + return findMatchingBracket(this, pos, config) + }); + CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){ + return scanForBracket(this, pos, dir, style, config); + }); +}); diff --git a/modules/editor/codemirror/addon/fold/brace-fold.js b/modules/editor/codemirror/addon/fold/brace-fold.js @@ -0,0 +1,105 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.registerHelper("fold", "brace", function(cm, start) { + var line = start.line, lineText = cm.getLine(line); + var tokenType; + + function findOpening(openCh) { + for (var at = start.ch, pass = 0;;) { + var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1); + if (found == -1) { + if (pass == 1) break; + pass = 1; + at = lineText.length; + continue; + } + if (pass == 1 && found < start.ch) break; + tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)); + if (!/^(comment|string)/.test(tokenType)) return found + 1; + at = found - 1; + } + } + + var startToken = "{", endToken = "}", startCh = findOpening("{"); + if (startCh == null) { + startToken = "[", endToken = "]"; + startCh = findOpening("["); + } + + if (startCh == null) return; + var count = 1, lastLine = cm.lastLine(), end, endCh; + outer: for (var i = line; i <= lastLine; ++i) { + var text = cm.getLine(i), pos = i == line ? startCh : 0; + for (;;) { + var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos); + if (nextOpen < 0) nextOpen = text.length; + if (nextClose < 0) nextClose = text.length; + pos = Math.min(nextOpen, nextClose); + if (pos == text.length) break; + if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) { + if (pos == nextOpen) ++count; + else if (!--count) { end = i; endCh = pos; break outer; } + } + ++pos; + } + } + if (end == null || line == end && endCh == startCh) return; + return {from: CodeMirror.Pos(line, startCh), + to: CodeMirror.Pos(end, endCh)}; +}); + +CodeMirror.registerHelper("fold", "import", function(cm, start) { + function hasImport(line) { + if (line < cm.firstLine() || line > cm.lastLine()) return null; + var start = cm.getTokenAt(CodeMirror.Pos(line, 1)); + if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1)); + if (start.type != "keyword" || start.string != "import") return null; + // Now find closing semicolon, return its position + for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) { + var text = cm.getLine(i), semi = text.indexOf(";"); + if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)}; + } + } + + var startLine = start.line, has = hasImport(startLine), prev; + if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1)) + return null; + for (var end = has.end;;) { + var next = hasImport(end.line + 1); + if (next == null) break; + end = next.end; + } + return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end}; +}); + +CodeMirror.registerHelper("fold", "include", function(cm, start) { + function hasInclude(line) { + if (line < cm.firstLine() || line > cm.lastLine()) return null; + var start = cm.getTokenAt(CodeMirror.Pos(line, 1)); + if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1)); + if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8; + } + + var startLine = start.line, has = hasInclude(startLine); + if (has == null || hasInclude(startLine - 1) != null) return null; + for (var end = startLine;;) { + var next = hasInclude(end + 1); + if (next == null) break; + ++end; + } + return {from: CodeMirror.Pos(startLine, has + 1), + to: cm.clipPos(CodeMirror.Pos(end))}; +}); + +}); diff --git a/modules/editor/codemirror/addon/fold/markdown-fold.min.js b/modules/editor/codemirror/addon/fold/markdown-fold.min.js @@ -0,0 +1,49 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.registerHelper("fold", "markdown", function(cm, start) { + var maxDepth = 100; + + function isHeader(lineNo) { + var tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0)); + return tokentype && /\bheader\b/.test(tokentype); + } + + function headerLevel(lineNo, line, nextLine) { + var match = line && line.match(/^#+/); + if (match && isHeader(lineNo)) return match[0].length; + match = nextLine && nextLine.match(/^[=\-]+\s*$/); + if (match && isHeader(lineNo + 1)) return nextLine[0] == "=" ? 1 : 2; + return maxDepth; + } + + var firstLine = cm.getLine(start.line), nextLine = cm.getLine(start.line + 1); + var level = headerLevel(start.line, firstLine, nextLine); + if (level === maxDepth) return undefined; + + var lastLineNo = cm.lastLine(); + var end = start.line, nextNextLine = cm.getLine(end + 2); + while (end < lastLineNo) { + if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break; + ++end; + nextLine = nextNextLine; + nextNextLine = cm.getLine(end + 2); + } + + return { + from: CodeMirror.Pos(start.line, firstLine.length), + to: CodeMirror.Pos(end, cm.getLine(end).length) + }; +}); + +}); diff --git a/modules/editor/codemirror/addon/hint/css-hint.js b/modules/editor/codemirror/addon/hint/css-hint.js @@ -0,0 +1,60 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../../mode/css/css")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../../mode/css/css"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var pseudoClasses = {link: 1, visited: 1, active: 1, hover: 1, focus: 1, + "first-letter": 1, "first-line": 1, "first-child": 1, + before: 1, after: 1, lang: 1}; + + CodeMirror.registerHelper("hint", "css", function(cm) { + var cur = cm.getCursor(), token = cm.getTokenAt(cur); + var inner = CodeMirror.innerMode(cm.getMode(), token.state); + if (inner.mode.name != "css") return; + + if (token.type == "keyword" && "!important".indexOf(token.string) == 0) + return {list: ["!important"], from: CodeMirror.Pos(cur.line, token.start), + to: CodeMirror.Pos(cur.line, token.end)}; + + var start = token.start, end = cur.ch, word = token.string.slice(0, end - start); + if (/[^\w$_-]/.test(word)) { + word = ""; start = end = cur.ch; + } + + var spec = CodeMirror.resolveMode("text/css"); + + var result = []; + function add(keywords) { + for (var name in keywords) + if (!word || name.lastIndexOf(word, 0) == 0) + result.push(name); + } + + var st = inner.state.state; + if (st == "pseudo" || token.type == "variable-3") { + add(pseudoClasses); + } else if (st == "block" || st == "maybeprop") { + add(spec.propertyKeywords); + } else if (st == "prop" || st == "parens" || st == "at" || st == "params") { + add(spec.valueKeywords); + add(spec.colorKeywords); + } else if (st == "media" || st == "media_parens") { + add(spec.mediaTypes); + add(spec.mediaFeatures); + } + + if (result.length) return { + list: result, + from: CodeMirror.Pos(cur.line, start), + to: CodeMirror.Pos(cur.line, end) + }; + }); +}); diff --git a/modules/editor/codemirror/addon/hint/show-hint.js b/modules/editor/codemirror/addon/hint/show-hint.js @@ -0,0 +1,432 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var HINT_ELEMENT_CLASS = "CodeMirror-hint"; + var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active"; + + // This is the old interface, kept around for now to stay + // backwards-compatible. + CodeMirror.showHint = function(cm, getHints, options) { + if (!getHints) return cm.showHint(options); + if (options && options.async) getHints.async = true; + var newOpts = {hint: getHints}; + if (options) for (var prop in options) newOpts[prop] = options[prop]; + return cm.showHint(newOpts); + }; + + CodeMirror.defineExtension("showHint", function(options) { + options = parseOptions(this, this.getCursor("start"), options); + var selections = this.listSelections() + if (selections.length > 1) return; + // By default, don't allow completion when something is selected. + // A hint function can have a `supportsSelection` property to + // indicate that it can handle selections. + if (this.somethingSelected()) { + if (!options.hint.supportsSelection) return; + // Don't try with cross-line selections + for (var i = 0; i < selections.length; i++) + if (selections[i].head.line != selections[i].anchor.line) return; + } + + if (this.state.completionActive) this.state.completionActive.close(); + var completion = this.state.completionActive = new Completion(this, options); + if (!completion.options.hint) return; + + CodeMirror.signal(this, "startCompletion", this); + completion.update(true); + }); + + function Completion(cm, options) { + this.cm = cm; + this.options = options; + this.widget = null; + this.debounce = 0; + this.tick = 0; + this.startPos = this.cm.getCursor("start"); + this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length; + + var self = this; + cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); }); + } + + var requestAnimationFrame = window.requestAnimationFrame || function(fn) { + return setTimeout(fn, 1000/60); + }; + var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; + + Completion.prototype = { + close: function() { + if (!this.active()) return; + this.cm.state.completionActive = null; + this.tick = null; + this.cm.off("cursorActivity", this.activityFunc); + + if (this.widget && this.data) CodeMirror.signal(this.data, "close"); + if (this.widget) this.widget.close(); + CodeMirror.signal(this.cm, "endCompletion", this.cm); + }, + + active: function() { + return this.cm.state.completionActive == this; + }, + + pick: function(data, i) { + var completion = data.list[i]; + if (completion.hint) completion.hint(this.cm, data, completion); + else this.cm.replaceRange(getText(completion), completion.from || data.from, + completion.to || data.to, "complete"); + CodeMirror.signal(data, "pick", completion); + this.close(); + }, + + cursorActivity: function() { + if (this.debounce) { + cancelAnimationFrame(this.debounce); + this.debounce = 0; + } + + var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line); + if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch || + pos.ch < this.startPos.ch || this.cm.somethingSelected() || + (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) { + this.close(); + } else { + var self = this; + this.debounce = requestAnimationFrame(function() {self.update();}); + if (this.widget) this.widget.disable(); + } + }, + + update: function(first) { + if (this.tick == null) return + var self = this, myTick = ++this.tick + fetchHints(this.options.hint, this.cm, this.options, function(data) { + if (self.tick == myTick) self.finishUpdate(data, first) + }) + }, + + finishUpdate: function(data, first) { + if (this.data) CodeMirror.signal(this.data, "update"); + + var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle); + if (this.widget) this.widget.close(); + + this.data = data; + + if (data && data.list.length) { + if (picked && data.list.length == 1) { + this.pick(data, 0); + } else { + this.widget = new Widget(this, data); + CodeMirror.signal(data, "shown"); + } + } + } + }; + + function parseOptions(cm, pos, options) { + var editor = cm.options.hintOptions; + var out = {}; + for (var prop in defaultOptions) out[prop] = defaultOptions[prop]; + if (editor) for (var prop in editor) + if (editor[prop] !== undefined) out[prop] = editor[prop]; + if (options) for (var prop in options) + if (options[prop] !== undefined) out[prop] = options[prop]; + if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos) + return out; + } + + function getText(completion) { + if (typeof completion == "string") return completion; + else return completion.text; + } + + function buildKeyMap(completion, handle) { + var baseMap = { + Up: function() {handle.moveFocus(-1);}, + Down: function() {handle.moveFocus(1);}, + PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);}, + PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);}, + Home: function() {handle.setFocus(0);}, + End: function() {handle.setFocus(handle.length - 1);}, + Enter: handle.pick, + Tab: handle.pick, + Esc: handle.close + }; + var custom = completion.options.customKeys; + var ourMap = custom ? {} : baseMap; + function addBinding(key, val) { + var bound; + if (typeof val != "string") + bound = function(cm) { return val(cm, handle); }; + // This mechanism is deprecated + else if (baseMap.hasOwnProperty(val)) + bound = baseMap[val]; + else + bound = val; + ourMap[key] = bound; + } + if (custom) + for (var key in custom) if (custom.hasOwnProperty(key)) + addBinding(key, custom[key]); + var extra = completion.options.extraKeys; + if (extra) + for (var key in extra) if (extra.hasOwnProperty(key)) + addBinding(key, extra[key]); + return ourMap; + } + + function getHintElement(hintsElement, el) { + while (el && el != hintsElement) { + if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el; + el = el.parentNode; + } + } + + function Widget(completion, data) { + this.completion = completion; + this.data = data; + this.picked = false; + var widget = this, cm = completion.cm; + + var hints = this.hints = document.createElement("ul"); + hints.className = "CodeMirror-hints"; + this.selectedHint = data.selectedHint || 0; + + var completions = data.list; + for (var i = 0; i < completions.length; ++i) { + var elt = hints.appendChild(document.createElement("li")), cur = completions[i]; + var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS); + if (cur.className != null) className = cur.className + " " + className; + elt.className = className; + if (cur.render) cur.render(elt, data, cur); + else elt.appendChild(document.createTextNode(cur.displayText || getText(cur))); + elt.hintId = i; + } + + var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null); + var left = pos.left, top = pos.bottom, below = true; + hints.style.left = left + "px"; + hints.style.top = top + "px"; + // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. + var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth); + var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); + (completion.options.container || document.body).appendChild(hints); + var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH; + var scrolls = hints.scrollHeight > hints.clientHeight + 1 + var startScroll = cm.getScrollInfo(); + + if (overlapY > 0) { + var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top); + if (curTop - height > 0) { // Fits above cursor + hints.style.top = (top = pos.top - height) + "px"; + below = false; + } else if (height > winH) { + hints.style.height = (winH - 5) + "px"; + hints.style.top = (top = pos.bottom - box.top) + "px"; + var cursor = cm.getCursor(); + if (data.from.ch != cursor.ch) { + pos = cm.cursorCoords(cursor); + hints.style.left = (left = pos.left) + "px"; + box = hints.getBoundingClientRect(); + } + } + } + var overlapX = box.right - winW; + if (overlapX > 0) { + if (box.right - box.left > winW) { + hints.style.width = (winW - 5) + "px"; + overlapX -= (box.right - box.left) - winW; + } + hints.style.left = (left = pos.left - overlapX) + "px"; + } + if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling) + node.style.paddingRight = cm.display.nativeBarWidth + "px" + + cm.addKeyMap(this.keyMap = buildKeyMap(completion, { + moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); }, + setFocus: function(n) { widget.changeActive(n); }, + menuSize: function() { return widget.screenAmount(); }, + length: completions.length, + close: function() { completion.close(); }, + pick: function() { widget.pick(); }, + data: data + })); + + if (completion.options.closeOnUnfocus) { + var closingOnBlur; + cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); }); + cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); }); + } + + cm.on("scroll", this.onScroll = function() { + var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect(); + var newTop = top + startScroll.top - curScroll.top; + var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop); + if (!below) point += hints.offsetHeight; + if (point <= editor.top || point >= editor.bottom) return completion.close(); + hints.style.top = newTop + "px"; + hints.style.left = (left + startScroll.left - curScroll.left) + "px"; + }); + + CodeMirror.on(hints, "dblclick", function(e) { + var t = getHintElement(hints, e.target || e.srcElement); + if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();} + }); + + CodeMirror.on(hints, "click", function(e) { + var t = getHintElement(hints, e.target || e.srcElement); + if (t && t.hintId != null) { + widget.changeActive(t.hintId); + if (completion.options.completeOnSingleClick) widget.pick(); + } + }); + + CodeMirror.on(hints, "mousedown", function() { + setTimeout(function(){cm.focus();}, 20); + }); + + CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]); + return true; + } + + Widget.prototype = { + close: function() { + if (this.completion.widget != this) return; + this.completion.widget = null; + this.hints.parentNode.removeChild(this.hints); + this.completion.cm.removeKeyMap(this.keyMap); + + var cm = this.completion.cm; + if (this.completion.options.closeOnUnfocus) { + cm.off("blur", this.onBlur); + cm.off("focus", this.onFocus); + } + cm.off("scroll", this.onScroll); + }, + + disable: function() { + this.completion.cm.removeKeyMap(this.keyMap); + var widget = this; + this.keyMap = {Enter: function() { widget.picked = true; }}; + this.completion.cm.addKeyMap(this.keyMap); + }, + + pick: function() { + this.completion.pick(this.data, this.selectedHint); + }, + + changeActive: function(i, avoidWrap) { + if (i >= this.data.list.length) + i = avoidWrap ? this.data.list.length - 1 : 0; + else if (i < 0) + i = avoidWrap ? 0 : this.data.list.length - 1; + if (this.selectedHint == i) return; + var node = this.hints.childNodes[this.selectedHint]; + node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, ""); + node = this.hints.childNodes[this.selectedHint = i]; + node.className += " " + ACTIVE_HINT_ELEMENT_CLASS; + if (node.offsetTop < this.hints.scrollTop) + this.hints.scrollTop = node.offsetTop - 3; + else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight) + this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3; + CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node); + }, + + screenAmount: function() { + return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1; + } + }; + + function applicableHelpers(cm, helpers) { + if (!cm.somethingSelected()) return helpers + var result = [] + for (var i = 0; i < helpers.length; i++) + if (helpers[i].supportsSelection) result.push(helpers[i]) + return result + } + + function fetchHints(hint, cm, options, callback) { + if (hint.async) { + hint(cm, callback, options) + } else { + var result = hint(cm, options) + if (result && result.then) result.then(callback) + else callback(result) + } + } + + function resolveAutoHints(cm, pos) { + var helpers = cm.getHelpers(pos, "hint"), words + if (helpers.length) { + var resolved = function(cm, callback, options) { + var app = applicableHelpers(cm, helpers); + function run(i) { + if (i == app.length) return callback(null) + fetchHints(app[i], cm, options, function(result) { + if (result && result.list.length > 0) callback(result) + else run(i + 1) + }) + } + run(0) + } + resolved.async = true + resolved.supportsSelection = true + return resolved + } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) { + return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) } + } else if (CodeMirror.hint.anyword) { + return function(cm, options) { return CodeMirror.hint.anyword(cm, options) } + } else { + return function() {} + } + } + + CodeMirror.registerHelper("hint", "auto", { + resolve: resolveAutoHints + }); + + CodeMirror.registerHelper("hint", "fromList", function(cm, options) { + var cur = cm.getCursor(), token = cm.getTokenAt(cur); + var to = CodeMirror.Pos(cur.line, token.end); + if (token.string && /\w/.test(token.string[token.string.length - 1])) { + var term = token.string, from = CodeMirror.Pos(cur.line, token.start); + } else { + var term = "", from = to; + } + var found = []; + for (var i = 0; i < options.words.length; i++) { + var word = options.words[i]; + if (word.slice(0, term.length) == term) + found.push(word); + } + + if (found.length) return {list: found, from: from, to: to}; + }); + + CodeMirror.commands.autocomplete = CodeMirror.showHint; + + var defaultOptions = { + hint: CodeMirror.hint.auto, + completeSingle: true, + alignWithWord: true, + closeCharacters: /[\s()\[\]{};:>,]/, + closeOnUnfocus: true, + completeOnSingleClick: true, + container: null, + customKeys: null, + extraKeys: null + }; + + CodeMirror.defineOption("hintOptions", null); +}); diff --git a/modules/editor/codemirror/addon/hint/show-hint.min.js b/modules/editor/codemirror/addon/hint/show-hint.min.js @@ -0,0 +1,432 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var HINT_ELEMENT_CLASS = "CodeMirror-hint"; + var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active"; + + // This is the old interface, kept around for now to stay + // backwards-compatible. + CodeMirror.showHint = function(cm, getHints, options) { + if (!getHints) return cm.showHint(options); + if (options && options.async) getHints.async = true; + var newOpts = {hint: getHints}; + if (options) for (var prop in options) newOpts[prop] = options[prop]; + return cm.showHint(newOpts); + }; + + CodeMirror.defineExtension("showHint", function(options) { + options = parseOptions(this, this.getCursor("start"), options); + var selections = this.listSelections() + if (selections.length > 1) return; + // By default, don't allow completion when something is selected. + // A hint function can have a `supportsSelection` property to + // indicate that it can handle selections. + if (this.somethingSelected()) { + if (!options.hint.supportsSelection) return; + // Don't try with cross-line selections + for (var i = 0; i < selections.length; i++) + if (selections[i].head.line != selections[i].anchor.line) return; + } + + if (this.state.completionActive) this.state.completionActive.close(); + var completion = this.state.completionActive = new Completion(this, options); + if (!completion.options.hint) return; + + CodeMirror.signal(this, "startCompletion", this); + completion.update(true); + }); + + function Completion(cm, options) { + this.cm = cm; + this.options = options; + this.widget = null; + this.debounce = 0; + this.tick = 0; + this.startPos = this.cm.getCursor("start"); + this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length; + + var self = this; + cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); }); + } + + var requestAnimationFrame = window.requestAnimationFrame || function(fn) { + return setTimeout(fn, 1000/60); + }; + var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; + + Completion.prototype = { + close: function() { + if (!this.active()) return; + this.cm.state.completionActive = null; + this.tick = null; + this.cm.off("cursorActivity", this.activityFunc); + + if (this.widget && this.data) CodeMirror.signal(this.data, "close"); + if (this.widget) this.widget.close(); + CodeMirror.signal(this.cm, "endCompletion", this.cm); + }, + + active: function() { + return this.cm.state.completionActive == this; + }, + + pick: function(data, i) { + var completion = data.list[i]; + if (completion.hint) completion.hint(this.cm, data, completion); + else this.cm.replaceRange(getText(completion), completion.from || data.from, + completion.to || data.to, "complete"); + CodeMirror.signal(data, "pick", completion); + this.close(); + }, + + cursorActivity: function() { + if (this.debounce) { + cancelAnimationFrame(this.debounce); + this.debounce = 0; + } + + var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line); + if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch || + pos.ch < this.startPos.ch || this.cm.somethingSelected() || + (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) { + this.close(); + } else { + var self = this; + this.debounce = requestAnimationFrame(function() {self.update();}); + if (this.widget) this.widget.disable(); + } + }, + + update: function(first) { + if (this.tick == null) return + var self = this, myTick = ++this.tick + fetchHints(this.options.hint, this.cm, this.options, function(data) { + if (self.tick == myTick) self.finishUpdate(data, first) + }) + }, + + finishUpdate: function(data, first) { + if (this.data) CodeMirror.signal(this.data, "update"); + + var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle); + if (this.widget) this.widget.close(); + + this.data = data; + + if (data && data.list.length) { + if (picked && data.list.length == 1) { + this.pick(data, 0); + } else { + this.widget = new Widget(this, data); + CodeMirror.signal(data, "shown"); + } + } + } + }; + + function parseOptions(cm, pos, options) { + var editor = cm.options.hintOptions; + var out = {}; + for (var prop in defaultOptions) out[prop] = defaultOptions[prop]; + if (editor) for (var prop in editor) + if (editor[prop] !== undefined) out[prop] = editor[prop]; + if (options) for (var prop in options) + if (options[prop] !== undefined) out[prop] = options[prop]; + if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos) + return out; + } + + function getText(completion) { + if (typeof completion == "string") return completion; + else return completion.text; + } + + function buildKeyMap(completion, handle) { + var baseMap = { + Up: function() {handle.moveFocus(-1);}, + Down: function() {handle.moveFocus(1);}, + PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);}, + PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);}, + Home: function() {handle.setFocus(0);}, + End: function() {handle.setFocus(handle.length - 1);}, + Enter: handle.pick, + Tab: handle.pick, + Esc: handle.close + }; + var custom = completion.options.customKeys; + var ourMap = custom ? {} : baseMap; + function addBinding(key, val) { + var bound; + if (typeof val != "string") + bound = function(cm) { return val(cm, handle); }; + // This mechanism is deprecated + else if (baseMap.hasOwnProperty(val)) + bound = baseMap[val]; + else + bound = val; + ourMap[key] = bound; + } + if (custom) + for (var key in custom) if (custom.hasOwnProperty(key)) + addBinding(key, custom[key]); + var extra = completion.options.extraKeys; + if (extra) + for (var key in extra) if (extra.hasOwnProperty(key)) + addBinding(key, extra[key]); + return ourMap; + } + + function getHintElement(hintsElement, el) { + while (el && el != hintsElement) { + if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el; + el = el.parentNode; + } + } + + function Widget(completion, data) { + this.completion = completion; + this.data = data; + this.picked = false; + var widget = this, cm = completion.cm; + + var hints = this.hints = document.createElement("ul"); + hints.className = "CodeMirror-hints"; + this.selectedHint = data.selectedHint || 0; + + var completions = data.list; + for (var i = 0; i < completions.length; ++i) { + var elt = hints.appendChild(document.createElement("li")), cur = completions[i]; + var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS); + if (cur.className != null) className = cur.className + " " + className; + elt.className = className; + if (cur.render) cur.render(elt, data, cur); + else elt.appendChild(document.createTextNode(cur.displayText || getText(cur))); + elt.hintId = i; + } + + var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null); + var left = pos.left, top = pos.bottom, below = true; + hints.style.left = left + "px"; + hints.style.top = top + "px"; + // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. + var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth); + var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); + (completion.options.container || document.body).appendChild(hints); + var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH; + var scrolls = hints.scrollHeight > hints.clientHeight + 1 + var startScroll = cm.getScrollInfo(); + + if (overlapY > 0) { + var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top); + if (curTop - height > 0) { // Fits above cursor + hints.style.top = (top = pos.top - height) + "px"; + below = false; + } else if (height > winH) { + hints.style.height = (winH - 5) + "px"; + hints.style.top = (top = pos.bottom - box.top) + "px"; + var cursor = cm.getCursor(); + if (data.from.ch != cursor.ch) { + pos = cm.cursorCoords(cursor); + hints.style.left = (left = pos.left) + "px"; + box = hints.getBoundingClientRect(); + } + } + } + var overlapX = box.right - winW; + if (overlapX > 0) { + if (box.right - box.left > winW) { + hints.style.width = (winW - 5) + "px"; + overlapX -= (box.right - box.left) - winW; + } + hints.style.left = (left = pos.left - overlapX) + "px"; + } + if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling) + node.style.paddingRight = cm.display.nativeBarWidth + "px" + + cm.addKeyMap(this.keyMap = buildKeyMap(completion, { + moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); }, + setFocus: function(n) { widget.changeActive(n); }, + menuSize: function() { return widget.screenAmount(); }, + length: completions.length, + close: function() { completion.close(); }, + pick: function() { widget.pick(); }, + data: data + })); + + if (completion.options.closeOnUnfocus) { + var closingOnBlur; + cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); }); + cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); }); + } + + cm.on("scroll", this.onScroll = function() { + var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect(); + var newTop = top + startScroll.top - curScroll.top; + var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop); + if (!below) point += hints.offsetHeight; + if (point <= editor.top || point >= editor.bottom) return completion.close(); + hints.style.top = newTop + "px"; + hints.style.left = (left + startScroll.left - curScroll.left) + "px"; + }); + + CodeMirror.on(hints, "dblclick", function(e) { + var t = getHintElement(hints, e.target || e.srcElement); + if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();} + }); + + CodeMirror.on(hints, "click", function(e) { + var t = getHintElement(hints, e.target || e.srcElement); + if (t && t.hintId != null) { + widget.changeActive(t.hintId); + if (completion.options.completeOnSingleClick) widget.pick(); + } + }); + + CodeMirror.on(hints, "mousedown", function() { + setTimeout(function(){cm.focus();}, 20); + }); + + CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]); + return true; + } + + Widget.prototype = { + close: function() { + if (this.completion.widget != this) return; + this.completion.widget = null; + this.hints.parentNode.removeChild(this.hints); + this.completion.cm.removeKeyMap(this.keyMap); + + var cm = this.completion.cm; + if (this.completion.options.closeOnUnfocus) { + cm.off("blur", this.onBlur); + cm.off("focus", this.onFocus); + } + cm.off("scroll", this.onScroll); + }, + + disable: function() { + this.completion.cm.removeKeyMap(this.keyMap); + var widget = this; + this.keyMap = {Enter: function() { widget.picked = true; }}; + this.completion.cm.addKeyMap(this.keyMap); + }, + + pick: function() { + this.completion.pick(this.data, this.selectedHint); + }, + + changeActive: function(i, avoidWrap) { + if (i >= this.data.list.length) + i = avoidWrap ? this.data.list.length - 1 : 0; + else if (i < 0) + i = avoidWrap ? 0 : this.data.list.length - 1; + if (this.selectedHint == i) return; + var node = this.hints.childNodes[this.selectedHint]; + node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, ""); + node = this.hints.childNodes[this.selectedHint = i]; + node.className += " " + ACTIVE_HINT_ELEMENT_CLASS; + if (node.offsetTop < this.hints.scrollTop) + this.hints.scrollTop = node.offsetTop - 3; + else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight) + this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3; + CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node); + }, + + screenAmount: function() { + return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1; + } + }; + + function applicableHelpers(cm, helpers) { + if (!cm.somethingSelected()) return helpers + var result = [] + for (var i = 0; i < helpers.length; i++) + if (helpers[i].supportsSelection) result.push(helpers[i]) + return result + } + + function fetchHints(hint, cm, options, callback) { + if (hint.async) { + hint(cm, callback, options) + } else { + var result = hint(cm, options) + if (result && result.then) result.then(callback) + else callback(result) + } + } + + function resolveAutoHints(cm, pos) { + var helpers = cm.getHelpers(pos, "hint"), words + if (helpers.length) { + var resolved = function(cm, callback, options) { + var app = applicableHelpers(cm, helpers); + function run(i) { + if (i == app.length) return callback(null) + fetchHints(app[i], cm, options, function(result) { + if (result && result.list.length > 0) callback(result) + else run(i + 1) + }) + } + run(0) + } + resolved.async = true + resolved.supportsSelection = true + return resolved + } else if (words = cm.getHelper(cm.getCursor(), "hintWords")) { + return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) } + } else if (CodeMirror.hint.anyword) { + return function(cm, options) { return CodeMirror.hint.anyword(cm, options) } + } else { + return function() {} + } + } + + CodeMirror.registerHelper("hint", "auto", { + resolve: resolveAutoHints + }); + + CodeMirror.registerHelper("hint", "fromList", function(cm, options) { + var cur = cm.getCursor(), token = cm.getTokenAt(cur); + var to = CodeMirror.Pos(cur.line, token.end); + if (token.string && /\w/.test(token.string[token.string.length - 1])) { + var term = token.string, from = CodeMirror.Pos(cur.line, token.start); + } else { + var term = "", from = to; + } + var found = []; + for (var i = 0; i < options.words.length; i++) { + var word = options.words[i]; + if (word.slice(0, term.length) == term) + found.push(word); + } + + if (found.length) return {list: found, from: from, to: to}; + }); + + CodeMirror.commands.autocomplete = CodeMirror.showHint; + + var defaultOptions = { + hint: CodeMirror.hint.auto, + completeSingle: true, + alignWithWord: true, + closeCharacters: /[\s()\[\]{};:>,]/, + closeOnUnfocus: true, + completeOnSingleClick: true, + container: null, + customKeys: null, + extraKeys: null + }; + + CodeMirror.defineOption("hintOptions", null); +}); diff --git a/modules/editor/codemirror/addon/lint/html-lint.min.js b/modules/editor/codemirror/addon/lint/html-lint.min.js @@ -0,0 +1,53 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Depends on htmlhint.js from http://htmlhint.com/js/htmlhint.js + +// declare global: HTMLHint + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("htmlhint")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "htmlhint"], mod); + else // Plain browser env + mod(CodeMirror, window.HTMLHint); +})(function(CodeMirror, HTMLHint) { + "use strict"; + + var defaultRules = { + "tagname-lowercase": true, + "attr-lowercase": true, + "attr-value-double-quotes": true, + "doctype-first": false, + "tag-pair": true, + "spec-char-escape": true, + "id-unique": true, + "src-not-empty": true, + "attr-no-duplication": true + }; + + CodeMirror.registerHelper("lint", "html", function(text, options) { + var found = []; + if (HTMLHint && !HTMLHint.verify) HTMLHint = HTMLHint.HTMLHint; + if (!HTMLHint) HTMLHint = window.HTMLHint; + if (!HTMLHint) { + if (window.console) { + window.console.error("Error: HTMLHint not found, not defined on window, or not available through define/require, CodeMirror HTML linting cannot run."); + } + return found; + } + var messages = HTMLHint.verify(text, options && options.rules || defaultRules); + for (var i = 0; i < messages.length; i++) { + var message = messages[i]; + var startLine = message.line - 1, endLine = message.line - 1, startCol = message.col - 1, endCol = message.col; + found.push({ + from: CodeMirror.Pos(startLine, startCol), + to: CodeMirror.Pos(endLine, endCol), + message: message.message, + severity : message.type + }); + } + return found; + }); +}); diff --git a/modules/editor/codemirror/addon/lint/json-lint.min.js b/modules/editor/codemirror/addon/lint/json-lint.min.js @@ -0,0 +1,37 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Depends on jsonlint.js from https://github.com/zaach/jsonlint + +// declare global: jsonlint + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.registerHelper("lint", "json", function(text) { + var found = []; + if (!window.jsonlint) { + if (window.console) { + window.console.error("Error: window.jsonlint not defined, CodeMirror JSON linting cannot run."); + } + return found; + } + jsonlint.parseError = function(str, hash) { + var loc = hash.loc; + found.push({from: CodeMirror.Pos(loc.first_line - 1, loc.first_column), + to: CodeMirror.Pos(loc.last_line - 1, loc.last_column), + message: str}); + }; + try { jsonlint.parse(text); } + catch(e) {} + return found; +}); + +}); diff --git a/modules/editor/codemirror/addon/merge/merge.min.js b/modules/editor/codemirror/addon/merge/merge.min.js @@ -0,0 +1,1001 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// declare global: diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); // Note non-packaged dependency diff_match_patch + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "diff_match_patch"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + var Pos = CodeMirror.Pos; + var svgNS = "http://www.w3.org/2000/svg"; + + function DiffView(mv, type) { + this.mv = mv; + this.type = type; + this.classes = type == "left" + ? {chunk: "CodeMirror-merge-l-chunk", + start: "CodeMirror-merge-l-chunk-start", + end: "CodeMirror-merge-l-chunk-end", + insert: "CodeMirror-merge-l-inserted", + del: "CodeMirror-merge-l-deleted", + connect: "CodeMirror-merge-l-connect"} + : {chunk: "CodeMirror-merge-r-chunk", + start: "CodeMirror-merge-r-chunk-start", + end: "CodeMirror-merge-r-chunk-end", + insert: "CodeMirror-merge-r-inserted", + del: "CodeMirror-merge-r-deleted", + connect: "CodeMirror-merge-r-connect"}; + } + + DiffView.prototype = { + constructor: DiffView, + init: function(pane, orig, options) { + this.edit = this.mv.edit; + ;(this.edit.state.diffViews || (this.edit.state.diffViews = [])).push(this); + this.orig = CodeMirror(pane, copyObj({value: orig, readOnly: !this.mv.options.allowEditingOriginals}, copyObj(options))); + if (this.mv.options.connect == "align") { + if (!this.edit.state.trackAlignable) this.edit.state.trackAlignable = new TrackAlignable(this.edit) + this.orig.state.trackAlignable = new TrackAlignable(this.orig) + } + + this.orig.state.diffViews = [this]; + var classLocation = options.chunkClassLocation || "background"; + if (Object.prototype.toString.call(classLocation) != "[object Array]") classLocation = [classLocation] + this.classes.classLocation = classLocation + + this.diff = getDiff(asString(orig), asString(options.value), this.mv.options.ignoreWhitespace); + this.chunks = getChunks(this.diff); + this.diffOutOfDate = this.dealigned = false; + this.needsScrollSync = null + + this.showDifferences = options.showDifferences !== false; + }, + registerEvents: function(otherDv) { + this.forceUpdate = registerUpdate(this); + setScrollLock(this, true, false); + registerScroll(this, otherDv); + }, + setShowDifferences: function(val) { + val = val !== false; + if (val != this.showDifferences) { + this.showDifferences = val; + this.forceUpdate("full"); + } + } + }; + + function ensureDiff(dv) { + if (dv.diffOutOfDate) { + dv.diff = getDiff(dv.orig.getValue(), dv.edit.getValue(), dv.mv.options.ignoreWhitespace); + dv.chunks = getChunks(dv.diff); + dv.diffOutOfDate = false; + CodeMirror.signal(dv.edit, "updateDiff", dv.diff); + } + } + + var updating = false; + function registerUpdate(dv) { + var edit = {from: 0, to: 0, marked: []}; + var orig = {from: 0, to: 0, marked: []}; + var debounceChange, updatingFast = false; + function update(mode) { + updating = true; + updatingFast = false; + if (mode == "full") { + if (dv.svg) clear(dv.svg); + if (dv.copyButtons) clear(dv.copyButtons); + clearMarks(dv.edit, edit.marked, dv.classes); + clearMarks(dv.orig, orig.marked, dv.classes); + edit.from = edit.to = orig.from = orig.to = 0; + } + ensureDiff(dv); + if (dv.showDifferences) { + updateMarks(dv.edit, dv.diff, edit, DIFF_INSERT, dv.classes); + updateMarks(dv.orig, dv.diff, orig, DIFF_DELETE, dv.classes); + } + + if (dv.mv.options.connect == "align") + alignChunks(dv); + makeConnections(dv); + if (dv.needsScrollSync != null) syncScroll(dv, dv.needsScrollSync) + + updating = false; + } + function setDealign(fast) { + if (updating) return; + dv.dealigned = true; + set(fast); + } + function set(fast) { + if (updating || updatingFast) return; + clearTimeout(debounceChange); + if (fast === true) updatingFast = true; + debounceChange = setTimeout(update, fast === true ? 20 : 250); + } + function change(_cm, change) { + if (!dv.diffOutOfDate) { + dv.diffOutOfDate = true; + edit.from = edit.to = orig.from = orig.to = 0; + } + // Update faster when a line was added/removed + setDealign(change.text.length - 1 != change.to.line - change.from.line); + } + function swapDoc() { + dv.diffOutOfDate = true; + dv.dealigned = true; + update("full"); + } + dv.edit.on("change", change); + dv.orig.on("change", change); + dv.edit.on("swapDoc", swapDoc); + dv.orig.on("swapDoc", swapDoc); + if (dv.mv.options.connect == "align") { + CodeMirror.on(dv.edit.state.trackAlignable, "realign", setDealign) + CodeMirror.on(dv.orig.state.trackAlignable, "realign", setDealign) + } + dv.edit.on("viewportChange", function() { set(false); }); + dv.orig.on("viewportChange", function() { set(false); }); + update(); + return update; + } + + function registerScroll(dv, otherDv) { + dv.edit.on("scroll", function() { + syncScroll(dv, true) && makeConnections(dv); + }); + dv.orig.on("scroll", function() { + syncScroll(dv, false) && makeConnections(dv); + if (otherDv) syncScroll(otherDv, true) && makeConnections(otherDv); + }); + } + + function syncScroll(dv, toOrig) { + // Change handler will do a refresh after a timeout when diff is out of date + if (dv.diffOutOfDate) { + if (dv.lockScroll && dv.needsScrollSync == null) dv.needsScrollSync = toOrig + return false + } + dv.needsScrollSync = null + if (!dv.lockScroll) return true; + var editor, other, now = +new Date; + if (toOrig) { editor = dv.edit; other = dv.orig; } + else { editor = dv.orig; other = dv.edit; } + // Don't take action if the position of this editor was recently set + // (to prevent feedback loops) + if (editor.state.scrollSetBy == dv && (editor.state.scrollSetAt || 0) + 250 > now) return false; + + var sInfo = editor.getScrollInfo(); + if (dv.mv.options.connect == "align") { + targetPos = sInfo.top; + } else { + var halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen; + var mid = editor.lineAtHeight(midY, "local"); + var around = chunkBoundariesAround(dv.chunks, mid, toOrig); + var off = getOffsets(editor, toOrig ? around.edit : around.orig); + var offOther = getOffsets(other, toOrig ? around.orig : around.edit); + var ratio = (midY - off.top) / (off.bot - off.top); + var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top); + + var botDist, mix; + // Some careful tweaking to make sure no space is left out of view + // when scrolling to top or bottom. + if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) { + targetPos = targetPos * mix + sInfo.top * (1 - mix); + } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) { + var otherInfo = other.getScrollInfo(); + var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos; + if (botDistOther > botDist && (mix = botDist / halfScreen) < 1) + targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix); + } + } + + other.scrollTo(sInfo.left, targetPos); + other.state.scrollSetAt = now; + other.state.scrollSetBy = dv; + return true; + } + + function getOffsets(editor, around) { + var bot = around.after; + if (bot == null) bot = editor.lastLine() + 1; + return {top: editor.heightAtLine(around.before || 0, "local"), + bot: editor.heightAtLine(bot, "local")}; + } + + function setScrollLock(dv, val, action) { + dv.lockScroll = val; + if (val && action != false) syncScroll(dv, DIFF_INSERT) && makeConnections(dv); + dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db&nbsp;&nbsp;\u21da"; + } + + // Updating the marks for editor content + + function removeClass(editor, line, classes) { + var locs = classes.classLocation + for (var i = 0; i < locs.length; i++) { + editor.removeLineClass(line, locs[i], classes.chunk); + editor.removeLineClass(line, locs[i], classes.start); + editor.removeLineClass(line, locs[i], classes.end); + } + } + + function clearMarks(editor, arr, classes) { + for (var i = 0; i < arr.length; ++i) { + var mark = arr[i]; + if (mark instanceof CodeMirror.TextMarker) + mark.clear(); + else if (mark.parent) + removeClass(editor, mark, classes); + } + arr.length = 0; + } + + // FIXME maybe add a margin around viewport to prevent too many updates + function updateMarks(editor, diff, state, type, classes) { + var vp = editor.getViewport(); + editor.operation(function() { + if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { + clearMarks(editor, state.marked, classes); + markChanges(editor, diff, type, state.marked, vp.from, vp.to, classes); + state.from = vp.from; state.to = vp.to; + } else { + if (vp.from < state.from) { + markChanges(editor, diff, type, state.marked, vp.from, state.from, classes); + state.from = vp.from; + } + if (vp.to > state.to) { + markChanges(editor, diff, type, state.marked, state.to, vp.to, classes); + state.to = vp.to; + } + } + }); + } + + function addClass(editor, lineNr, classes, main, start, end) { + var locs = classes.classLocation, line = editor.getLineHandle(lineNr); + for (var i = 0; i < locs.length; i++) { + if (main) editor.addLineClass(line, locs[i], classes.chunk); + if (start) editor.addLineClass(line, locs[i], classes.start); + if (end) editor.addLineClass(line, locs[i], classes.end); + } + return line; + } + + function markChanges(editor, diff, type, marks, from, to, classes) { + var pos = Pos(0, 0); + var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1)); + var cls = type == DIFF_DELETE ? classes.del : classes.insert; + function markChunk(start, end) { + var bfrom = Math.max(from, start), bto = Math.min(to, end); + for (var i = bfrom; i < bto; ++i) + marks.push(addClass(editor, i, classes, true, i == start, i == end - 1)); + // When the chunk is empty, make sure a horizontal line shows up + if (start == end && bfrom == end && bto == end) { + if (bfrom) + marks.push(addClass(editor, bfrom - 1, classes, false, false, true)); + else + marks.push(addClass(editor, bfrom, classes, false, true, false)); + } + } + + var chunkStart = 0, pending = false; + for (var i = 0; i < diff.length; ++i) { + var part = diff[i], tp = part[0], str = part[1]; + if (tp == DIFF_EQUAL) { + var cleanFrom = pos.line + (startOfLineClean(diff, i) ? 0 : 1); + moveOver(pos, str); + var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0); + if (cleanTo > cleanFrom) { + if (pending) { markChunk(chunkStart, cleanFrom); pending = false } + chunkStart = cleanTo; + } + } else { + pending = true + if (tp == type) { + var end = moveOver(pos, str, true); + var a = posMax(top, pos), b = posMin(bot, end); + if (!posEq(a, b)) + marks.push(editor.markText(a, b, {className: cls})); + pos = end; + } + } + } + if (pending) markChunk(chunkStart, pos.line + 1); + } + + // Updating the gap between editor and original + + function makeConnections(dv) { + if (!dv.showDifferences) return; + + if (dv.svg) { + clear(dv.svg); + var w = dv.gap.offsetWidth; + attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight); + } + if (dv.copyButtons) clear(dv.copyButtons); + + var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); + var outerTop = dv.mv.wrap.getBoundingClientRect().top + var sTopEdit = outerTop - dv.edit.getScrollerElement().getBoundingClientRect().top + dv.edit.getScrollInfo().top + var sTopOrig = outerTop - dv.orig.getScrollerElement().getBoundingClientRect().top + dv.orig.getScrollInfo().top; + for (var i = 0; i < dv.chunks.length; i++) { + var ch = dv.chunks[i]; + if (ch.editFrom <= vpEdit.to && ch.editTo >= vpEdit.from && + ch.origFrom <= vpOrig.to && ch.origTo >= vpOrig.from) + drawConnectorsForChunk(dv, ch, sTopOrig, sTopEdit, w); + } + } + + function getMatchingOrigLine(editLine, chunks) { + var editStart = 0, origStart = 0; + for (var i = 0; i < chunks.length; i++) { + var chunk = chunks[i]; + if (chunk.editTo > editLine && chunk.editFrom <= editLine) return null; + if (chunk.editFrom > editLine) break; + editStart = chunk.editTo; + origStart = chunk.origTo; + } + return origStart + (editLine - editStart); + } + + // Combines information about chunks and widgets/markers to return + // an array of lines, in a single editor, that probably need to be + // aligned with their counterparts in the editor next to it. + function alignableFor(cm, chunks, isOrig) { + var tracker = cm.state.trackAlignable + var start = cm.firstLine(), trackI = 0 + var result = [] + for (var i = 0;; i++) { + var chunk = chunks[i] + var chunkStart = !chunk ? 1e9 : isOrig ? chunk.origFrom : chunk.editFrom + for (; trackI < tracker.alignable.length; trackI += 2) { + var n = tracker.alignable[trackI] + 1 + if (n <= start) continue + if (n <= chunkStart) result.push(n) + else break + } + if (!chunk) break + result.push(start = isOrig ? chunk.origTo : chunk.editTo) + } + return result + } + + // Given information about alignable lines in two editors, fill in + // the result (an array of three-element arrays) to reflect the + // lines that need to be aligned with each other. + function mergeAlignable(result, origAlignable, chunks, setIndex) { + var rI = 0, origI = 0, chunkI = 0, diff = 0 + outer: for (;; rI++) { + var nextR = result[rI], nextO = origAlignable[origI] + if (!nextR && nextO == null) break + + var rLine = nextR ? nextR[0] : 1e9, oLine = nextO == null ? 1e9 : nextO + while (chunkI < chunks.length) { + var chunk = chunks[chunkI] + if (chunk.origFrom <= oLine && chunk.origTo > oLine) { + origI++ + rI-- + continue outer; + } + if (chunk.editTo > rLine) { + if (chunk.editFrom <= rLine) continue outer; + break + } + diff += (chunk.origTo - chunk.origFrom) - (chunk.editTo - chunk.editFrom) + chunkI++ + } + if (rLine == oLine - diff) { + nextR[setIndex] = oLine + origI++ + } else if (rLine < oLine - diff) { + nextR[setIndex] = rLine + diff + } else { + var record = [oLine - diff, null, null] + record[setIndex] = oLine + result.splice(rI, 0, record) + origI++ + } + } + } + + function findAlignedLines(dv, other) { + var alignable = alignableFor(dv.edit, dv.chunks, false), result = [] + if (other) for (var i = 0, j = 0; i < other.chunks.length; i++) { + var n = other.chunks[i].editTo + while (j < alignable.length && alignable[j] < n) j++ + if (j == alignable.length || alignable[j] != n) alignable.splice(j++, 0, n) + } + for (var i = 0; i < alignable.length; i++) + result.push([alignable[i], null, null]) + + mergeAlignable(result, alignableFor(dv.orig, dv.chunks, true), dv.chunks, 1) + if (other) + mergeAlignable(result, alignableFor(other.orig, other.chunks, true), other.chunks, 2) + + return result + } + + function alignChunks(dv, force) { + if (!dv.dealigned && !force) return; + if (!dv.orig.curOp) return dv.orig.operation(function() { + alignChunks(dv, force); + }); + + dv.dealigned = false; + var other = dv.mv.left == dv ? dv.mv.right : dv.mv.left; + if (other) { + ensureDiff(other); + other.dealigned = false; + } + var linesToAlign = findAlignedLines(dv, other); + + // Clear old aligners + var aligners = dv.mv.aligners; + for (var i = 0; i < aligners.length; i++) + aligners[i].clear(); + aligners.length = 0; + + var cm = [dv.edit, dv.orig], scroll = []; + if (other) cm.push(other.orig); + for (var i = 0; i < cm.length; i++) + scroll.push(cm[i].getScrollInfo().top); + + for (var ln = 0; ln < linesToAlign.length; ln++) + alignLines(cm, linesToAlign[ln], aligners); + + for (var i = 0; i < cm.length; i++) + cm[i].scrollTo(null, scroll[i]); + } + + function alignLines(cm, lines, aligners) { + var maxOffset = 0, offset = []; + for (var i = 0; i < cm.length; i++) if (lines[i] != null) { + var off = cm[i].heightAtLine(lines[i], "local"); + offset[i] = off; + maxOffset = Math.max(maxOffset, off); + } + for (var i = 0; i < cm.length; i++) if (lines[i] != null) { + var diff = maxOffset - offset[i]; + if (diff > 1) + aligners.push(padAbove(cm[i], lines[i], diff)); + } + } + + function padAbove(cm, line, size) { + var above = true; + if (line > cm.lastLine()) { + line--; + above = false; + } + var elt = document.createElement("div"); + elt.className = "CodeMirror-merge-spacer"; + elt.style.height = size + "px"; elt.style.minWidth = "1px"; + return cm.addLineWidget(line, elt, {height: size, above: above, mergeSpacer: true, handleMouseEvents: true}); + } + + function drawConnectorsForChunk(dv, chunk, sTopOrig, sTopEdit, w) { + var flip = dv.type == "left"; + var top = dv.orig.heightAtLine(chunk.origFrom, "local", true) - sTopOrig; + if (dv.svg) { + var topLpx = top; + var topRpx = dv.edit.heightAtLine(chunk.editFrom, "local", true) - sTopEdit; + if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; } + var botLpx = dv.orig.heightAtLine(chunk.origTo, "local", true) - sTopOrig; + var botRpx = dv.edit.heightAtLine(chunk.editTo, "local", true) - sTopEdit; + if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; } + var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx; + var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx; + attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")), + "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z", + "class", dv.classes.connect); + } + if (dv.copyButtons) { + var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", + "CodeMirror-merge-copy")); + var editOriginals = dv.mv.options.allowEditingOriginals; + copy.title = editOriginals ? "Push to left" : "Revert chunk"; + copy.chunk = chunk; + copy.style.top = (chunk.origTo > chunk.origFrom ? top : dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit) + "px"; + + if (editOriginals) { + var topReverse = dv.edit.heightAtLine(chunk.editFrom, "local") - sTopEdit; + var copyReverse = dv.copyButtons.appendChild(elt("div", dv.type == "right" ? "\u21dd" : "\u21dc", + "CodeMirror-merge-copy-reverse")); + copyReverse.title = "Push to right"; + copyReverse.chunk = {editFrom: chunk.origFrom, editTo: chunk.origTo, + origFrom: chunk.editFrom, origTo: chunk.editTo}; + copyReverse.style.top = topReverse + "px"; + dv.type == "right" ? copyReverse.style.left = "2px" : copyReverse.style.right = "2px"; + } + } + } + + function copyChunk(dv, to, from, chunk) { + if (dv.diffOutOfDate) return; + var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0) + var origEnd = Pos(chunk.origTo, 0) + var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0) + var editEnd = Pos(chunk.editTo, 0) + var handler = dv.mv.options.revertChunk + if (handler) + handler(dv.mv, from, origStart, origEnd, to, editStart, editEnd) + else + to.replaceRange(from.getRange(origStart, origEnd), editStart, editEnd) + } + + // Merge view, containing 0, 1, or 2 diff views. + + var MergeView = CodeMirror.MergeView = function(node, options) { + if (!(this instanceof MergeView)) return new MergeView(node, options); + + this.options = options; + var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; + + var hasLeft = origLeft != null, hasRight = origRight != null; + var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); + var wrap = [], left = this.left = null, right = this.right = null; + var self = this; + + if (hasLeft) { + left = this.left = new DiffView(this, "left"); + var leftPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-left"); + wrap.push(leftPane); + wrap.push(buildGap(left)); + } + + var editPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-editor"); + wrap.push(editPane); + + if (hasRight) { + right = this.right = new DiffView(this, "right"); + wrap.push(buildGap(right)); + var rightPane = elt("div", null, "CodeMirror-merge-pane CodeMirror-merge-right"); + wrap.push(rightPane); + } + + (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost"; + + wrap.push(elt("div", null, null, "height: 0; clear: both;")); + + var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane")); + this.edit = CodeMirror(editPane, copyObj(options)); + + if (left) left.init(leftPane, origLeft, options); + if (right) right.init(rightPane, origRight, options); + if (options.collapseIdentical) + this.editor().operation(function() { + collapseIdenticalStretches(self, options.collapseIdentical); + }); + if (options.connect == "align") { + this.aligners = []; + alignChunks(this.left || this.right, true); + } + if (left) left.registerEvents(right) + if (right) right.registerEvents(left) + + + var onResize = function() { + if (left) makeConnections(left); + if (right) makeConnections(right); + }; + CodeMirror.on(window, "resize", onResize); + var resizeInterval = setInterval(function() { + for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {} + if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); } + }, 5000); + }; + + function buildGap(dv) { + var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock"); + lock.title = "Toggle locked scrolling"; + var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap"); + CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); }); + var gapElts = [lockWrap]; + if (dv.mv.options.revertButtons !== false) { + dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); + CodeMirror.on(dv.copyButtons, "click", function(e) { + var node = e.target || e.srcElement; + if (!node.chunk) return; + if (node.className == "CodeMirror-merge-copy-reverse") { + copyChunk(dv, dv.orig, dv.edit, node.chunk); + return; + } + copyChunk(dv, dv.edit, dv.orig, node.chunk); + }); + gapElts.unshift(dv.copyButtons); + } + if (dv.mv.options.connect != "align") { + var svg = document.createElementNS && document.createElementNS(svgNS, "svg"); + if (svg && !svg.createSVGRect) svg = null; + dv.svg = svg; + if (svg) gapElts.push(svg); + } + + return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap"); + } + + MergeView.prototype = { + constructor: MergeView, + editor: function() { return this.edit; }, + rightOriginal: function() { return this.right && this.right.orig; }, + leftOriginal: function() { return this.left && this.left.orig; }, + setShowDifferences: function(val) { + if (this.right) this.right.setShowDifferences(val); + if (this.left) this.left.setShowDifferences(val); + }, + rightChunks: function() { + if (this.right) { ensureDiff(this.right); return this.right.chunks; } + }, + leftChunks: function() { + if (this.left) { ensureDiff(this.left); return this.left.chunks; } + } + }; + + function asString(obj) { + if (typeof obj == "string") return obj; + else return obj.getValue(); + } + + // Operations on diffs + var dmp; + function getDiff(a, b, ignoreWhitespace) { + if (!dmp) dmp = new diff_match_patch(); + + var diff = dmp.diff_main(a, b); + // The library sometimes leaves in empty parts, which confuse the algorithm + for (var i = 0; i < diff.length; ++i) { + var part = diff[i]; + if (ignoreWhitespace ? !/[^ \t]/.test(part[1]) : !part[1]) { + diff.splice(i--, 1); + } else if (i && diff[i - 1][0] == part[0]) { + diff.splice(i--, 1); + diff[i][1] += part[1]; + } + } + return diff; + } + + function getChunks(diff) { + var chunks = []; + var startEdit = 0, startOrig = 0; + var edit = Pos(0, 0), orig = Pos(0, 0); + for (var i = 0; i < diff.length; ++i) { + var part = diff[i], tp = part[0]; + if (tp == DIFF_EQUAL) { + var startOff = !startOfLineClean(diff, i) || edit.line < startEdit || orig.line < startOrig ? 1 : 0; + var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff; + moveOver(edit, part[1], null, orig); + var endOff = endOfLineClean(diff, i) ? 1 : 0; + var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff; + if (cleanToEdit > cleanFromEdit) { + if (i) chunks.push({origFrom: startOrig, origTo: cleanFromOrig, + editFrom: startEdit, editTo: cleanFromEdit}); + startEdit = cleanToEdit; startOrig = cleanToOrig; + } + } else { + moveOver(tp == DIFF_INSERT ? edit : orig, part[1]); + } + } + if (startEdit <= edit.line || startOrig <= orig.line) + chunks.push({origFrom: startOrig, origTo: orig.line + 1, + editFrom: startEdit, editTo: edit.line + 1}); + return chunks; + } + + function endOfLineClean(diff, i) { + if (i == diff.length - 1) return true; + var next = diff[i + 1][1]; + if ((next.length == 1 && i < diff.length - 2) || next.charCodeAt(0) != 10) return false; + if (i == diff.length - 2) return true; + next = diff[i + 2][1]; + return (next.length > 1 || i == diff.length - 3) && next.charCodeAt(0) == 10; + } + + function startOfLineClean(diff, i) { + if (i == 0) return true; + var last = diff[i - 1][1]; + if (last.charCodeAt(last.length - 1) != 10) return false; + if (i == 1) return true; + last = diff[i - 2][1]; + return last.charCodeAt(last.length - 1) == 10; + } + + function chunkBoundariesAround(chunks, n, nInEdit) { + var beforeE, afterE, beforeO, afterO; + for (var i = 0; i < chunks.length; i++) { + var chunk = chunks[i]; + var fromLocal = nInEdit ? chunk.editFrom : chunk.origFrom; + var toLocal = nInEdit ? chunk.editTo : chunk.origTo; + if (afterE == null) { + if (fromLocal > n) { afterE = chunk.editFrom; afterO = chunk.origFrom; } + else if (toLocal > n) { afterE = chunk.editTo; afterO = chunk.origTo; } + } + if (toLocal <= n) { beforeE = chunk.editTo; beforeO = chunk.origTo; } + else if (fromLocal <= n) { beforeE = chunk.editFrom; beforeO = chunk.origFrom; } + } + return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}}; + } + + function collapseSingle(cm, from, to) { + cm.addLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); + var widget = document.createElement("span"); + widget.className = "CodeMirror-merge-collapsed-widget"; + widget.title = "Identical text collapsed. Click to expand."; + var mark = cm.markText(Pos(from, 0), Pos(to - 1), { + inclusiveLeft: true, + inclusiveRight: true, + replacedWith: widget, + clearOnEnter: true + }); + function clear() { + mark.clear(); + cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line"); + } + if (mark.explicitlyCleared) clear(); + CodeMirror.on(widget, "click", clear); + mark.on("clear", clear); + CodeMirror.on(widget, "click", clear); + return {mark: mark, clear: clear}; + } + + function collapseStretch(size, editors) { + var marks = []; + function clear() { + for (var i = 0; i < marks.length; i++) marks[i].clear(); + } + for (var i = 0; i < editors.length; i++) { + var editor = editors[i]; + var mark = collapseSingle(editor.cm, editor.line, editor.line + size); + marks.push(mark); + mark.mark.on("clear", clear); + } + return marks[0].mark; + } + + function unclearNearChunks(dv, margin, off, clear) { + for (var i = 0; i < dv.chunks.length; i++) { + var chunk = dv.chunks[i]; + for (var l = chunk.editFrom - margin; l < chunk.editTo + margin; l++) { + var pos = l + off; + if (pos >= 0 && pos < clear.length) clear[pos] = false; + } + } + } + + function collapseIdenticalStretches(mv, margin) { + if (typeof margin != "number") margin = 2; + var clear = [], edit = mv.editor(), off = edit.firstLine(); + for (var l = off, e = edit.lastLine(); l <= e; l++) clear.push(true); + if (mv.left) unclearNearChunks(mv.left, margin, off, clear); + if (mv.right) unclearNearChunks(mv.right, margin, off, clear); + + for (var i = 0; i < clear.length; i++) { + if (clear[i]) { + var line = i + off; + for (var size = 1; i < clear.length - 1 && clear[i + 1]; i++, size++) {} + if (size > margin) { + var editors = [{line: line, cm: edit}]; + if (mv.left) editors.push({line: getMatchingOrigLine(line, mv.left.chunks), cm: mv.left.orig}); + if (mv.right) editors.push({line: getMatchingOrigLine(line, mv.right.chunks), cm: mv.right.orig}); + var mark = collapseStretch(size, editors); + if (mv.options.onCollapse) mv.options.onCollapse(mv, line, size, mark); + } + } + } + } + + // General utilities + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") e.appendChild(document.createTextNode(content)); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + + function clear(node) { + for (var count = node.childNodes.length; count > 0; --count) + node.removeChild(node.firstChild); + } + + function attrs(elt) { + for (var i = 1; i < arguments.length; i += 2) + elt.setAttribute(arguments[i], arguments[i+1]); + } + + function copyObj(obj, target) { + if (!target) target = {}; + for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; + return target; + } + + function moveOver(pos, str, copy, other) { + var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0; + for (;;) { + var nl = str.indexOf("\n", at); + if (nl == -1) break; + ++out.line; + if (other) ++other.line; + at = nl + 1; + } + out.ch = (at ? 0 : out.ch) + (str.length - at); + if (other) other.ch = (at ? 0 : other.ch) + (str.length - at); + return out; + } + + // Tracks collapsed markers and line widgets, in order to be able to + // accurately align the content of two editors. + + var F_WIDGET = 1, F_WIDGET_BELOW = 2, F_MARKER = 4 + + function TrackAlignable(cm) { + this.cm = cm + this.alignable = [] + this.height = cm.doc.height + var self = this + cm.on("markerAdded", function(_, marker) { + if (!marker.collapsed) return + var found = marker.find(1) + if (found != null) self.set(found.line, F_MARKER) + }) + cm.on("markerCleared", function(_, marker, _min, max) { + if (max != null && marker.collapsed) + self.check(max, F_MARKER, self.hasMarker) + }) + cm.on("markerChanged", this.signal.bind(this)) + cm.on("lineWidgetAdded", function(_, widget, lineNo) { + if (widget.mergeSpacer) return + if (widget.above) self.set(lineNo - 1, F_WIDGET_BELOW) + else self.set(lineNo, F_WIDGET) + }) + cm.on("lineWidgetCleared", function(_, widget, lineNo) { + if (widget.mergeSpacer) return + if (widget.above) self.check(lineNo - 1, F_WIDGET_BELOW, self.hasWidgetBelow) + else self.check(lineNo, F_WIDGET, self.hasWidget) + }) + cm.on("lineWidgetChanged", this.signal.bind(this)) + cm.on("change", function(_, change) { + var start = change.from.line, nBefore = change.to.line - change.from.line + var nAfter = change.text.length - 1, end = start + nAfter + if (nBefore || nAfter) self.map(start, nBefore, nAfter) + self.check(end, F_MARKER, self.hasMarker) + if (nBefore || nAfter) self.check(change.from.line, F_MARKER, self.hasMarker) + }) + cm.on("viewportChange", function() { + if (self.cm.doc.height != self.height) self.signal() + }) + } + + TrackAlignable.prototype = { + signal: function() { + CodeMirror.signal(this, "realign") + this.height = this.cm.doc.height + }, + + set: function(n, flags) { + var pos = -1 + for (; pos < this.alignable.length; pos += 2) { + var diff = this.alignable[pos] - n + if (diff == 0) { + if ((this.alignable[pos + 1] & flags) == flags) return + this.alignable[pos + 1] |= flags + this.signal() + return + } + if (diff > 0) break + } + this.signal() + this.alignable.splice(pos, 0, n, flags) + }, + + find: function(n) { + for (var i = 0; i < this.alignable.length; i += 2) + if (this.alignable[i] == n) return i + return -1 + }, + + check: function(n, flag, pred) { + var found = this.find(n) + if (found == -1 || !(this.alignable[found + 1] & flag)) return + if (!pred.call(this, n)) { + this.signal() + var flags = this.alignable[found + 1] & ~flag + if (flags) this.alignable[found + 1] = flags + else this.alignable.splice(found, 2) + } + }, + + hasMarker: function(n) { + var handle = this.cm.getLineHandle(n) + if (handle.markedSpans) for (var i = 0; i < handle.markedSpans.length; i++) + if (handle.markedSpans[i].mark.collapsed && handle.markedSpans[i].to != null) + return true + return false + }, + + hasWidget: function(n) { + var handle = this.cm.getLineHandle(n) + if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++) + if (!handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true + return false + }, + + hasWidgetBelow: function(n) { + if (n == this.cm.lastLine()) return false + var handle = this.cm.getLineHandle(n + 1) + if (handle.widgets) for (var i = 0; i < handle.widgets.length; i++) + if (handle.widgets[i].above && !handle.widgets[i].mergeSpacer) return true + return false + }, + + map: function(from, nBefore, nAfter) { + var diff = nAfter - nBefore, to = from + nBefore, widgetFrom = -1, widgetTo = -1 + for (var i = 0; i < this.alignable.length; i += 2) { + var n = this.alignable[i] + if (n == from && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetFrom = i + if (n == to && (this.alignable[i + 1] & F_WIDGET_BELOW)) widgetTo = i + if (n <= from) continue + else if (n < to) this.alignable.splice(i--, 2) + else this.alignable[i] += diff + } + if (widgetFrom > -1) { + var flags = this.alignable[widgetFrom + 1] + if (flags == F_WIDGET_BELOW) this.alignable.splice(widgetFrom, 2) + else this.alignable[widgetFrom + 1] = flags & ~F_WIDGET_BELOW + } + if (widgetTo > -1 && nAfter) + this.set(from + nAfter, F_WIDGET_BELOW) + } + } + + function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; } + function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; } + function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } + + function findPrevDiff(chunks, start, isOrig) { + for (var i = chunks.length - 1; i >= 0; i--) { + var chunk = chunks[i]; + var to = (isOrig ? chunk.origTo : chunk.editTo) - 1; + if (to < start) return to; + } + } + + function findNextDiff(chunks, start, isOrig) { + for (var i = 0; i < chunks.length; i++) { + var chunk = chunks[i]; + var from = (isOrig ? chunk.origFrom : chunk.editFrom); + if (from > start) return from; + } + } + + function goNearbyDiff(cm, dir) { + var found = null, views = cm.state.diffViews, line = cm.getCursor().line; + if (views) for (var i = 0; i < views.length; i++) { + var dv = views[i], isOrig = cm == dv.orig; + ensureDiff(dv); + var pos = dir < 0 ? findPrevDiff(dv.chunks, line, isOrig) : findNextDiff(dv.chunks, line, isOrig); + if (pos != null && (found == null || (dir < 0 ? pos > found : pos < found))) + found = pos; + } + if (found != null) + cm.setCursor(found, 0); + else + return CodeMirror.Pass; + } + + CodeMirror.commands.goNextDiff = function(cm) { + return goNearbyDiff(cm, 1); + }; + CodeMirror.commands.goPrevDiff = function(cm) { + return goNearbyDiff(cm, -1); + }; +}); diff --git a/modules/editor/codemirror/addon/mode/multiplex_test.min.js b/modules/editor/codemirror/addon/mode/multiplex_test.min.js @@ -0,0 +1,33 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + CodeMirror.defineMode("markdown_with_stex", function(){ + var inner = CodeMirror.getMode({}, "stex"); + var outer = CodeMirror.getMode({}, "markdown"); + + var innerOptions = { + open: '$', + close: '$', + mode: inner, + delimStyle: 'delim', + innerStyle: 'inner' + }; + + return CodeMirror.multiplexingMode(outer, innerOptions); + }); + + var mode = CodeMirror.getMode({}, "markdown_with_stex"); + + function MT(name) { + test.mode( + name, + mode, + Array.prototype.slice.call(arguments, 1), + 'multiplexing'); + } + + MT( + "stexInsideMarkdown", + "[strong **Equation:**] [delim&delim-open $][inner&tag \\pi][delim&delim-close $]"); +})(); diff --git a/modules/editor/codemirror/addon/mode/simple.js b/modules/editor/codemirror/addon/mode/simple.js @@ -0,0 +1,216 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineSimpleMode = function(name, states) { + CodeMirror.defineMode(name, function(config) { + return CodeMirror.simpleMode(config, states); + }); + }; + + CodeMirror.simpleMode = function(config, states) { + ensureState(states, "start"); + var states_ = {}, meta = states.meta || {}, hasIndentation = false; + for (var state in states) if (state != meta && states.hasOwnProperty(state)) { + var list = states_[state] = [], orig = states[state]; + for (var i = 0; i < orig.length; i++) { + var data = orig[i]; + list.push(new Rule(data, states)); + if (data.indent || data.dedent) hasIndentation = true; + } + } + var mode = { + startState: function() { + return {state: "start", pending: null, + local: null, localState: null, + indent: hasIndentation ? [] : null}; + }, + copyState: function(state) { + var s = {state: state.state, pending: state.pending, + local: state.local, localState: null, + indent: state.indent && state.indent.slice(0)}; + if (state.localState) + s.localState = CodeMirror.copyState(state.local.mode, state.localState); + if (state.stack) + s.stack = state.stack.slice(0); + for (var pers = state.persistentStates; pers; pers = pers.next) + s.persistentStates = {mode: pers.mode, + spec: pers.spec, + state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state), + next: s.persistentStates}; + return s; + }, + token: tokenFunction(states_, config), + innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; }, + indent: indentFunction(states_, meta) + }; + if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop)) + mode[prop] = meta[prop]; + return mode; + }; + + function ensureState(states, name) { + if (!states.hasOwnProperty(name)) + throw new Error("Undefined state " + name + " in simple mode"); + } + + function toRegex(val, caret) { + if (!val) return /(?:)/; + var flags = ""; + if (val instanceof RegExp) { + if (val.ignoreCase) flags = "i"; + val = val.source; + } else { + val = String(val); + } + return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags); + } + + function asToken(val) { + if (!val) return null; + if (val.apply) return val + if (typeof val == "string") return val.replace(/\./g, " "); + var result = []; + for (var i = 0; i < val.length; i++) + result.push(val[i] && val[i].replace(/\./g, " ")); + return result; + } + + function Rule(data, states) { + if (data.next || data.push) ensureState(states, data.next || data.push); + this.regex = toRegex(data.regex); + this.token = asToken(data.token); + this.data = data; + } + + function tokenFunction(states, config) { + return function(stream, state) { + if (state.pending) { + var pend = state.pending.shift(); + if (state.pending.length == 0) state.pending = null; + stream.pos += pend.text.length; + return pend.token; + } + + if (state.local) { + if (state.local.end && stream.match(state.local.end)) { + var tok = state.local.endToken || null; + state.local = state.localState = null; + return tok; + } else { + var tok = state.local.mode.token(stream, state.localState), m; + if (state.local.endScan && (m = state.local.endScan.exec(stream.current()))) + stream.pos = stream.start + m.index; + return tok; + } + } + + var curState = states[state.state]; + for (var i = 0; i < curState.length; i++) { + var rule = curState[i]; + var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex); + if (matches) { + if (rule.data.next) { + state.state = rule.data.next; + } else if (rule.data.push) { + (state.stack || (state.stack = [])).push(state.state); + state.state = rule.data.push; + } else if (rule.data.pop && state.stack && state.stack.length) { + state.state = state.stack.pop(); + } + + if (rule.data.mode) + enterLocalMode(config, state, rule.data.mode, rule.token); + if (rule.data.indent) + state.indent.push(stream.indentation() + config.indentUnit); + if (rule.data.dedent) + state.indent.pop(); + var token = rule.token + if (token && token.apply) token = token(matches) + if (matches.length > 2 && rule.token && typeof rule.token != "string") { + state.pending = []; + for (var j = 2; j < matches.length; j++) + if (matches[j]) + state.pending.push({text: matches[j], token: rule.token[j - 1]}); + stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0)); + return token[0]; + } else if (token && token.join) { + return token[0]; + } else { + return token; + } + } + } + stream.next(); + return null; + }; + } + + function cmp(a, b) { + if (a === b) return true; + if (!a || typeof a != "object" || !b || typeof b != "object") return false; + var props = 0; + for (var prop in a) if (a.hasOwnProperty(prop)) { + if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false; + props++; + } + for (var prop in b) if (b.hasOwnProperty(prop)) props--; + return props == 0; + } + + function enterLocalMode(config, state, spec, token) { + var pers; + if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next) + if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p; + var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec); + var lState = pers ? pers.state : CodeMirror.startState(mode); + if (spec.persistent && !pers) + state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates}; + + state.localState = lState; + state.local = {mode: mode, + end: spec.end && toRegex(spec.end), + endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false), + endToken: token && token.join ? token[token.length - 1] : token}; + } + + function indexOf(val, arr) { + for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true; + } + + function indentFunction(states, meta) { + return function(state, textAfter, line) { + if (state.local && state.local.mode.indent) + return state.local.mode.indent(state.localState, textAfter, line); + if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1) + return CodeMirror.Pass; + + var pos = state.indent.length - 1, rules = states[state.state]; + scan: for (;;) { + for (var i = 0; i < rules.length; i++) { + var rule = rules[i]; + if (rule.data.dedent && rule.data.dedentIfLineStart !== false) { + var m = rule.regex.exec(textAfter); + if (m && m[0]) { + pos--; + if (rule.next || rule.push) rules = states[rule.next || rule.push]; + textAfter = textAfter.slice(m[0].length); + continue scan; + } + } + } + break; + } + return pos < 0 ? 0 : state.indent[pos]; + }; + } +}); diff --git a/modules/editor/codemirror/addon/runmode/colorize.min.js b/modules/editor/codemirror/addon/runmode/colorize.min.js @@ -0,0 +1,40 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("./runmode")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "./runmode"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + var isBlock = /^(p|li|div|h\\d|pre|blockquote|td)$/; + + function textContent(node, out) { + if (node.nodeType == 3) return out.push(node.nodeValue); + for (var ch = node.firstChild; ch; ch = ch.nextSibling) { + textContent(ch, out); + if (isBlock.test(node.nodeType)) out.push("\n"); + } + } + + CodeMirror.colorize = function(collection, defaultMode) { + if (!collection) collection = document.body.getElementsByTagName("pre"); + + for (var i = 0; i < collection.length; ++i) { + var node = collection[i]; + var mode = node.getAttribute("data-lang") || defaultMode; + if (!mode) continue; + + var text = []; + textContent(node, text); + node.innerHTML = ""; + CodeMirror.runMode(text.join(""), mode, node); + + node.className += " cm-s-default"; + } + }; +}); diff --git a/modules/editor/codemirror/addon/runmode/runmode.node.js b/modules/editor/codemirror/addon/runmode/runmode.node.js @@ -0,0 +1,197 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +/* Just enough of CodeMirror to run runMode under node.js */ + +function splitLines(string){return string.split(/\r\n?|\n/);}; + +// Counts the column offset in a string, taking tabs into account. +// Used mostly to find indentation. +var countColumn = exports.countColumn = function(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;;) { + var nextTab = string.indexOf("\t", i); + if (nextTab < 0 || nextTab >= end) + return n + (end - i); + n += nextTab - i; + n += tabSize - (n % tabSize); + i = nextTab + 1; + } +}; + +function StringStream(string, tabSize, context) { + this.pos = this.start = 0; + this.string = string; + this.tabSize = tabSize || 8; + this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; + this.context = context +}; + +StringStream.prototype = { + eol: function() {return this.pos >= this.string.length;}, + sol: function() {return this.pos == this.lineStart;}, + 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 - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + indentation: function() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + match: function(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length; + return true; + } + } else { + var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; + if (match && consume !== false) this.pos += match[0].length; + return match; + } + }, + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + }, + lookAhead: function(n) { + var line = this.context.line + n + return line >= this.context.lines.length ? null : this.context.lines[line] + } +}; +exports.StringStream = StringStream; + +exports.startState = function(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true; +}; + +var modes = exports.modes = {}, mimeModes = exports.mimeModes = {}; +exports.defineMode = function(name, mode) { + if (arguments.length > 2) + mode.dependencies = Array.prototype.slice.call(arguments, 2); + modes[name] = mode; +}; +exports.defineMIME = function(mime, spec) { mimeModes[mime] = spec; }; + +exports.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; +}); +exports.defineMIME("text/plain", "null"); + +exports.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + spec = mimeModes[spec.name]; + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; +}; + +function copyObj(obj, target, overwrite) { + if (!target) target = {}; + for (var prop in obj) + if (obj.hasOwnProperty(prop) && (overwrite !== false || !target.hasOwnProperty(prop))) + target[prop] = obj[prop]; + return target; +} + +// This can be used to attach properties to mode objects from +// outside the actual mode definition. +var modeExtensions = exports.modeExtensions = {}; +exports.extendMode = function(mode, properties) { + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}); + copyObj(properties, exts); +}; + +exports.getMode = function(options, spec) { + var spec = exports.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return exports.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; + if (spec.helperType) modeObj.helperType = spec.helperType; + if (spec.modeProps) for (var prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop]; + + return modeObj; +}; + +exports.innerMode = function(mode, state) { + var info; + while (mode.innerMode) { + info = mode.innerMode(state); + if (!info || info.mode == mode) break; + state = info.state; + mode = info.mode; + } + return info || {mode: mode, state: state}; +} + +exports.registerHelper = exports.registerGlobalHelper = Math.min; + +exports.runMode = function(string, modespec, callback, options) { + var mode = exports.getMode({indentUnit: 2}, modespec); + var lines = splitLines(string), state = (options && options.state) || exports.startState(mode); + var context = {lines: lines, line: 0} + for (var i = 0, e = lines.length; i < e; ++i, ++context.line) { + if (i) callback("\n"); + var stream = new exports.StringStream(lines[i], 4, context); + if (!stream.string && mode.blankLine) mode.blankLine(state); + while (!stream.eol()) { + var style = mode.token(stream, state); + callback(stream.current(), style, i, stream.start, state); + stream.start = stream.pos; + } + } +}; + +require.cache[require.resolve("../../lib/codemirror")] = require.cache[require.resolve("./runmode.node")]; +require.cache[require.resolve("../../addon/runmode/runmode")] = require.cache[require.resolve("./runmode.node")]; diff --git a/modules/editor/codemirror/addon/scroll/scrollpastend.min.js b/modules/editor/codemirror/addon/scroll/scrollpastend.min.js @@ -0,0 +1,48 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineOption("scrollPastEnd", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.off("change", onChange); + cm.off("refresh", updateBottomMargin); + cm.display.lineSpace.parentNode.style.paddingBottom = ""; + cm.state.scrollPastEndPadding = null; + } + if (val) { + cm.on("change", onChange); + cm.on("refresh", updateBottomMargin); + updateBottomMargin(cm); + } + }); + + function onChange(cm, change) { + if (CodeMirror.changeEnd(change).line == cm.lastLine()) + updateBottomMargin(cm); + } + + function updateBottomMargin(cm) { + var padding = ""; + if (cm.lineCount() > 1) { + var totalH = cm.display.scroller.clientHeight - 30, + lastLineH = cm.getLineHandle(cm.lastLine()).height; + padding = (totalH - lastLineH) + "px"; + } + if (cm.state.scrollPastEndPadding != padding) { + cm.state.scrollPastEndPadding = padding; + cm.display.lineSpace.parentNode.style.paddingBottom = padding; + cm.off("refresh", updateBottomMargin); + cm.setSize(); + cm.on("refresh", updateBottomMargin); + } + } +}); diff --git a/modules/editor/codemirror/addon/search/searchcursor.js b/modules/editor/codemirror/addon/search/searchcursor.js @@ -0,0 +1,289 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")) + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod) + else // Plain browser env + mod(CodeMirror) +})(function(CodeMirror) { + "use strict" + var Pos = CodeMirror.Pos + + function regexpFlags(regexp) { + var flags = regexp.flags + return flags != null ? flags : (regexp.ignoreCase ? "i" : "") + + (regexp.global ? "g" : "") + + (regexp.multiline ? "m" : "") + } + + function ensureGlobal(regexp) { + return regexp.global ? regexp : new RegExp(regexp.source, regexpFlags(regexp) + "g") + } + + function maybeMultiline(regexp) { + return /\\s|\\n|\n|\\W|\\D|\[\^/.test(regexp.source) + } + + function searchRegexpForward(doc, regexp, start) { + regexp = ensureGlobal(regexp) + for (var line = start.line, ch = start.ch, last = doc.lastLine(); line <= last; line++, ch = 0) { + regexp.lastIndex = ch + var string = doc.getLine(line), match = regexp.exec(string) + if (match) + return {from: Pos(line, match.index), + to: Pos(line, match.index + match[0].length), + match: match} + } + } + + function searchRegexpForwardMultiline(doc, regexp, start) { + if (!maybeMultiline(regexp)) return searchRegexpForward(doc, regexp, start) + + regexp = ensureGlobal(regexp) + var string, chunk = 1 + for (var line = start.line, last = doc.lastLine(); line <= last;) { + // This grows the search buffer in exponentially-sized chunks + // between matches, so that nearby matches are fast and don't + // require concatenating the whole document (in case we're + // searching for something that has tons of matches), but at the + // same time, the amount of retries is limited. + for (var i = 0; i < chunk; i++) { + var curLine = doc.getLine(line++) + string = string == null ? curLine : string + "\n" + curLine + } + chunk = chunk * 2 + regexp.lastIndex = start.ch + var match = regexp.exec(string) + if (match) { + var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n") + var startLine = start.line + before.length - 1, startCh = before[before.length - 1].length + return {from: Pos(startLine, startCh), + to: Pos(startLine + inside.length - 1, + inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length), + match: match} + } + } + } + + function lastMatchIn(string, regexp) { + var cutOff = 0, match + for (;;) { + regexp.lastIndex = cutOff + var newMatch = regexp.exec(string) + if (!newMatch) return match + match = newMatch + cutOff = match.index + (match[0].length || 1) + if (cutOff == string.length) return match + } + } + + function searchRegexpBackward(doc, regexp, start) { + regexp = ensureGlobal(regexp) + for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) { + var string = doc.getLine(line) + if (ch > -1) string = string.slice(0, ch) + var match = lastMatchIn(string, regexp) + if (match) + return {from: Pos(line, match.index), + to: Pos(line, match.index + match[0].length), + match: match} + } + } + + function searchRegexpBackwardMultiline(doc, regexp, start) { + regexp = ensureGlobal(regexp) + var string, chunk = 1 + for (var line = start.line, first = doc.firstLine(); line >= first;) { + for (var i = 0; i < chunk; i++) { + var curLine = doc.getLine(line--) + string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string + } + chunk *= 2 + + var match = lastMatchIn(string, regexp) + if (match) { + var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n") + var startLine = line + before.length, startCh = before[before.length - 1].length + return {from: Pos(startLine, startCh), + to: Pos(startLine + inside.length - 1, + inside.length == 1 ? startCh + inside[0].length : inside[inside.length - 1].length), + match: match} + } + } + } + + var doFold, noFold + if (String.prototype.normalize) { + doFold = function(str) { return str.normalize("NFD").toLowerCase() } + noFold = function(str) { return str.normalize("NFD") } + } else { + doFold = function(str) { return str.toLowerCase() } + noFold = function(str) { return str } + } + + // Maps a position in a case-folded line back to a position in the original line + // (compensating for codepoints increasing in number during folding) + function adjustPos(orig, folded, pos, foldFunc) { + if (orig.length == folded.length) return pos + for (var min = 0, max = pos + Math.max(0, orig.length - folded.length);;) { + if (min == max) return min + var mid = (min + max) >> 1 + var len = foldFunc(orig.slice(0, mid)).length + if (len == pos) return mid + else if (len > pos) max = mid + else min = mid + 1 + } + } + + function searchStringForward(doc, query, start, caseFold) { + // Empty string would match anything and never progress, so we + // define it to match nothing instead. + if (!query.length) return null + var fold = caseFold ? doFold : noFold + var lines = fold(query).split(/\r|\n\r?/) + + search: for (var line = start.line, ch = start.ch, last = doc.lastLine() + 1 - lines.length; line <= last; line++, ch = 0) { + var orig = doc.getLine(line).slice(ch), string = fold(orig) + if (lines.length == 1) { + var found = string.indexOf(lines[0]) + if (found == -1) continue search + var start = adjustPos(orig, string, found, fold) + ch + return {from: Pos(line, adjustPos(orig, string, found, fold) + ch), + to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold) + ch)} + } else { + var cutFrom = string.length - lines[0].length + if (string.slice(cutFrom) != lines[0]) continue search + for (var i = 1; i < lines.length - 1; i++) + if (fold(doc.getLine(line + i)) != lines[i]) continue search + var end = doc.getLine(line + lines.length - 1), endString = fold(end), lastLine = lines[lines.length - 1] + if (endString.slice(0, lastLine.length) != lastLine) continue search + return {from: Pos(line, adjustPos(orig, string, cutFrom, fold) + ch), + to: Pos(line + lines.length - 1, adjustPos(end, endString, lastLine.length, fold))} + } + } + } + + function searchStringBackward(doc, query, start, caseFold) { + if (!query.length) return null + var fold = caseFold ? doFold : noFold + var lines = fold(query).split(/\r|\n\r?/) + + search: for (var line = start.line, ch = start.ch, first = doc.firstLine() - 1 + lines.length; line >= first; line--, ch = -1) { + var orig = doc.getLine(line) + if (ch > -1) orig = orig.slice(0, ch) + var string = fold(orig) + if (lines.length == 1) { + var found = string.lastIndexOf(lines[0]) + if (found == -1) continue search + return {from: Pos(line, adjustPos(orig, string, found, fold)), + to: Pos(line, adjustPos(orig, string, found + lines[0].length, fold))} + } else { + var lastLine = lines[lines.length - 1] + if (string.slice(0, lastLine.length) != lastLine) continue search + for (var i = 1, start = line - lines.length + 1; i < lines.length - 1; i++) + if (fold(doc.getLine(start + i)) != lines[i]) continue search + var top = doc.getLine(line + 1 - lines.length), topString = fold(top) + if (topString.slice(topString.length - lines[0].length) != lines[0]) continue search + return {from: Pos(line + 1 - lines.length, adjustPos(top, topString, top.length - lines[0].length, fold)), + to: Pos(line, adjustPos(orig, string, lastLine.length, fold))} + } + } + } + + function SearchCursor(doc, query, pos, options) { + this.atOccurrence = false + this.doc = doc + pos = pos ? doc.clipPos(pos) : Pos(0, 0) + this.pos = {from: pos, to: pos} + + var caseFold + if (typeof options == "object") { + caseFold = options.caseFold + } else { // Backwards compat for when caseFold was the 4th argument + caseFold = options + options = null + } + + if (typeof query == "string") { + if (caseFold == null) caseFold = false + this.matches = function(reverse, pos) { + return (reverse ? searchStringBackward : searchStringForward)(doc, query, pos, caseFold) + } + } else { + query = ensureGlobal(query) + if (!options || options.multiline !== false) + this.matches = function(reverse, pos) { + return (reverse ? searchRegexpBackwardMultiline : searchRegexpForwardMultiline)(doc, query, pos) + } + else + this.matches = function(reverse, pos) { + return (reverse ? searchRegexpBackward : searchRegexpForward)(doc, query, pos) + } + } + } + + SearchCursor.prototype = { + findNext: function() {return this.find(false)}, + findPrevious: function() {return this.find(true)}, + + find: function(reverse) { + var result = this.matches(reverse, this.doc.clipPos(reverse ? this.pos.from : this.pos.to)) + + // Implements weird auto-growing behavior on null-matches for + // backwards-compatiblity with the vim code (unfortunately) + while (result && CodeMirror.cmpPos(result.from, result.to) == 0) { + if (reverse) { + if (result.from.ch) result.from = Pos(result.from.line, result.from.ch - 1) + else if (result.from.line == this.doc.firstLine()) result = null + else result = this.matches(reverse, this.doc.clipPos(Pos(result.from.line - 1))) + } else { + if (result.to.ch < this.doc.getLine(result.to.line).length) result.to = Pos(result.to.line, result.to.ch + 1) + else if (result.to.line == this.doc.lastLine()) result = null + else result = this.matches(reverse, Pos(result.to.line + 1, 0)) + } + } + + if (result) { + this.pos = result + this.atOccurrence = true + return this.pos.match || true + } else { + var end = Pos(reverse ? this.doc.firstLine() : this.doc.lastLine() + 1, 0) + this.pos = {from: end, to: end} + return this.atOccurrence = false + } + }, + + from: function() {if (this.atOccurrence) return this.pos.from}, + to: function() {if (this.atOccurrence) return this.pos.to}, + + replace: function(newText, origin) { + if (!this.atOccurrence) return + var lines = CodeMirror.splitLines(newText) + this.doc.replaceRange(lines, this.pos.from, this.pos.to, origin) + this.pos.to = Pos(this.pos.from.line + lines.length - 1, + lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)) + } + } + + CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { + return new SearchCursor(this.doc, query, pos, caseFold) + }) + CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { + return new SearchCursor(this, query, pos, caseFold) + }) + + CodeMirror.defineExtension("selectMatches", function(query, caseFold) { + var ranges = [] + var cur = this.getSearchCursor(query, this.getCursor("from"), caseFold) + while (cur.findNext()) { + if (CodeMirror.cmpPos(cur.to(), this.getCursor("to")) > 0) break + ranges.push({anchor: cur.from(), head: cur.to()}) + } + if (ranges.length) + this.setSelections(ranges, 0) + }) +}); diff --git a/modules/editor/codemirror/addon/selection/active-line.js b/modules/editor/codemirror/addon/selection/active-line.js @@ -0,0 +1,72 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + var WRAP_CLASS = "CodeMirror-activeline"; + var BACK_CLASS = "CodeMirror-activeline-background"; + var GUTT_CLASS = "CodeMirror-activeline-gutter"; + + CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) { + var prev = old == CodeMirror.Init ? false : old; + if (val == prev) return + if (prev) { + cm.off("beforeSelectionChange", selectionChange); + clearActiveLines(cm); + delete cm.state.activeLines; + } + if (val) { + cm.state.activeLines = []; + updateActiveLines(cm, cm.listSelections()); + cm.on("beforeSelectionChange", selectionChange); + } + }); + + function clearActiveLines(cm) { + for (var i = 0; i < cm.state.activeLines.length; i++) { + cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS); + cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS); + cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS); + } + } + + function sameArray(a, b) { + if (a.length != b.length) return false; + for (var i = 0; i < a.length; i++) + if (a[i] != b[i]) return false; + return true; + } + + function updateActiveLines(cm, ranges) { + var active = []; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + var option = cm.getOption("styleActiveLine"); + if (typeof option == "object" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty()) + continue + var line = cm.getLineHandleVisualStart(range.head.line); + if (active[active.length - 1] != line) active.push(line); + } + if (sameArray(cm.state.activeLines, active)) return; + cm.operation(function() { + clearActiveLines(cm); + for (var i = 0; i < active.length; i++) { + cm.addLineClass(active[i], "wrap", WRAP_CLASS); + cm.addLineClass(active[i], "background", BACK_CLASS); + cm.addLineClass(active[i], "gutter", GUTT_CLASS); + } + cm.state.activeLines = active; + }); + } + + function selectionChange(cm, sel) { + updateActiveLines(cm, sel.ranges); + } +}); diff --git a/modules/editor/codemirror/addon/selection/selection-pointer.js b/modules/editor/codemirror/addon/selection/selection-pointer.js @@ -0,0 +1,98 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineOption("selectionPointer", false, function(cm, val) { + var data = cm.state.selectionPointer; + if (data) { + CodeMirror.off(cm.getWrapperElement(), "mousemove", data.mousemove); + CodeMirror.off(cm.getWrapperElement(), "mouseout", data.mouseout); + CodeMirror.off(window, "scroll", data.windowScroll); + cm.off("cursorActivity", reset); + cm.off("scroll", reset); + cm.state.selectionPointer = null; + cm.display.lineDiv.style.cursor = ""; + } + if (val) { + data = cm.state.selectionPointer = { + value: typeof val == "string" ? val : "default", + mousemove: function(event) { mousemove(cm, event); }, + mouseout: function(event) { mouseout(cm, event); }, + windowScroll: function() { reset(cm); }, + rects: null, + mouseX: null, mouseY: null, + willUpdate: false + }; + CodeMirror.on(cm.getWrapperElement(), "mousemove", data.mousemove); + CodeMirror.on(cm.getWrapperElement(), "mouseout", data.mouseout); + CodeMirror.on(window, "scroll", data.windowScroll); + cm.on("cursorActivity", reset); + cm.on("scroll", reset); + } + }); + + function mousemove(cm, event) { + var data = cm.state.selectionPointer; + if (event.buttons == null ? event.which : event.buttons) { + data.mouseX = data.mouseY = null; + } else { + data.mouseX = event.clientX; + data.mouseY = event.clientY; + } + scheduleUpdate(cm); + } + + function mouseout(cm, event) { + if (!cm.getWrapperElement().contains(event.relatedTarget)) { + var data = cm.state.selectionPointer; + data.mouseX = data.mouseY = null; + scheduleUpdate(cm); + } + } + + function reset(cm) { + cm.state.selectionPointer.rects = null; + scheduleUpdate(cm); + } + + function scheduleUpdate(cm) { + if (!cm.state.selectionPointer.willUpdate) { + cm.state.selectionPointer.willUpdate = true; + setTimeout(function() { + update(cm); + cm.state.selectionPointer.willUpdate = false; + }, 50); + } + } + + function update(cm) { + var data = cm.state.selectionPointer; + if (!data) return; + if (data.rects == null && data.mouseX != null) { + data.rects = []; + if (cm.somethingSelected()) { + for (var sel = cm.display.selectionDiv.firstChild; sel; sel = sel.nextSibling) + data.rects.push(sel.getBoundingClientRect()); + } + } + var inside = false; + if (data.mouseX != null) for (var i = 0; i < data.rects.length; i++) { + var rect = data.rects[i]; + if (rect.left <= data.mouseX && rect.right >= data.mouseX && + rect.top <= data.mouseY && rect.bottom >= data.mouseY) + inside = true; + } + var cursor = inside ? data.value : ""; + if (cm.display.lineDiv.style.cursor != cursor) + cm.display.lineDiv.style.cursor = cursor; + } +}); diff --git a/modules/editor/codemirror/addon/tern/tern.min.js b/modules/editor/codemirror/addon/tern/tern.min.js @@ -0,0 +1,717 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Glue code between CodeMirror and Tern. +// +// Create a CodeMirror.TernServer to wrap an actual Tern server, +// register open documents (CodeMirror.Doc instances) with it, and +// call its methods to activate the assisting functions that Tern +// provides. +// +// Options supported (all optional): +// * defs: An array of JSON definition data structures. +// * plugins: An object mapping plugin names to configuration +// options. +// * getFile: A function(name, c) that can be used to access files in +// the project that haven't been loaded yet. Simply do c(null) to +// indicate that a file is not available. +// * fileFilter: A function(value, docName, doc) that will be applied +// to documents before passing them on to Tern. +// * switchToDoc: A function(name, doc) that should, when providing a +// multi-file view, switch the view or focus to the named file. +// * showError: A function(editor, message) that can be used to +// override the way errors are displayed. +// * completionTip: Customize the content in tooltips for completions. +// Is passed a single argument—the completion's data as returned by +// Tern—and may return a string, DOM node, or null to indicate that +// no tip should be shown. By default the docstring is shown. +// * typeTip: Like completionTip, but for the tooltips shown for type +// queries. +// * responseFilter: A function(doc, query, request, error, data) that +// will be applied to the Tern responses before treating them +// +// +// It is possible to run the Tern server in a web worker by specifying +// these additional options: +// * useWorker: Set to true to enable web worker mode. You'll probably +// want to feature detect the actual value you use here, for example +// !!window.Worker. +// * workerScript: The main script of the worker. Point this to +// wherever you are hosting worker.js from this directory. +// * workerDeps: An array of paths pointing (relative to workerScript) +// to the Acorn and Tern libraries and any Tern plugins you want to +// load. Or, if you minified those into a single script and included +// them in the workerScript, simply leave this undefined. + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + // declare global: tern + + CodeMirror.TernServer = function(options) { + var self = this; + this.options = options || {}; + var plugins = this.options.plugins || (this.options.plugins = {}); + if (!plugins.doc_comment) plugins.doc_comment = true; + this.docs = Object.create(null); + if (this.options.useWorker) { + this.server = new WorkerServer(this); + } else { + this.server = new tern.Server({ + getFile: function(name, c) { return getFile(self, name, c); }, + async: true, + defs: this.options.defs || [], + plugins: plugins + }); + } + this.trackChange = function(doc, change) { trackChange(self, doc, change); }; + + this.cachedArgHints = null; + this.activeArgHints = null; + this.jumpStack = []; + + this.getHint = function(cm, c) { return hint(self, cm, c); }; + this.getHint.async = true; + }; + + CodeMirror.TernServer.prototype = { + addDoc: function(name, doc) { + var data = {doc: doc, name: name, changed: null}; + this.server.addFile(name, docValue(this, data)); + CodeMirror.on(doc, "change", this.trackChange); + return this.docs[name] = data; + }, + + delDoc: function(id) { + var found = resolveDoc(this, id); + if (!found) return; + CodeMirror.off(found.doc, "change", this.trackChange); + delete this.docs[found.name]; + this.server.delFile(found.name); + }, + + hideDoc: function(id) { + closeArgHints(this); + var found = resolveDoc(this, id); + if (found && found.changed) sendDoc(this, found); + }, + + complete: function(cm) { + cm.showHint({hint: this.getHint}); + }, + + showType: function(cm, pos, c) { showContextInfo(this, cm, pos, "type", c); }, + + showDocs: function(cm, pos, c) { showContextInfo(this, cm, pos, "documentation", c); }, + + updateArgHints: function(cm) { updateArgHints(this, cm); }, + + jumpToDef: function(cm) { jumpToDef(this, cm); }, + + jumpBack: function(cm) { jumpBack(this, cm); }, + + rename: function(cm) { rename(this, cm); }, + + selectName: function(cm) { selectName(this, cm); }, + + request: function (cm, query, c, pos) { + var self = this; + var doc = findDoc(this, cm.getDoc()); + var request = buildRequest(this, doc, query, pos); + var extraOptions = request.query && this.options.queryOptions && this.options.queryOptions[request.query.type] + if (extraOptions) for (var prop in extraOptions) request.query[prop] = extraOptions[prop]; + + this.server.request(request, function (error, data) { + if (!error && self.options.responseFilter) + data = self.options.responseFilter(doc, query, request, error, data); + c(error, data); + }); + }, + + destroy: function () { + closeArgHints(this) + if (this.worker) { + this.worker.terminate(); + this.worker = null; + } + } + }; + + var Pos = CodeMirror.Pos; + var cls = "CodeMirror-Tern-"; + var bigDoc = 250; + + function getFile(ts, name, c) { + var buf = ts.docs[name]; + if (buf) + c(docValue(ts, buf)); + else if (ts.options.getFile) + ts.options.getFile(name, c); + else + c(null); + } + + function findDoc(ts, doc, name) { + for (var n in ts.docs) { + var cur = ts.docs[n]; + if (cur.doc == doc) return cur; + } + if (!name) for (var i = 0;; ++i) { + n = "[doc" + (i || "") + "]"; + if (!ts.docs[n]) { name = n; break; } + } + return ts.addDoc(name, doc); + } + + function resolveDoc(ts, id) { + if (typeof id == "string") return ts.docs[id]; + if (id instanceof CodeMirror) id = id.getDoc(); + if (id instanceof CodeMirror.Doc) return findDoc(ts, id); + } + + function trackChange(ts, doc, change) { + var data = findDoc(ts, doc); + + var argHints = ts.cachedArgHints; + if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) >= 0) + ts.cachedArgHints = null; + + var changed = data.changed; + if (changed == null) + data.changed = changed = {from: change.from.line, to: change.from.line}; + var end = change.from.line + (change.text.length - 1); + if (change.from.line < changed.to) changed.to = changed.to - (change.to.line - end); + if (end >= changed.to) changed.to = end + 1; + if (changed.from > change.from.line) changed.from = change.from.line; + + if (doc.lineCount() > bigDoc && change.to - changed.from > 100) setTimeout(function() { + if (data.changed && data.changed.to - data.changed.from > 100) sendDoc(ts, data); + }, 200); + } + + function sendDoc(ts, doc) { + ts.server.request({files: [{type: "full", name: doc.name, text: docValue(ts, doc)}]}, function(error) { + if (error) window.console.error(error); + else doc.changed = null; + }); + } + + // Completion + + function hint(ts, cm, c) { + ts.request(cm, {type: "completions", types: true, docs: true, urls: true}, function(error, data) { + if (error) return showError(ts, cm, error); + var completions = [], after = ""; + var from = data.start, to = data.end; + if (cm.getRange(Pos(from.line, from.ch - 2), from) == "[\"" && + cm.getRange(to, Pos(to.line, to.ch + 2)) != "\"]") + after = "\"]"; + + for (var i = 0; i < data.completions.length; ++i) { + var completion = data.completions[i], className = typeToIcon(completion.type); + if (data.guess) className += " " + cls + "guess"; + completions.push({text: completion.name + after, + displayText: completion.displayName || completion.name, + className: className, + data: completion}); + } + + var obj = {from: from, to: to, list: completions}; + var tooltip = null; + CodeMirror.on(obj, "close", function() { remove(tooltip); }); + CodeMirror.on(obj, "update", function() { remove(tooltip); }); + CodeMirror.on(obj, "select", function(cur, node) { + remove(tooltip); + var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc; + if (content) { + tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset, + node.getBoundingClientRect().top + window.pageYOffset, content); + tooltip.className += " " + cls + "hint-doc"; + } + }); + c(obj); + }); + } + + function typeToIcon(type) { + var suffix; + if (type == "?") suffix = "unknown"; + else if (type == "number" || type == "string" || type == "bool") suffix = type; + else if (/^fn\(/.test(type)) suffix = "fn"; + else if (/^\[/.test(type)) suffix = "array"; + else suffix = "object"; + return cls + "completion " + cls + "completion-" + suffix; + } + + // Type queries + + function showContextInfo(ts, cm, pos, queryName, c) { + ts.request(cm, queryName, function(error, data) { + if (error) return showError(ts, cm, error); + if (ts.options.typeTip) { + var tip = ts.options.typeTip(data); + } else { + var tip = elt("span", null, elt("strong", null, data.type || "not found")); + if (data.doc) + tip.appendChild(document.createTextNode(" — " + data.doc)); + if (data.url) { + tip.appendChild(document.createTextNode(" ")); + var child = tip.appendChild(elt("a", null, "[docs]")); + child.href = data.url; + child.target = "_blank"; + } + } + tempTooltip(cm, tip, ts); + if (c) c(); + }, pos); + } + + // Maintaining argument hints + + function updateArgHints(ts, cm) { + closeArgHints(ts); + + if (cm.somethingSelected()) return; + var state = cm.getTokenAt(cm.getCursor()).state; + var inner = CodeMirror.innerMode(cm.getMode(), state); + if (inner.mode.name != "javascript") return; + var lex = inner.state.lexical; + if (lex.info != "call") return; + + var ch, argPos = lex.pos || 0, tabSize = cm.getOption("tabSize"); + for (var line = cm.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) { + var str = cm.getLine(line), extra = 0; + for (var pos = 0;;) { + var tab = str.indexOf("\t", pos); + if (tab == -1) break; + extra += tabSize - (tab + extra) % tabSize - 1; + pos = tab + 1; + } + ch = lex.column - extra; + if (str.charAt(ch) == "(") {found = true; break;} + } + if (!found) return; + + var start = Pos(line, ch); + var cache = ts.cachedArgHints; + if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0) + return showArgHints(ts, cm, argPos); + + ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) { + if (error || !data.type || !(/^fn\(/).test(data.type)) return; + ts.cachedArgHints = { + start: start, + type: parseFnType(data.type), + name: data.exprName || data.name || "fn", + guess: data.guess, + doc: cm.getDoc() + }; + showArgHints(ts, cm, argPos); + }); + } + + function showArgHints(ts, cm, pos) { + closeArgHints(ts); + + var cache = ts.cachedArgHints, tp = cache.type; + var tip = elt("span", cache.guess ? cls + "fhint-guess" : null, + elt("span", cls + "fname", cache.name), "("); + for (var i = 0; i < tp.args.length; ++i) { + if (i) tip.appendChild(document.createTextNode(", ")); + var arg = tp.args[i]; + tip.appendChild(elt("span", cls + "farg" + (i == pos ? " " + cls + "farg-current" : ""), arg.name || "?")); + if (arg.type != "?") { + tip.appendChild(document.createTextNode(":\u00a0")); + tip.appendChild(elt("span", cls + "type", arg.type)); + } + } + tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")")); + if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype)); + var place = cm.cursorCoords(null, "page"); + var tooltip = ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip) + setTimeout(function() { + tooltip.clear = onEditorActivity(cm, function() { + if (ts.activeArgHints == tooltip) closeArgHints(ts) }) + }, 20) + } + + function parseFnType(text) { + var args = [], pos = 3; + + function skipMatching(upto) { + var depth = 0, start = pos; + for (;;) { + var next = text.charAt(pos); + if (upto.test(next) && !depth) return text.slice(start, pos); + if (/[{\[\(]/.test(next)) ++depth; + else if (/[}\]\)]/.test(next)) --depth; + ++pos; + } + } + + // Parse arguments + if (text.charAt(pos) != ")") for (;;) { + var name = text.slice(pos).match(/^([^, \(\[\{]+): /); + if (name) { + pos += name[0].length; + name = name[1]; + } + args.push({name: name, type: skipMatching(/[\),]/)}); + if (text.charAt(pos) == ")") break; + pos += 2; + } + + var rettype = text.slice(pos).match(/^\) -> (.*)$/); + + return {args: args, rettype: rettype && rettype[1]}; + } + + // Moving to the definition of something + + function jumpToDef(ts, cm) { + function inner(varName) { + var req = {type: "definition", variable: varName || null}; + var doc = findDoc(ts, cm.getDoc()); + ts.server.request(buildRequest(ts, doc, req), function(error, data) { + if (error) return showError(ts, cm, error); + if (!data.file && data.url) { window.open(data.url); return; } + + if (data.file) { + var localDoc = ts.docs[data.file], found; + if (localDoc && (found = findContext(localDoc.doc, data))) { + ts.jumpStack.push({file: doc.name, + start: cm.getCursor("from"), + end: cm.getCursor("to")}); + moveTo(ts, doc, localDoc, found.start, found.end); + return; + } + } + showError(ts, cm, "Could not find a definition."); + }); + } + + if (!atInterestingExpression(cm)) + dialog(cm, "Jump to variable", function(name) { if (name) inner(name); }); + else + inner(); + } + + function jumpBack(ts, cm) { + var pos = ts.jumpStack.pop(), doc = pos && ts.docs[pos.file]; + if (!doc) return; + moveTo(ts, findDoc(ts, cm.getDoc()), doc, pos.start, pos.end); + } + + function moveTo(ts, curDoc, doc, start, end) { + doc.doc.setSelection(start, end); + if (curDoc != doc && ts.options.switchToDoc) { + closeArgHints(ts); + ts.options.switchToDoc(doc.name, doc.doc); + } + } + + // The {line,ch} representation of positions makes this rather awkward. + function findContext(doc, data) { + var before = data.context.slice(0, data.contextOffset).split("\n"); + var startLine = data.start.line - (before.length - 1); + var start = Pos(startLine, (before.length == 1 ? data.start.ch : doc.getLine(startLine).length) - before[0].length); + + var text = doc.getLine(startLine).slice(start.ch); + for (var cur = startLine + 1; cur < doc.lineCount() && text.length < data.context.length; ++cur) + text += "\n" + doc.getLine(cur); + if (text.slice(0, data.context.length) == data.context) return data; + + var cursor = doc.getSearchCursor(data.context, 0, false); + var nearest, nearestDist = Infinity; + while (cursor.findNext()) { + var from = cursor.from(), dist = Math.abs(from.line - start.line) * 10000; + if (!dist) dist = Math.abs(from.ch - start.ch); + if (dist < nearestDist) { nearest = from; nearestDist = dist; } + } + if (!nearest) return null; + + if (before.length == 1) + nearest.ch += before[0].length; + else + nearest = Pos(nearest.line + (before.length - 1), before[before.length - 1].length); + if (data.start.line == data.end.line) + var end = Pos(nearest.line, nearest.ch + (data.end.ch - data.start.ch)); + else + var end = Pos(nearest.line + (data.end.line - data.start.line), data.end.ch); + return {start: nearest, end: end}; + } + + function atInterestingExpression(cm) { + var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos); + if (tok.start < pos.ch && tok.type == "comment") return false; + return /[\w)\]]/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1)); + } + + // Variable renaming + + function rename(ts, cm) { + var token = cm.getTokenAt(cm.getCursor()); + if (!/\w/.test(token.string)) return showError(ts, cm, "Not at a variable"); + dialog(cm, "New name for " + token.string, function(newName) { + ts.request(cm, {type: "rename", newName: newName, fullDocs: true}, function(error, data) { + if (error) return showError(ts, cm, error); + applyChanges(ts, data.changes); + }); + }); + } + + function selectName(ts, cm) { + var name = findDoc(ts, cm.doc).name; + ts.request(cm, {type: "refs"}, function(error, data) { + if (error) return showError(ts, cm, error); + var ranges = [], cur = 0; + var curPos = cm.getCursor(); + for (var i = 0; i < data.refs.length; i++) { + var ref = data.refs[i]; + if (ref.file == name) { + ranges.push({anchor: ref.start, head: ref.end}); + if (cmpPos(curPos, ref.start) >= 0 && cmpPos(curPos, ref.end) <= 0) + cur = ranges.length - 1; + } + } + cm.setSelections(ranges, cur); + }); + } + + var nextChangeOrig = 0; + function applyChanges(ts, changes) { + var perFile = Object.create(null); + for (var i = 0; i < changes.length; ++i) { + var ch = changes[i]; + (perFile[ch.file] || (perFile[ch.file] = [])).push(ch); + } + for (var file in perFile) { + var known = ts.docs[file], chs = perFile[file];; + if (!known) continue; + chs.sort(function(a, b) { return cmpPos(b.start, a.start); }); + var origin = "*rename" + (++nextChangeOrig); + for (var i = 0; i < chs.length; ++i) { + var ch = chs[i]; + known.doc.replaceRange(ch.text, ch.start, ch.end, origin); + } + } + } + + // Generic request-building helper + + function buildRequest(ts, doc, query, pos) { + var files = [], offsetLines = 0, allowFragments = !query.fullDocs; + if (!allowFragments) delete query.fullDocs; + if (typeof query == "string") query = {type: query}; + query.lineCharPositions = true; + if (query.end == null) { + query.end = pos || doc.doc.getCursor("end"); + if (doc.doc.somethingSelected()) + query.start = doc.doc.getCursor("start"); + } + var startPos = query.start || query.end; + + if (doc.changed) { + if (doc.doc.lineCount() > bigDoc && allowFragments !== false && + doc.changed.to - doc.changed.from < 100 && + doc.changed.from <= startPos.line && doc.changed.to > query.end.line) { + files.push(getFragmentAround(doc, startPos, query.end)); + query.file = "#0"; + var offsetLines = files[0].offsetLines; + if (query.start != null) query.start = Pos(query.start.line - -offsetLines, query.start.ch); + query.end = Pos(query.end.line - offsetLines, query.end.ch); + } else { + files.push({type: "full", + name: doc.name, + text: docValue(ts, doc)}); + query.file = doc.name; + doc.changed = null; + } + } else { + query.file = doc.name; + } + for (var name in ts.docs) { + var cur = ts.docs[name]; + if (cur.changed && cur != doc) { + files.push({type: "full", name: cur.name, text: docValue(ts, cur)}); + cur.changed = null; + } + } + + return {query: query, files: files}; + } + + function getFragmentAround(data, start, end) { + var doc = data.doc; + var minIndent = null, minLine = null, endLine, tabSize = 4; + for (var p = start.line - 1, min = Math.max(0, p - 50); p >= min; --p) { + var line = doc.getLine(p), fn = line.search(/\bfunction\b/); + if (fn < 0) continue; + var indent = CodeMirror.countColumn(line, null, tabSize); + if (minIndent != null && minIndent <= indent) continue; + minIndent = indent; + minLine = p; + } + if (minLine == null) minLine = min; + var max = Math.min(doc.lastLine(), end.line + 20); + if (minIndent == null || minIndent == CodeMirror.countColumn(doc.getLine(start.line), null, tabSize)) + endLine = max; + else for (endLine = end.line + 1; endLine < max; ++endLine) { + var indent = CodeMirror.countColumn(doc.getLine(endLine), null, tabSize); + if (indent <= minIndent) break; + } + var from = Pos(minLine, 0); + + return {type: "part", + name: data.name, + offsetLines: from.line, + text: doc.getRange(from, Pos(endLine, end.line == endLine ? null : 0))}; + } + + // Generic utilities + + var cmpPos = CodeMirror.cmpPos; + + function elt(tagname, cls /*, ... elts*/) { + var e = document.createElement(tagname); + if (cls) e.className = cls; + for (var i = 2; i < arguments.length; ++i) { + var elt = arguments[i]; + if (typeof elt == "string") elt = document.createTextNode(elt); + e.appendChild(elt); + } + return e; + } + + function dialog(cm, text, f) { + if (cm.openDialog) + cm.openDialog(text + ": <input type=text>", f); + else + f(prompt(text, "")); + } + + // Tooltips + + function tempTooltip(cm, content, ts) { + if (cm.state.ternTooltip) remove(cm.state.ternTooltip); + var where = cm.cursorCoords(); + var tip = cm.state.ternTooltip = makeTooltip(where.right + 1, where.bottom, content); + function maybeClear() { + old = true; + if (!mouseOnTip) clear(); + } + function clear() { + cm.state.ternTooltip = null; + if (tip.parentNode) fadeOut(tip) + clearActivity() + } + var mouseOnTip = false, old = false; + CodeMirror.on(tip, "mousemove", function() { mouseOnTip = true; }); + CodeMirror.on(tip, "mouseout", function(e) { + if (!CodeMirror.contains(tip, e.relatedTarget || e.toElement)) { + if (old) clear(); + else mouseOnTip = false; + } + }); + setTimeout(maybeClear, ts.options.hintDelay ? ts.options.hintDelay : 1700); + var clearActivity = onEditorActivity(cm, clear) + } + + function onEditorActivity(cm, f) { + cm.on("cursorActivity", f) + cm.on("blur", f) + cm.on("scroll", f) + cm.on("setDoc", f) + return function() { + cm.off("cursorActivity", f) + cm.off("blur", f) + cm.off("scroll", f) + cm.off("setDoc", f) + } + } + + function makeTooltip(x, y, content) { + var node = elt("div", cls + "tooltip", content); + node.style.left = x + "px"; + node.style.top = y + "px"; + document.body.appendChild(node); + return node; + } + + function remove(node) { + var p = node && node.parentNode; + if (p) p.removeChild(node); + } + + function fadeOut(tooltip) { + tooltip.style.opacity = "0"; + setTimeout(function() { remove(tooltip); }, 1100); + } + + function showError(ts, cm, msg) { + if (ts.options.showError) + ts.options.showError(cm, msg); + else + tempTooltip(cm, String(msg), ts); + } + + function closeArgHints(ts) { + if (ts.activeArgHints) { + if (ts.activeArgHints.clear) ts.activeArgHints.clear() + remove(ts.activeArgHints) + ts.activeArgHints = null + } + } + + function docValue(ts, doc) { + var val = doc.doc.getValue(); + if (ts.options.fileFilter) val = ts.options.fileFilter(val, doc.name, doc.doc); + return val; + } + + // Worker wrapper + + function WorkerServer(ts) { + var worker = ts.worker = new Worker(ts.options.workerScript); + worker.postMessage({type: "init", + defs: ts.options.defs, + plugins: ts.options.plugins, + scripts: ts.options.workerDeps}); + var msgId = 0, pending = {}; + + function send(data, c) { + if (c) { + data.id = ++msgId; + pending[msgId] = c; + } + worker.postMessage(data); + } + worker.onmessage = function(e) { + var data = e.data; + if (data.type == "getFile") { + getFile(ts, data.name, function(err, text) { + send({type: "getFile", err: String(err), text: text, id: data.id}); + }); + } else if (data.type == "debug") { + window.console.log(data.message); + } else if (data.id && pending[data.id]) { + pending[data.id](data.err, data.body); + delete pending[data.id]; + } + }; + worker.onerror = function(e) { + for (var id in pending) pending[id](e); + pending = {}; + }; + + this.addFile = function(name, text) { send({type: "add", name: name, text: text}); }; + this.delFile = function(name) { send({type: "del", name: name}); }; + this.request = function(body, c) { send({type: "req", body: body}, c); }; + } +}); diff --git a/modules/editor/codemirror/addon/tern/worker.min.js b/modules/editor/codemirror/addon/tern/worker.min.js @@ -0,0 +1,44 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// declare global: tern, server + +var server; + +this.onmessage = function(e) { + var data = e.data; + switch (data.type) { + case "init": return startServer(data.defs, data.plugins, data.scripts); + case "add": return server.addFile(data.name, data.text); + case "del": return server.delFile(data.name); + case "req": return server.request(data.body, function(err, reqData) { + postMessage({id: data.id, body: reqData, err: err && String(err)}); + }); + case "getFile": + var c = pending[data.id]; + delete pending[data.id]; + return c(data.err, data.text); + default: throw new Error("Unknown message type: " + data.type); + } +}; + +var nextId = 0, pending = {}; +function getFile(file, c) { + postMessage({type: "getFile", name: file, id: ++nextId}); + pending[nextId] = c; +} + +function startServer(defs, plugins, scripts) { + if (scripts) importScripts.apply(null, scripts); + + server = new tern.Server({ + getFile: getFile, + async: true, + defs: defs, + plugins: plugins + }); +} + +this.console = { + log: function(v) { postMessage({type: "debug", message: v}); } +}; diff --git a/modules/editor/codemirror/demo/buffers.html b/modules/editor/codemirror/demo/buffers.html @@ -0,0 +1,109 @@ +<!doctype html> + +<title>CodeMirror: Multiple Buffer & Split View Demo</title> +<meta charset="utf-8"/> +<link rel=stylesheet href="../doc/docs.css"> + +<link rel="stylesheet" href="../lib/codemirror.css"> +<script src="../lib/codemirror.js"></script> +<script src="../mode/javascript/javascript.js"></script> +<script src="../mode/css/css.js"></script> +<style type="text/css" id=style> + .CodeMirror {border: 1px solid black; height: 250px;} + </style> +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active href="#">Multiple Buffer & Split View</a> + </ul> +</div> + +<article> +<h2>Multiple Buffer & Split View Demo</h2> + + + <div id=code_top></div> + <div> + Select buffer: <select id=buffers_top></select> + &nbsp; &nbsp; <button onclick="newBuf('top')">New buffer</button> + </div> + <div id=code_bot></div> + <div> + Select buffer: <select id=buffers_bot></select> + &nbsp; &nbsp; <button onclick="newBuf('bot')">New buffer</button> + </div> + + <script id=script> +var sel_top = document.getElementById("buffers_top"); +CodeMirror.on(sel_top, "change", function() { + selectBuffer(ed_top, sel_top.options[sel_top.selectedIndex].value); +}); + +var sel_bot = document.getElementById("buffers_bot"); +CodeMirror.on(sel_bot, "change", function() { + selectBuffer(ed_bot, sel_bot.options[sel_bot.selectedIndex].value); +}); + +var buffers = {}; + +function openBuffer(name, text, mode) { + buffers[name] = CodeMirror.Doc(text, mode); + var opt = document.createElement("option"); + opt.appendChild(document.createTextNode(name)); + sel_top.appendChild(opt); + sel_bot.appendChild(opt.cloneNode(true)); +} + +function newBuf(where) { + var name = prompt("Name for the buffer", "*scratch*"); + if (name == null) return; + if (buffers.hasOwnProperty(name)) { + alert("There's already a buffer by that name."); + return; + } + openBuffer(name, "", "javascript"); + selectBuffer(where == "top" ? ed_top : ed_bot, name); + var sel = where == "top" ? sel_top : sel_bot; + sel.value = name; +} + +function selectBuffer(editor, name) { + var buf = buffers[name]; + if (buf.getEditor()) buf = buf.linkedDoc({sharedHist: true}); + var old = editor.swapDoc(buf); + var linked = old.iterLinkedDocs(function(doc) {linked = doc;}); + if (linked) { + // Make sure the document in buffers is the one the other view is looking at + for (var name in buffers) if (buffers[name] == old) buffers[name] = linked; + old.unlinkDoc(linked); + } + editor.focus(); +} + +function nodeContent(id) { + var node = document.getElementById(id), val = node.textContent || node.innerText; + val = val.slice(val.match(/^\s*/)[0].length, val.length - val.match(/\s*$/)[0].length) + "\n"; + return val; +} +openBuffer("js", nodeContent("script"), "javascript"); +openBuffer("css", nodeContent("style"), "css"); + +var ed_top = CodeMirror(document.getElementById("code_top"), {lineNumbers: true}); +selectBuffer(ed_top, "js"); +var ed_bot = CodeMirror(document.getElementById("code_bot"), {lineNumbers: true}); +selectBuffer(ed_bot, "js"); +</script> + + <p>Demonstration of + using <a href="../doc/manual.html#linkedDoc">linked documents</a> + to provide a split view on a document, and + using <a href="../doc/manual.html#swapDoc"><code>swapDoc</code></a> + to use a single editor to display multiple documents.</p> + + </article> diff --git a/modules/editor/codemirror/demo/emacs.html b/modules/editor/codemirror/demo/emacs.html @@ -0,0 +1,75 @@ +<!doctype html> + +<title>CodeMirror: Emacs bindings demo</title> +<meta charset="utf-8"/> +<link rel=stylesheet href="../doc/docs.css"> + +<link rel="stylesheet" href="../lib/codemirror.css"> +<link rel="stylesheet" href="../addon/dialog/dialog.css"> +<script src="../lib/codemirror.js"></script> +<script src="../mode/clike/clike.js"></script> +<script src="../keymap/emacs.js"></script> +<script src="../addon/edit/matchbrackets.js"></script> +<script src="../addon/comment/comment.js"></script> +<script src="../addon/dialog/dialog.js"></script> +<script src="../addon/search/searchcursor.js"></script> +<script src="../addon/search/search.js"></script> +<style type="text/css"> + .CodeMirror {border-top: 1px solid #eee; border-bottom: 1px solid #eee;} + </style> +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active href="#">Emacs bindings</a> + </ul> +</div> + +<article> +<h2>Emacs bindings demo</h2> +<form><textarea id="code" name="code"> +#include "syscalls.h" +/* getchar: simple buffered version */ +int getchar(void) +{ + static char buf[BUFSIZ]; + static char *bufp = buf; + static int n = 0; + if (n == 0) { /* buffer is empty */ + n = read(0, buf, sizeof buf); + bufp = buf; + } + return (--n >= 0) ? (unsigned char) *bufp++ : EOF; +} +</textarea></form> + +<p>The emacs keybindings are enabled by +including <a href="../keymap/emacs.js">keymap/emacs.js</a> and setting +the <code>keyMap</code> option to <code>"emacs"</code>. Because +CodeMirror's internal API is quite different from Emacs, they are only +a loose approximation of actual emacs bindings, though.</p> + +<p>Also note that a lot of browsers disallow certain keys from being +captured. For example, Chrome blocks both Ctrl-W and Ctrl-N, with the +result that idiomatic use of Emacs keys will constantly close your tab +or open a new window.</p> + + <script> + CodeMirror.commands.save = function() { + var elt = editor.getWrapperElement(); + elt.style.background = "#def"; + setTimeout(function() { elt.style.background = ""; }, 300); + }; + var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + lineNumbers: true, + mode: "text/x-csrc", + keyMap: "emacs" + }); + </script> + + </article> diff --git a/modules/editor/codemirror/demo/fullscreen.html b/modules/editor/codemirror/demo/fullscreen.html @@ -0,0 +1,83 @@ +<!doctype html> + +<title>CodeMirror: Full Screen Editing</title> +<meta charset="utf-8"/> +<link rel=stylesheet href="../doc/docs.css"> + +<link rel="stylesheet" href="../lib/codemirror.css"> +<link rel="stylesheet" href="../addon/display/fullscreen.css"> +<link rel="stylesheet" href="../theme/night.css"> +<script src="../lib/codemirror.js"></script> +<script src="../mode/xml/xml.js"></script> +<script src="../addon/display/fullscreen.js"></script> + +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active href="#">Full Screen Editing</a> + </ul> +</div> + +<article> +<h2>Full Screen Editing</h2> +<form><textarea id="code" name="code" rows="5"> +<dl> + <dt id="option_indentWithTabs"><code><strong>indentWithTabs</strong>: boolean</code></dt> + <dd>Whether, when indenting, the first N*<code>tabSize</code> + spaces should be replaced by N tabs. Default is false.</dd> + + <dt id="option_electricChars"><code><strong>electricChars</strong>: boolean</code></dt> + <dd>Configures whether the editor should re-indent the current + line when a character is typed that might change its proper + indentation (only works if the mode supports indentation). + Default is true.</dd> + + <dt id="option_specialChars"><code><strong>specialChars</strong>: RegExp</code></dt> + <dd>A regular expression used to determine which characters + should be replaced by a + special <a href="#option_specialCharPlaceholder">placeholder</a>. + Mostly useful for non-printing special characters. The default + is <code>/[\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/</code>.</dd> + <dt id="option_specialCharPlaceholder"><code><strong>specialCharPlaceholder</strong>: function(char) → Element</code></dt> + <dd>A function that, given a special character identified by + the <a href="#option_specialChars"><code>specialChars</code></a> + option, produces a DOM node that is used to represent the + character. By default, a red dot (<span style="color: red">•</span>) + is shown, with a title tooltip to indicate the character code.</dd> + + <dt id="option_rtlMoveVisually"><code><strong>rtlMoveVisually</strong>: boolean</code></dt> + <dd>Determines whether horizontal cursor movement through + right-to-left (Arabic, Hebrew) text is visual (pressing the left + arrow moves the cursor left) or logical (pressing the left arrow + moves to the next lower index in the string, which is visually + right in right-to-left text). The default is <code>false</code> + on Windows, and <code>true</code> on other platforms.</dd> +</dl> +</textarea></form> + <script> + var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + lineNumbers: true, + theme: "night", + extraKeys: { + "F11": function(cm) { + cm.setOption("fullScreen", !cm.getOption("fullScreen")); + }, + "Esc": function(cm) { + if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false); + } + } + }); + </script> + + <p>Demonstration of + the <a href="../doc/manual.html#addon_fullscreen">fullscreen</a> + addon. Press <strong>F11</strong> when cursor is in the editor to + toggle full screen editing. <strong>Esc</strong> can also be used + to <i>exit</i> full screen editing.</p> + </article> diff --git a/modules/editor/codemirror/demo/html5complete.html b/modules/editor/codemirror/demo/html5complete.html @@ -0,0 +1,56 @@ +<!doctype html> + +<head> + <title>CodeMirror: HTML completion demo</title> + <meta charset="utf-8"/> + <link rel=stylesheet href="../doc/docs.css"> + + <link rel="stylesheet" href="../lib/codemirror.css"> + <link rel="stylesheet" href="../addon/hint/show-hint.css"> + <script src="../lib/codemirror.js"></script> + <script src="../addon/hint/show-hint.js"></script> + <script src="../addon/hint/xml-hint.js"></script> + <script src="../addon/hint/html-hint.js"></script> + <script src="../mode/xml/xml.js"></script> + <script src="../mode/javascript/javascript.js"></script> + <script src="../mode/css/css.js"></script> + <script src="../mode/htmlmixed/htmlmixed.js"></script> + <style type="text/css"> + .CodeMirror {border-top: 1px solid #888; border-bottom: 1px solid #888;} + </style> +</head> + +<body> + <div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active href="#">HTML completion</a> + </ul> + </div> + + <article> + <h2>HTML completion demo</h2> + + <p>Shows the <a href="xmlcomplete.html">XML completer</a> + parameterized with information about the tags in HTML. + Press <strong>ctrl-space</strong> to activate completion.</p> + + <div id="code"></div> + + <script type="text/javascript"> + window.onload = function() { + editor = CodeMirror(document.getElementById("code"), { + mode: "text/html", + extraKeys: {"Ctrl-Space": "autocomplete"}, + value: document.documentElement.innerHTML + }); + }; + </script> + </article> +</body> diff --git a/modules/editor/codemirror/demo/indentwrap.html b/modules/editor/codemirror/demo/indentwrap.html @@ -0,0 +1,59 @@ +<!doctype html> + +<title>CodeMirror: Indented wrapped line demo</title> +<meta charset="utf-8"/> +<link rel=stylesheet href="../doc/docs.css"> + +<link rel="stylesheet" href="../lib/codemirror.css"> +<script src="../lib/codemirror.js"></script> +<script src="../mode/xml/xml.js"></script> +<style type="text/css"> + .CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;} + .CodeMirror pre > * { text-indent: 0px; } + </style> +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active href="#">Indented wrapped line</a> + </ul> +</div> + +<article> +<h2>Indented wrapped line demo</h2> +<form><textarea id="code" name="code"> +<!doctype html> +<body> + <h2 id="overview">Overview</h2> + + <p>CodeMirror is a code-editor component that can be embedded in Web pages. The core library provides <em>only</em> the editor component, no accompanying buttons, auto-completion, or other IDE functionality. It does provide a rich API on top of which such functionality can be straightforwardly implemented. See the <a href="#addons">add-ons</a> included in the distribution, and the <a href="https://github.com/jagthedrummer/codemirror-ui">CodeMirror UI</a> project, for reusable implementations of extra features.</p> + + <p>CodeMirror works with language-specific modes. Modes are JavaScript programs that help color (and optionally indent) text written in a given language. The distribution comes with a number of modes (see the <a href="../mode/"><code>mode/</code></a> directory), and it isn't hard to <a href="#modeapi">write new ones</a> for other languages.</p> +</body> +</textarea></form> + + <p>This page uses a hack on top of the <code>"renderLine"</code> + event to make wrapped text line up with the base indentation of + the line.</p> + + <script> + var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + lineNumbers: true, + lineWrapping: true, + mode: "text/html" + }); + var charWidth = editor.defaultCharWidth(), basePadding = 4; + editor.on("renderLine", function(cm, line, elt) { + var off = CodeMirror.countColumn(line.text, null, cm.getOption("tabSize")) * charWidth; + elt.style.textIndent = "-" + off + "px"; + elt.style.paddingLeft = (basePadding + off) + "px"; + }); + editor.refresh(); + </script> + + </article> diff --git a/modules/editor/codemirror/demo/loadmode.html b/modules/editor/codemirror/demo/loadmode.html @@ -0,0 +1,72 @@ +<!doctype html> + +<title>CodeMirror: Lazy Mode Loading Demo</title> +<meta charset="utf-8"/> +<link rel=stylesheet href="../doc/docs.css"> + +<link rel="stylesheet" href="../lib/codemirror.css"> +<script src="../lib/codemirror.js"></script> +<script src="../addon/mode/loadmode.js"></script> +<script src="../mode/meta.js"></script> +<style type="text/css"> + .CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;} + </style> +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active href="#">Lazy Mode Loading</a> + </ul> +</div> + +<article> +<h2>Lazy Mode Loading Demo</h2> +<p style="color: gray">Current mode: <span id="modeinfo">text/plain</span></p> +<form><textarea id="code" name="code">This is the editor. +// It starts out in plain text mode, +# use the control below to load and apply a mode + "you'll see the highlighting of" this text /*change*/. +</textarea></form> +<p>Filename, mime, or mode name: <input type=text value=foo.js id=mode> <button type=button onclick="change()">change mode</button></p> + + <script> +CodeMirror.modeURL = "../mode/%N/%N.js"; +var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + lineNumbers: true +}); +var modeInput = document.getElementById("mode"); +CodeMirror.on(modeInput, "keypress", function(e) { + if (e.keyCode == 13) change(); +}); +function change() { + var val = modeInput.value, m, mode, spec; + if (m = /.+\.([^.]+)$/.exec(val)) { + var info = CodeMirror.findModeByExtension(m[1]); + if (info) { + mode = info.mode; + spec = info.mime; + } + } else if (/\//.test(val)) { + var info = CodeMirror.findModeByMIME(val); + if (info) { + mode = info.mode; + spec = val; + } + } else { + mode = spec = val; + } + if (mode) { + editor.setOption("mode", spec); + CodeMirror.autoLoadMode(editor, mode); + document.getElementById("modeinfo").textContent = spec; + } else { + alert("Could not find a mode corresponding to " + val); + } +} +</script> + </article> diff --git a/modules/editor/codemirror/demo/marker.html b/modules/editor/codemirror/demo/marker.html @@ -0,0 +1,52 @@ +<!doctype html> + +<title>CodeMirror: Breakpoint Demo</title> +<meta charset="utf-8"/> +<link rel=stylesheet href="../doc/docs.css"> + +<link rel="stylesheet" href="../lib/codemirror.css"> +<script src="../lib/codemirror.js"></script> +<script src="../mode/javascript/javascript.js"></script> +<style type="text/css"> + .breakpoints {width: .8em;} + .breakpoint { color: #822; } + .CodeMirror {border: 1px solid #aaa;} + </style> +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active href="#">Breakpoint</a> + </ul> +</div> + +<article> +<h2>Breakpoint Demo</h2> +<form><textarea id="code" name="code"> +var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + lineNumbers: true, + gutters: ["CodeMirror-linenumbers", "breakpoints"] +}); +editor.on("gutterClick", function(cm, n) { + var info = cm.lineInfo(n); + cm.setGutterMarker(n, "breakpoints", info.gutterMarkers ? null : makeMarker()); +}); + +function makeMarker() { + var marker = document.createElement("div"); + marker.style.color = "#822"; + marker.innerHTML = "●"; + return marker; +} +</textarea></form> + +<p>Click the line-number gutter to add or remove 'breakpoints'.</p> + + <script>eval(document.getElementById("code").value);</script> + + </article> diff --git a/modules/editor/codemirror/demo/matchhighlighter.html b/modules/editor/codemirror/demo/matchhighlighter.html @@ -0,0 +1,103 @@ +<!doctype html> + +<title>CodeMirror: Match Highlighter Demo</title> +<meta charset="utf-8"/> +<link rel=stylesheet href="../doc/docs.css"> + +<link rel="stylesheet" href="../lib/codemirror.css"> +<script src="../lib/codemirror.js"></script> +<script src="../addon/scroll/annotatescrollbar.js"></script> +<script src="../addon/search/matchesonscrollbar.js"></script> +<script src="../addon/search/searchcursor.js"></script> +<script src="../addon/search/match-highlighter.js"></script> +<style type="text/css"> + .CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;} + .CodeMirror-focused .cm-matchhighlight { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==); + background-position: bottom; + background-repeat: repeat-x; + } + .cm-matchhighlight {background-color: lightgreen} + .CodeMirror-selection-highlight-scrollbar {background-color: green} + </style> +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active href="#">Match Highlighter</a> + </ul> +</div> + +<article> +<h2>Match Highlighter Demo</h2> +<form><textarea id="code" name="code">Select this text: hardtospot + And everywhere else in your code where hardtospot appears will +automatically illuminate. Give it a try! No more hard to spot +variables - stay in context of your code all the time. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut pharetra +interdum dui eu pulvinar. Mauris maximus ligula venenatis tempus +interdum. Cras hendrerit, ipsum sed ultrices pharetra, ligula diam +porttitor lacus, ac tempor eros est a massa. Nam orci elit, vulputate +in tristique quis, consectetur vitae metus. Pellentesque et enim +elementum, lobortis augue in, lacinia sapien. Morbi eu nunc semper, +sagittis felis a, pellentesque mauris. Lorem ipsum dolor sit amet, +consectetur adipiscing elit. Aenean quis diam turpis. + +Fusce lobortis nisl quis aliquet euismod. Aenean vitae nulla non ipsum +efficitur scelerisque. Curabitur auctor, lorem non rhoncus porttitor, +augue ligula lacinia dolor, et vehicula magna lorem imperdiet velit. +Fusce risus sem, hardtospot commodo eleifend hendrerit vitae, mollis +quis risus. Cras tincidunt, justo vitae hendrerit venenatis, urna +dolor placerat tortor, eu lobortis lectus dolor in ligula. Nullam non +erat non nisl vulputate ultrices sit amet vestibulum dolor. Quisque in +tortor porta, pellentesque odio nec, malesuada nibh. + +In a dui feugiat, ullamcorper urna in, accumsan magna. Donec egestas +sem nec eros rhoncus, vel gravida purus ornare. Nulla orci mauris, +porta nec pharetra sed, ornare et lorem. Donec luctus turpis nunc, +eget dictum felis mollis et. Sed sodales hardtospot nunc vitae leo +rhoncus imperdiet. Donec elementum malesuada velit quis placerat. +Proin accumsan lorem id nisi volutpat ullamcorper. Vivamus laoreet +dolor ac sem malesuada, ac scelerisque ex efficitur. Aliquam tempus +libero velit, vel tristique augue vulputate nec. + +Mauris ultrices leo felis, sit amet congue augue aliquam condimentum. +Vivamus purus leo, mattis vitae dignissim vel, ultricies ac ex. Mauris +eu dolor eu purus ultricies ultrices. Sed euismod feugiat ex et +mattis. Morbi cursus laoreet pharetra. Donec eu dolor sodales, +ultricies nisi et, malesuada urna. Praesent sit amet fringilla felis. +Nam rhoncus, est blandit auctor auctor, lorem ipsum laoreet ipsum, +quis sodales libero odio in lorem. Phasellus odio dolor, elementum +sagittis nibh non, fermentum semper libero. Mauris hendrerit +hardtospot lectus sit amet commodo eleifend. Morbi pulvinar eget nisl +at eleifend. Fusce eget porta erat, vitae lobortis libero. + +Phasellus sit amet massa in massa pharetra malesuada. Vestibulum at +quam vel libero aliquam volutpat at ut dui. Praesent scelerisque vel +mauris sit amet vehicula. Phasellus at mi nec ligula cursus interdum +sit amet non quam. Aliquam tempus sollicitudin euismod. Nulla euismod +mollis enim tincidunt placerat. Proin ac scelerisque enim, quis +sollicitudin metus. Pellentesque congue nec sapien ut rhoncus. Sed +eget ornare diam, ut consectetur ante. Aenean eleifend mauris quis +ornare accumsan. In hac habitasse hardtospot platea dictumst. + +</textarea></form> + + <script> +var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + lineNumbers: true, + // To highlight on scrollbars as well, pass annotateScrollbar in options + // as below. + highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: true} +}); +</script> + + <p>Search and highlight occurences of the selected text.</p> + + </article> diff --git a/modules/editor/codemirror/demo/simplemode.html b/modules/editor/codemirror/demo/simplemode.html @@ -0,0 +1,185 @@ +<!doctype html> + +<title>CodeMirror: Simple Mode Demo</title> +<meta charset="utf-8"/> +<link rel=stylesheet href="../doc/docs.css"> + +<link rel="stylesheet" href="../lib/codemirror.css"> +<script src="../lib/codemirror.js"></script> +<script src="../addon/mode/simple.js"></script> +<script src="../mode/xml/xml.js"></script> +<style type="text/css"> + .CodeMirror {border: 1px solid silver; margin-bottom: 1em; } + dt { text-indent: -2em; padding-left: 2em; margin-top: 1em; } + dd { margin-left: 1.5em; margin-bottom: 1em; } + dt {margin-top: 1em;} +</style> + +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active href="#">Simple Mode</a> + </ul> +</div> + +<article> +<h2>Simple Mode Demo</h2> + +<p>The <a href="../addon/mode/simple.js"><code>mode/simple</code></a> +addon allows CodeMirror modes to be specified using a relatively simple +declarative format. This format is not as powerful as writing code +directly against the <a href="../doc/manual.html#modeapi">mode +interface</a>, but is a lot easier to get started with, and +sufficiently expressive for many simple language modes.</p> + +<p>This interface is still in flux. It is unlikely to be scrapped or +overhauled completely, so do start writing code against it, but +details might change as it stabilizes, and you might have to tweak +your code when upgrading.</p> + +<p>Simple modes (loosely based on +the <a href="https://github.com/mozilla/skywriter/wiki/Common-JavaScript-Syntax-Highlighting-Specification">Common +JavaScript Syntax Highlighting Specification</a>, which never took +off), are state machines, where each state has a number of rules that +match tokens. A rule describes a type of token that may occur in the +current state, and possibly a transition to another state caused by +that token.</p> + +<p>The <code>CodeMirror.defineSimpleMode(name, states)</code> method +takes a mode name and an object that describes the mode's states. The +editor below shows an example of such a mode (and is itself +highlighted by the mode shown in it).</p> + +<div id="code"></div> + +<p>Each state is an array of rules. A rule may have the following properties:</p> + +<dl> + <dt><code><strong>regex</strong>: string | RegExp</code></dt> + <dd>The regular expression that matches the token. May be a string + or a regex object. When a regex, the <code>ignoreCase</code> flag + will be taken into account when matching the token. This regex + has to capture groups when the <code>token</code> property is + an array. If it captures groups, it must capture <em>all</em> of the string + (since JS provides no way to find out where a group matched).</dd> + <dt><code><strong>token</strong></code>: string | array&lt;string&gt; | null</dt> + <dd>An optional token style. Multiple styles can be specified by + separating them with dots or spaces. When this property holds an array of token styles, + the <code>regex</code> for this rule must capture a group for each array item. + </dd> + <dt><code><strong>sol</strong></code>: boolean</dt> + <dd>When true, this token will only match at the start of the line. + (The <code>^</code> regexp marker doesn't work as you'd expect in + this context because of limitations in JavaScript's RegExp + API.)</dd> + <dt><code><strong>next</strong>: string</code></dt> + <dd>When a <code>next</code> property is present, the mode will + transfer to the state named by the property when the token is + encountered.</dd> + <dt><code><strong>push</strong>: string</code></dt> + <dd>Like <code>next</code>, but instead replacing the current state + by the new state, the current state is kept on a stack, and can be + returned to with the <code>pop</code> directive.</dd> + <dt><code><strong>pop</strong>: bool</code></dt> + <dd>When true, and there is another state on the state stack, will + cause the mode to pop that state off the stack and transition to + it.</dd> + <dt><code><strong>mode</strong>: {spec, end, persistent}</code></dt> + <dd>Can be used to embed another mode inside a mode. When present, + must hold an object with a <code>spec</code> property that describes + the embedded mode, and an optional <code>end</code> end property + that specifies the regexp that will end the extent of the mode. When + a <code>persistent</code> property is set (and true), the nested + mode's state will be preserved between occurrences of the mode.</dd> + <dt><code><strong>indent</strong>: bool</code></dt> + <dd>When true, this token changes the indentation to be one unit + more than the current line's indentation.</dd> + <dt><code><strong>dedent</strong>: bool</code></dt> + <dd>When true, this token will pop one scope off the indentation + stack.</dd> + <dt><code><strong>dedentIfLineStart</strong>: bool</code></dt> + <dd>If a token has its <code>dedent</code> property set, it will, by + default, cause lines where it appears at the start to be dedented. + Set this property to false to prevent that behavior.</dd> +</dl> + +<p>The <code>meta</code> property of the states object is special, and +will not be interpreted as a state. Instead, properties set on it will +be set on the mode, which is useful for properties +like <a href="../doc/manual.html#addon_comment"><code>lineComment</code></a>, +which sets the comment style for a mode. The simple mode addon also +recognizes a few such properties:</p> + +<dl> + <dt><code><strong>dontIndentStates</strong>: array&lt;string&gt;</code></dt> + <dd>An array of states in which the mode's auto-indentation should + not take effect. Usually used for multi-line comment and string + states.</dd> +</dl> + +<script id="modecode">/* Example definition of a simple mode that understands a subset of + * JavaScript: + */ + +CodeMirror.defineSimpleMode("simplemode", { + // The start state contains the rules that are intially used + start: [ + // The regex matches the token, the token property contains the type + {regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: "string"}, + // You can match multiple tokens at once. Note that the captured + // groups must span the whole string in this case + {regex: /(function)(\s+)([a-z$][\w$]*)/, + token: ["keyword", null, "variable-2"]}, + // Rules are matched in the order in which they appear, so there is + // no ambiguity between this one and the one above + {regex: /(?:function|var|return|if|for|while|else|do|this)\b/, + token: "keyword"}, + {regex: /true|false|null|undefined/, token: "atom"}, + {regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i, + token: "number"}, + {regex: /\/\/.*/, token: "comment"}, + {regex: /\/(?:[^\\]|\\.)*?\//, token: "variable-3"}, + // A next property will cause the mode to move to a different state + {regex: /\/\*/, token: "comment", next: "comment"}, + {regex: /[-+\/*=<>!]+/, token: "operator"}, + // indent and dedent properties guide autoindentation + {regex: /[\{\[\(]/, indent: true}, + {regex: /[\}\]\)]/, dedent: true}, + {regex: /[a-z$][\w$]*/, token: "variable"}, + // You can embed other modes with the mode property. This rule + // causes all code between << and >> to be highlighted with the XML + // mode. + {regex: /<</, token: "meta", mode: {spec: "xml", end: />>/}} + ], + // The multi-line comment state. + comment: [ + {regex: /.*?\*\//, token: "comment", next: "start"}, + {regex: /.*/, token: "comment"} + ], + // The meta property contains global information about the mode. It + // can contain properties like lineComment, which are supported by + // all modes, and also directives like dontIndentStates, which are + // specific to simple modes. + meta: { + dontIndentStates: ["comment"], + lineComment: "//" + } +}); +</script> + +<script> +var sc = document.getElementById("modecode"); +var code = document.getElementById("code"); +var editor = CodeMirror(code, { + value: (sc.textContent || sc.innerText || sc.innerHTML), + mode: "simplemode" +}); +</script> + +</article> diff --git a/modules/editor/codemirror/demo/trailingspace.html b/modules/editor/codemirror/demo/trailingspace.html @@ -0,0 +1,48 @@ +<!doctype html> + +<title>CodeMirror: Trailing Whitespace Demo</title> +<meta charset="utf-8"/> +<link rel=stylesheet href="../doc/docs.css"> + +<link rel="stylesheet" href="../lib/codemirror.css"> +<script src="../lib/codemirror.js"></script> +<script src="../addon/edit/trailingspace.js"></script> +<style type="text/css"> + .CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;} + .cm-trailingspace { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==); + background-position: bottom left; + background-repeat: repeat-x; + } + </style> +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active href="#">Trailing Whitespace</a> + </ul> +</div> + +<article> +<h2>Trailing Whitespace Demo</h2> +<form><textarea id="code" name="code">This text + has some +trailing whitespace!</textarea></form> + + <script> +var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + lineNumbers: true, + showTrailingSpace: true +}); +</script> + +<p>Uses +the <a href="../doc/manual.html#addon_trailingspace">trailingspace</a> +addon to highlight trailing whitespace.</p> + + </article> diff --git a/modules/editor/codemirror/doc/activebookmark.min.js b/modules/editor/codemirror/doc/activebookmark.min.js @@ -0,0 +1,57 @@ +// Kludge in HTML5 tag recognition in IE8 +document.createElement("section"); +document.createElement("article"); + +(function() { + if (!window.addEventListener) return; + var pending = false, prevVal = null; + + function updateSoon() { + if (!pending) { + pending = true; + setTimeout(update, 250); + } + } + + function update() { + pending = false; + var marks = document.getElementById("nav").getElementsByTagName("a"), found; + for (var i = 0; i < marks.length; ++i) { + var mark = marks[i], m; + if (mark.getAttribute("data-default")) { + if (found == null) found = i; + } else if (m = mark.href.match(/#(.*)/)) { + var ref = document.getElementById(m[1]); + if (ref && ref.getBoundingClientRect().top < 50) + found = i; + } + } + if (found != null && found != prevVal) { + prevVal = found; + var lis = document.getElementById("nav").getElementsByTagName("li"); + for (var i = 0; i < lis.length; ++i) lis[i].className = ""; + for (var i = 0; i < marks.length; ++i) { + if (found == i) { + marks[i].className = "active"; + for (var n = marks[i]; n; n = n.parentNode) + if (n.nodeName == "LI") n.className = "active"; + } else { + marks[i].className = ""; + } + } + } + } + + window.addEventListener("scroll", updateSoon); + window.addEventListener("load", updateSoon); + window.addEventListener("hashchange", function() { + setTimeout(function() { + var hash = document.location.hash, found = null, m; + var marks = document.getElementById("nav").getElementsByTagName("a"); + for (var i = 0; i < marks.length; i++) + if ((m = marks[i].href.match(/(#.*)/)) && m[1] == hash) { found = i; break; } + if (found != null) for (var i = 0; i < marks.length; i++) + marks[i].className = i == found ? "active" : ""; + }, 300); + }); +})(); diff --git a/modules/editor/codemirror/doc/releases.html b/modules/editor/codemirror/doc/releases.html @@ -0,0 +1,1557 @@ +<!doctype html> + +<title>CodeMirror: Release History</title> +<meta charset="utf-8"/> +<link rel=stylesheet href="docs.css"> +<script src="activebookmark.js"></script> + +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active data-default="true" href="#v5">Version 5.x</a> + <li><a href="#v4">Version 4.x</a> + <li><a href="#v3">Version 3.x</a> + <li><a href="#v2">Version 2.x</a> + <li><a href="#v1">Version 0.x</a> + </ul> +</div> + +<article> + +<h2>Release notes and version history</h2> + +<section id=v5 class=first> + + <h2>Version 5.x</h2> + + <p class="rel">22-11-2017: <a href="http://codemirror.net/codemirror-5.32.0.zip">Version 5.32.0</a>:</p> + + <ul class="rel-note"> + <li>Increase contrast on default bracket-matching colors.</li> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: Recognize TypeScript type parameters for calls, type guards, and type parameter defaults. Improve handling of <code>enum</code> and <code>module</code> keywords.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_comment">comment addon</a>: Fix bug when uncommenting a comment that spans all but the last selected line.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_searchcursor">searchcursor addon</a>: Fix bug in case folding.</li> + <li><a href="http://codemirror.net/demo/emacs.html">emacs bindings</a>: Prevent single-character deletions from resetting the kill ring.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_closebrackets">closebrackets addon</a>: Tweak quote matching behavior.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_continuelist">continuelist addon</a>: Increment ordered list numbers when adding one.</li> + </ul> + + <p class="rel">20-10-2017: <a href="http://codemirror.net/codemirror-5.31.0.zip">Version 5.31.0</a>:</p> + + <ul class="rel-note"> + <li>Modes added with <a href="http://codemirror.net/doc/manual.html#addOverlay"><code>addOverlay</code></a> now have access to a <a href="http://codemirror.net/doc/manual.html#baseToken"><code>baseToken</code></a> method on their input stream, giving access to the tokens of the underlying mode.</li> + <li>Further improve selection drawing and cursor motion in right-to-left documents.</li> + <li><a href="http://codemirror.net/demo/vim.html">vim bindings</a>: Fix ctrl-w behavior, support quote-dot and backtick-dot marks, make the wide cursor visible in contentEditable <a href="http://codemirror.net/doc/manual.html#option_contentEditable">input mode</a>.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_continuecomment">continuecomment addon</a>: Fix bug when pressing enter after a single-line block comment.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Fix issue with leaving indented fenced code blocks.</li> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: Fix bad parsing of operators without spaces between them. Fix some corner cases around semicolon insertion and regexps.</li> + </ul> + + <p class="rel">20-09-2017: <a href="http://codemirror.net/codemirror-5.30.0.zip">Version 5.30.0</a>:</p> + + <ul class="rel-note"> + <li>Fixed a number of issues with drawing right-to-left selections and mouse selection in bidirectional text.</li> + <li><a href="http://codemirror.net/demo/search/">search addon</a>: Fix crash when restarting search after doing empty search.</li> + <li><a href="http://cm/doc/manual.html#addon_mark-selection">mark-selection addon</a>: Fix off-by-one bug.</li> + <li><a href="http://codemirror.net/demo/tern.html">tern addon</a>: Fix bad request made when editing at the bottom of a large document.</li> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: Improve parsing in a number of corner cases.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Fix crash when a sub-mode doesn't support indentation, allow uppercase X in task lists.</li> + <li><a href="http://codemirror.net/mode/gfm/">gfm mode</a>: Don't highlight SHA1 'hashes' without numbers to avoid false positives.</li> + <li><a href="http://codemirror.net/mode/soy/">soy mode</a>: Support injected data and <code>@param</code> in comments.</li> + <li><a href="http://codemirror.net/demo/simplemode.html">simple mode addon</a>: Allow groups in regexps when <code>token</code> isn't an array.</li> + </ul> + + <p class="rel">24-08-2017: <a href="http://codemirror.net/codemirror-5.29.0.zip">Version 5.29.0</a>:</p> + + <ul class="rel-note"> + <li>Fix crash in contentEditable input style when editing near a bookmark.</li> + <li>Make sure change origins are preserved when splitting changes on <a href="http://codemirror.net/doc/manual.html#mark_readOnly">read-only marks</a>.</li> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: More support for TypeScript syntax.</li> + <li><a href="http://codemirror.net/mode/d/">d mode</a>: Support nested comments.</li> + <li><a href="http://codemirror.net/mode/python/">python mode</a>: Improve tokenizing of operators.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Further improve CommonMark conformance.</li> + <li><a href="http://codemirror.net/mode/css/">css mode</a>: Don't run comment tokens through the mode's state machine.</li> + <li><a href="http://codemirror.net/mode/shell/">shell mode</a>: Allow strings to span lines.</li> + <li><a href="http://codemirror.net/demo/search/">search addon</a>: Fix crash in persistent search when <code>extraKeys</code> is null.</li> + </ul> + + <p class="rel">21-07-2017: <a href="http://codemirror.net/codemirror-5.28.0.zip">Version 5.28.0</a>:</p> + + <ul class="rel-note"> + <li>Fix copying of, or replacing editor content with, a single dash character when copying a big selection in some corner cases.</li> + <li>Make <a href="http://codemirror.net/doc/manual.html#command_goLineLeft"><code>&quot;goLineLeft&quot;</code></a>/<code>&quot;goLineRight&quot;</code> behave better on wrapped lines.</li> + <li><a href="http://codemirror.net/mode/sql/">sql mode</a>: Fix tokenizing of multi-dot operator and allow digits in subfield names.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_searchcursor">searchcursor addon</a>: Fix infinite loop on some composed character inputs.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Make list parsing more CommonMark-compliant.</li> + <li><a href="http://codemirror.net/mode/gfm/">gfm mode</a>: Highlight colon syntax for emoji.</li> + </ul> + + <p class="rel">29-06-2017: <a href="http://codemirror.net/codemirror-5.27.4.zip">Version 5.27.4</a>:</p> + + <ul class="rel-note"> + <li>Fix crash when using mode lookahead.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Don't block inner mode's indentation support.</li> + </ul> + + <p class="rel">22-06-2017: <a href="http://codemirror.net/codemirror-5.27.2.zip">Version 5.27.2</a>:</p> + + <ul class="rel-note"> + <li>Fix crash in the <a href="http://codemirror.net/demo/simplemode.html">simple mode</a> addon.</li> + </ul> + + <p class="rel">22-06-2017: <a href="http://codemirror.net/codemirror-5.27.0.zip">Version 5.27.0</a>:</p> + + <ul class="rel-note"> + <li>Fix infinite loop in forced display update.</li> + <li>Properly disable the hidden textarea when <code>readOnly</code> is <code>&quot;nocursor&quot;</code>.</li> + <li>Calling the <code>Doc</code> constructor without <code>new</code> works again.</li> + <li><a href="http://codemirror.net/mode/sql/">sql mode</a>: Handle nested comments.</li> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: Improve support for TypeScript syntax.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Fix bug where markup was ignored on indented paragraph lines.</li> + <li><a href="http://codemirror.net/demo/vim.html">vim bindings</a>: Referencing invalid registers no longer causes an uncaught exception.</li> + <li><a href="http://codemirror.net/mode/rust/">rust mode</a>: Add the correct MIME type.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_matchbrackets">matchbrackets addon</a>: Document options.</li> + <li>Mouse button clicks can now be bound in keymaps by using names like <code>&quot;LeftClick&quot;</code> or <code>&quot;Ctrl-Alt-MiddleTripleClick&quot;</code>. When bound to a function, that function will be passed the position of the click as second argument.</li> + <li>The behavior of mouse selection and dragging can now be customized with the <a href="http://codemirror.net/doc/manual.html#option_configureMouse"><code>configureMouse</code></a> option.</li> + <li>Modes can now look ahead across line boundaries with the <a href="http://codemirror.net/doc/manual.html#StringStream"><code>StringStream</code></a><code>.lookahead</code> method.</li> + <li>Introduces a <code>&quot;type&quot;</code> token type, makes modes that recognize types output it, and add styling for it to the themes.</li> + <li>New <a href="http://codemirror.net/doc/manual.html#option_pasteLinesPerSelection"><code>pasteLinesPerSelection</code></a> option to control the behavior of pasting multiple lines into multiple selections.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_searchcursor">searchcursor addon</a>: Support multi-line regular expression matches, and normalize strings when matching.</li> + </ul> + + <p class="rel">22-05-2017: <a href="http://codemirror.net/codemirror-5.26.0.zip">Version 5.26.0</a>:</p> + + <ul class="rel-note"> + <li>In textarea-mode, don't reset the input field during composition.</li> + <li>More careful restoration of selections in widgets, during editor redraw.</li> + <li><a href="http://codemirror.net/demo/vim.html">vim bindings</a>: Parse line offsets in line or range specs.</li> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: More TypeScript parsing fixes.</li> + <li><a href="http://codemirror.net/mode/julia/">julia mode</a>: Fix issue where the mode gets stuck.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Understand cross-line links, parse all bracketed things as links.</li> + <li><a href="http://codemirror.net/mode/soy/">soy mode</a>: Support single-quoted strings.</li> + <li><a href="http://codemirror.net/mode/go/">go mode</a>: Don't try to indent inside strings or comments.</li> + </ul> + + <p class="rel">20-04-2017: <a href="http://codemirror.net/codemirror-5.25.2.zip">Version 5.25.2</a>:</p> + + <ul class="rel-note"> + <li>Better handling of selections that cover the whole viewport in contentEditable-mode.</li> + <li>No longer accidentally scroll the editor into view when calling <code>setValue</code>.</li> + <li>Work around Chrome Android bug when converting screen coordinates to editor positions.</li> + <li>Make sure long-clicking a selection sets a cursor and doesn't show the editor losing focus.</li> + <li>Fix issue where pointer events were incorrectly disabled on Chrome's overlay scrollbars.</li> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: Recognize annotations and TypeScript-style type parameters.</li> + <li><a href="http://codemirror.net/mode/shell/">shell mode</a>: Handle nested braces.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Make parsing of strong/em delimiters CommonMark-compliant.</li> + </ul> + + <p class="rel">20-03-2017: <a href="http://codemirror.net/codemirror-5.25.0.zip">Version 5.25.0</a>:</p> + + <ul class=rel-note> + <li>In contentEditable-mode, properly locate changes that repeat a character when inserted with IME.</li> + <li>Fix handling of selections bigger than the viewport in contentEditable mode.</li> + <li>Improve handling of changes that insert or delete lines in contentEditable mode.</li> + <li>Count Unicode control characters 0x80 to 0x9F as special (non-printing) chars.</li> + <li>Fix handling of shadow DOM roots when finding the active element.</li> + <li>Add <code>role=presentation</code> to more DOM elements to improve screen reader support.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_merge">merge addon</a>: Make aligning of unchanged chunks more robust.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_comment">comment addon</a>: Fix comment-toggling on a block of text that starts and ends in a (differnet) block comment.</li> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: Improve support for TypeScript syntax.</li> + <li><a href="http://codemirror.net/mode/r/">r mode</a>: Fix indentation after semicolon-less statements.</li> + <li><a href="http://codemirror.net/mode/shell/">shell mode</a>: Properly handle escaped parentheses in parenthesized expressions.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Fix a few bugs around leaving fenced code blocks.</li> + <li><a href="http://codemirror.net/mode/soy/">soy mode</a>: Improve indentation.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_lint">lint addon</a>: Support asynchronous linters that return promises.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_continuelist">continuelist addon</a>: Support continuing task lists.</li> + <li><a href="http://codemirror.net/demo/vim.html">vim bindings</a>: Make Y behave like yy.</li> + <li><a href="http://codemirror.net/mode/sql/">sql mode</a>: Support sqlite dialect.</li> + </ul> + + <p class="rel">22-02-2017: <a href="http://codemirror.net/codemirror-5.24.2.zip">Version 5.24.2</a>:</p> + + <ul class=rel-note> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: Support computed class method names.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_merge">merge addon</a>: Improve aligning of unchanged code in the presence of marks and line widgets.</li> + </ul> + + <p class="rel">20-02-2017: <a href="http://codemirror.net/codemirror-5.24.0.zip">Version 5.24.0</a>:</p> + + <ul class=rel-note> + <li>Positions now support a <code>sticky</code> property which determines whether they should be associated with the character before (value <code>"before"</code>) or after (value <code>"after"</code>) them.</li> + <li><a href="http://codemirror.net/demo/vim.html">vim bindings</a>: Make it possible to remove built-in bindings through the API.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_comment">comment addon</a>: Support a per-mode <code>useInnerComments</code> option to optionally suppress descending to the inner modes to get comment strings.</li> + <li>A cursor directly before a line-wrapping break is now drawn before or after the line break depending on which direction you arrived from.</li> + <li>Visual cursor motion in line-wrapped right-to-left text should be much more correct.</li> + <li>Fix bug in handling of read-only marked text.</li> + <li><a href="http://codemirror.net/mode/shell/">shell mode</a>: Properly tokenize nested parentheses.</li> + <li><a href="http://codemirror.net/mode/python/">python mode</a>: Support underscores in number literals.</li> + <li><a href="http://codemirror.net/mode/sass/">sass mode</a>: Uses the full list of CSS properties and keywords from the CSS mode, rather than defining its own incomplete subset. Now depends on the css mode.</li> + <li><a href="http://codemirror.net/mode/css/">css mode</a>: Expose <code>lineComment</code> property for LESS and SCSS dialects. Recognize vendor prefixes on pseudo-elements.</li> + <li><a href="http://codemirror.net/mode/julia/">julia mode</a>: Properly indent <code>elseif</code> lines.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Properly recognize the end of fenced code blocks when inside other markup.</li> + <li><a href="http://codemirror.net/mode/clike/">scala mode</a>: Improve handling of operators containing <code>#</code>, <code>@</code>, and <code>:</code> chars.</li> + <li><a href="http://codemirror.net/mode/xml/">xml mode</a>: Allow dashes in HTML tag names.</li> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: Improve parsing of async methods, TypeScript-style comma-separated superclass lists.</li> + <li><a href="http://codemirror.net/demo/folding.html">indent-fold addon</a>: Ignore comment lines.</li> + </ul> + + <p class="rel">19-01-2017: <a href="http://codemirror.net/codemirror-5.23.0.zip">Version 5.23.0</a>:</p> + + <ul class=rel-note> + <li>Presentation-related elements DOM elements are now marked as such to help screen readers.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Be more picky about what HTML tags look like to avoid false positives.</li> + <li><code>findModeByMIME</code> now understands <code>+json</code> and <code>+xml</code> MIME suffixes.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_closebrackets">closebrackets addon</a>: Add support for an <code>override</code> option to ignore language-specific defaults.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_panel">panel addon</a>: Add a <code>stable</code> option that auto-scrolls the content to keep it in the same place when inserting/removing a panel.</li> + </ul> + + <p class="rel">20-12-2016: <a href="http://codemirror.net/codemirror-5.22.0.zip">Version 5.22.0</a>:</p> + + <ul class=rel-note> + <li><a href="http://codemirror.net/demo/sublime.html">sublime bindings</a>: Make <code>selectBetweenBrackets</code> work with multiple cursors.</li> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: Fix issues with parsing complex TypeScript types, imports, and exports.</li> + <li>A contentEditable editor instance with autofocus enabled no longer crashes during initializing.</li> + <li><a href="http://codemirror.net/demo/emacs.html">emacs bindings</a>: Export <code>CodeMirror.emacs</code> to allow other addons to hook into Emacs-style functionality.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_active-line">active-line addon</a>: Add <code>nonEmpty</code> option.</li> + <li>New event: <a href="http://codemirror.net/doc/manual.html#event_optionChange"><code>optionChange</code></a>.</li> + </ul> + + <p class="rel">21-11-2016: <a href="http://codemirror.net/codemirror-5.21.0.zip">Version 5.21.0</a>:</p> + + <ul class=rel-note> + <li>Tapping/clicking the editor in <a href="http://codemirror.net/doc/manual.html#option_inputStyle">contentEditable mode</a> on Chrome now puts the cursor at the tapped position.</li> + <li>Fix various crashes and misbehaviors when reading composition events in <a href="http://codemirror.net/doc/manual.html#option_inputStyle">contentEditable mode</a>.</li> + <li>Catches and ignores an IE 'Unspecified Error' when creating an editor in an iframe before there is a <code>&lt;body&gt;</code>.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_merge">merge addon</a>: Fix several issues in the chunk-aligning feature.</li> + <li><a href="http://codemirror.net/mode/verilog">verilog mode</a>: Rewritten to address various issues.</li> + <li><a href="http://codemirror.net/mode/julia">julia mode</a>: Recognize Julia 0.5 syntax.</li> + <li><a href="http://codemirror.net/mode/swift">swift mode</a>: Various fixes and adjustments to current syntax.</li> + <li><a href="http://codemirror.net/mode/markdown">markdown mode</a>: Allow lists without a blank line above them.</li> + <li>The <a href="http://codemirror.net/doc/manual.html#setGutterMarker"><code>setGutterMarker</code></a>, <a href="http://codemirror.net/doc/manual.html#clearGutter"><code>clearGutter</code></a>, and <a href="http://codemirror.net/doc/manual.html#lineInfo"><code>lineInfo</code></a> methods are now available on <code>Doc</code> objects.</li> + <li>The <a href="http://codemirror.net/doc/manual.html#heightAtLine"><code>heightAtLine</code></a> method now takes an extra argument to allow finding the height at the top of the line's line widgets.</li> + <li><a href="http://codemirror.net/mode/ruby">ruby mode</a>: <code>else</code> and <code>elsif</code> are now immediately indented.</li> + <li><a href="http://codemirror.net/demo/vim.html">vim bindings</a>: Bind Ctrl-T and Ctrl-D to in- and dedent in insert mode.</li> + </ul> + + <p class="rel">20-10-2016: <a href="http://codemirror.net/codemirror-5.20.0.zip">Version 5.20.0</a>:</p> + + <ul class=rel-note> + <li>Make <code>newlineAndIndent</code> command work with multiple cursors on the same line.</li> + <li>Make sure keypress events for backspace are ignored.</li> + <li>Tokens styled with overlays no longer get a nonsense <code>cm-cm-overlay</code> class.</li> + <li>Line endings for pasted content are now normalized to the editor's <a href="http://codemirror.net/doc/manual.html#option_lineSeparator">preferred ending</a>.</li> + <li><a href="http://codemirror.net/mode/javascript">javascript mode</a>: Improve support for class expressions. Support TypeScript optional class properties, the <code>abstract</code> keyword, and return type declarations for arrow functions.</li> + <li><a href="http://codemirror.net/mode/css">css mode</a>: Fix highlighting of mixed-case keywords.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_closebrackets">closebrackets addon</a>: Improve behavior when typing a quote before a string.</li> + <li>The core is now maintained as a number of small files, using ES6 syntax and modules, under the <code>src/</code> directory. A git checkout no longer contains a working <code>codemirror.js</code> until you <code>npm build</code> (but when installing from NPM, it is included).</li> + <li>The <a href="http://codemirror.net/doc/manual.html#event_refresh"><code>refresh</code></a> event is now documented and stable.</li> + </ul> + + <p class="rel">20-09-2016: <a href="http://codemirror.net/codemirror-5.19.0.zip">Version 5.19.0</a>:</p> + + <ul class=rel-note> + <li><a href="http://codemirror.net/mode/erlang">erlang mode</a>: Fix mode crash when trying to read an empty context.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_comment">comment addon</a>: Fix broken behavior when toggling comments inside a comment.</li> + <li>xml-fold addon: Fix a null-dereference bug.</li> + <li>Page up and page down now do something even in single-line documents.</li> + <li>Fix an issue where the cursor position could be off in really long (~8000 character) tokens.</li> + <li><a href="http://codemirror.net/mode/javascript">javascript mode</a>: Better indentation when semicolons are missing. Better support for TypeScript classes, optional parameters, and the <code>type</code> keyword.</li> + <li>The <a href="http://codemirror.net/doc/manual.html#event_blur"><code>blur</code></a> and <a href="http://codemirror.net/doc/manual.html#event_focus"><code>focus</code></a> events now pass the DOM event to their handlers.</li> + </ul> + + <p class="rel">23-08-2016: <a href="http://codemirror.net/codemirror-5.18.2.zip">Version 5.18.2</a>:</p> + + <ul class=rel-note> + <li><a href="http://codemirror.net/mode/vue">vue mode</a>: Fix outdated references to renamed Pug mode dependency.</li> + </ul> + + <p class="rel">22-08-2016: <a href="http://codemirror.net/codemirror-5.18.0.zip">Version 5.18.0</a>:</p> + + <ul class=rel-note> + <li>Make sure <a href="http://codemirror.net/doc/manual.html#addLineClass">gutter backgrounds</a> stick to the rest of the gutter during horizontal scrolling.</li> + <li>The contenteditable <a href="http://codemirror.net/doc/manual.html#option_inputStyle"><code>inputStyle</code></a> now properly supports pasting on pre-Edge IE versions.</li> + <li><a href="http://codemirror.net/mode/javascript">javascript mode</a>: Fix some small parsing bugs and improve TypeScript support.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_matchbrackets">matchbrackets addon</a>: Fix bug where active highlighting was left in editor when the addon was disabled.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_match-highlighter">match-highlighter addon</a>: Only start highlighting things when the editor gains focus.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_javascript-hint">javascript-hint addon</a>: Also complete non-enumerable properties.</li> + <li>The <a href="http://codemirror.net/doc/manual.html#addOverlay"><code>addOverlay</code></a> method now supports a <code>priority</code> option to control the order in which overlays are applied.</li> + <li>MIME types that end in <code>+json</code> now default to the JSON mode when the MIME itself is not defined.</li> + <li>The mode formerly known as Jade was renamed to <a href="http://codemirror.net/mode/pug">Pug</a>.</li> + <li>The <a href="http://codemirror.net/mode/python">Python mode</a> now defaults to Python 3 (rather than 2) syntax.</li> + </ul> + + <p class="rel">19-07-2016: <a href="http://codemirror.net/codemirror-5.17.0.zip">Version 5.17.0</a>:</p> + + <ul class="rel-note"> + <li>Fix problem with wrapped trailing whitespace displaying incorrectly.</li> + <li>Prevent IME dialog from overlapping typed content in Chrome.</li> + <li>Improve measuring of characters near a line wrap.</li> + <li><a href="http://codemirror.net/mode/javascript">javascript mode</a>: Improve support for <code>async</code>, allow trailing commas in <code>import</code> lists.</li> + <li><a href="http://codemirror.net/demo/vim.html">vim bindings</a>: Fix backspace in replace mode.</li> + <li><a href="http://codemirror.net/demo/sublime.html">sublime bindings</a>: Fix some key bindings on OS X to match Sublime Text.</li> + <li><a href="http://codemirror.net/mode/markdown">markdown mode</a>: Add more classes to image links in highlight-formatting mode.</li> + </ul> + + <p class="rel">20-06-2016: <a href="http://codemirror.net/codemirror-5.16.0.zip">Version 5.16.0</a>:</p> + + <ul class="rel-note"> + <li>Fix glitches when dragging content caused by the drop indicator receiving mouse events.</li> + <li>Make Control-drag work on Firefox.</li> + <li>Make clicking or selection-dragging at the end of a wrapped line select the right position.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_show-hint">show-hint addon</a>: Prevent widget scrollbar from hiding part of the hint text.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_rulers">rulers addon</a>: Prevent rulers from forcing a horizontal editor scrollbar.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_search">search addon</a>: Automatically bind search-related keys in persistent dialog.</li> + <li><a href="http://codemirror.net/demo/sublime.html">sublime keymap</a>: Add a multi-cursor aware smart backspace binding.</li> + </ul> + + <p class="rel">20-05-2016: <a href="http://codemirror.net/codemirror-5.15.2.zip">Version 5.15.2</a>:</p> + + <ul class="rel-note"> + <li>Fix a critical document corruption bug that occurs when a document is gradually grown.</li> + </ul> + + <p class="rel">20-05-2016: <a href="http://codemirror.net/codemirror-5.15.0.zip">Version 5.15.0</a>:</p> + + <ul class="rel-note"> + <li>Fix bug that caused the selection to reset when focusing the editor in contentEditable input mode.</li> + <li>Fix issue where not all ASCII control characters were being replaced by placeholders.</li> + <li>Remove the assumption that all modes have a <code>startState</code> method from several wrapping modes.</li> + <li>Fix issue where the editor would complain about overlapping collapsed ranges when there weren't any.</li> + <li>Optimize document tree building when loading or pasting huge chunks of content.</li> + <li>Explicitly bind Ctrl-O on OS X to make that binding (“open line”) act as expected.</li> + <li>Pasting <a href="http://codemirror.net/doc/manual.html#option_lineWiseCopyCut">linewise-copied</a> content when there is no selection now inserts the lines above the current line.</li> + <li><a href="http://codemirror.net/mode/markdown/">markdown mode</a>: Fix several issues in matching link targets.</li> + <li><a href="http://codemirror.net/mode/clike/">clike mode</a>: Improve indentation of C++ template declarations.</li> + <li><a href="http://codemirror.net/mode/javascript/">javascript mode</a>: Support <code>async</code>/<code>await</code> and improve support for TypeScript type syntax.</li> + </ul> + + <p class="rel">20-04-2016: <a href="http://codemirror.net/codemirror-5.14.0.zip">Version 5.14.0</a>:</p> + + <ul class="rel-note"> + <li><a href="http://codemirror.net/doc/manual.html#posFromIndex"><code>posFromIndex</code></a> and <a href="http://codemirror.net/doc/manual.html#indexFromPos"><code>indexFromPos</code></a> now take <a href="http://codemirror.net/doc/manual.html#option_lineSeparator"><code>lineSeparator</code></a> into account</li> + <li><a href="http://codemirror.net/demo/vim.html">vim bindings</a>: Only call <code>.save()</code> when it is actually available</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_comment">comment addon</a>: Be careful not to mangle multi-line strings</li> + <li><a href="http://codemirror.net/mode/python/index.html">Python mode</a>: Improve distinguishing of decorators from <code>@</code> operators</li> + <li><a href="http://codemirror.net/doc/manual.html#findMarks"><code>findMarks</code></a>: No longer return marks that touch but don't overlap given range</li> + <li><a href="http://codemirror.net/demo/vim.html">vim bindings</a>: Add yank command</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_match-highlighter">match-highlighter addon</a>: Add <code>trim</code> option to disable ignoring of whitespace</li> + <li><a href="http://codemirror.net/mode/powershell/index.html">PowerShell mode</a>: Added</li> + <li><a href="http://codemirror.net/mode/yacas/index.html">Yacas mode</a>: Added</li> + <li><a href="http://codemirror.net/mode/webidl/index.html">Web IDL mode</a>: Added</li> + <li><a href="http://codemirror.net/mode/sas/index.html">SAS mode</a>: Added</li> + <li><a href="http://codemirror.net/mode/mbox/index.html">mbox mode</a>: Added</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.13.4...5.14.0">list of patches</a></li> + </ul> + + <p class="rel">21-03-2016: <a href="http://codemirror.net/codemirror-5.13.2.zip">Version 5.13.2</a>:</p> + + <ul class="rel-note"> + <li>Solves a problem where the gutter would sometimes not extend all the way to the end of the document.</li> + </ul> + + <p class="rel">21-03-2016: <a href="http://codemirror.net/codemirror-5.13.zip">Version 5.13</a>:</p> + + <ul class="rel-note"> + <li>New DOM event forwarded: <a href="http://codemirror.net/doc/manual.html#event_dom"><code>&quot;dragleave&quot;</code></a>.</li> + <li><a href="http://codemirror.net/mode/protobuf/index.html">protobuf mode</a>: Newly added.</li> + <li>Fix problem where <a href="http://codemirror.net/doc/manual.html#findMarks"><code>findMarks</code></a> sometimes failed to find multi-line marks.</li> + <li>Fix crash that showed up when atomic ranges and bidi text were combined.</li> + <li><a href="http://codemirror.net/demo/complete.html">show-hint addon</a>: Completion widgets no longer close when the line indented or dedented.</li> + <li><a href="http://codemirror.net/demo/merge.html">merge addon</a>: Fix bug when merging chunks at the end of the file.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_placeholder">placeholder addon</a>: No longer gets confused by <a href="http://codemirror.net/doc/manual.html#swapDoc"><code>swapDoc</code></a>.</li> + <li><a href="http://codemirror.net/doc/manual.html#addon_simplescrollbars">simplescrollbars addon</a>: Fix invalid state when deleting at end of document.</li> + <li><a href="http://codemirror.net/mode/clike/index.html">clike mode</a>: No longer gets confused when a comment starts after an operator.</li> + <li><a href="http://codemirror.net/mode/markdown/index.html">markdown mode</a>: Now supports CommonMark-style flexible list indentation.</li> + <li><a href="http://codemirror.net/mode/dylan/index.html">dylan mode</a>: Several improvements and fixes.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.12.0...5.13.0">list of patches</a></li> + </ul> + + <p class="rel">19-02-2016: <a href="http://codemirror.net/codemirror-5.12.zip">Version 5.12</a>:</p> + + <ul class="rel-note"> + <li><a href="http://codemirror.net/demo/vim.html">Vim bindings</a>: Ctrl-Q is now an alias for Ctrl-V.</li> + <li><a href="http://codemirror.net/demo/vim.html">Vim bindings</a>: The Vim API now exposes an <code>unmap</code> method to unmap bindings.</li> + <li><a href="http://codemirror.net/demo/activeline.html">active-line addon</a>: This addon can now style the active line's gutter.</li> + <li><a href="http://codemirror.net/mode/fcl/">FCL mode</a>: Newly added.</li> + <li><a href="http://codemirror.net/mode/sql/">SQL mode</a>: Now has a Postgresql dialect.</li> + <li>Fix <a href="https://github.com/codemirror/CodeMirror/issues/3781">issue</a> where trying to scroll to a horizontal position outside of the document's width could cause the gutter to be positioned incorrectly.</li> + <li>Use absolute, rather than fixed positioning in the context-menu intercept hack, to work around a <a href="https://github.com/codemirror/CodeMirror/issues/3238">problem</a> when the editor is inside a transformed parent container.</li> + <li>Solve a <a href="https://github.com/codemirror/CodeMirror/issues/3821">problem</a> where the horizontal scrollbar could hide text in Firefox.</li> + <li>Fix a <a href="https://github.com/codemirror/CodeMirror/issues/3834">bug</a> that caused phantom scroll space under the text in some situations.</li> + <li><a href="http://codemirror.net/demo/sublime.html">Sublime Text bindings</a>: Bind delete-line to Shift-Ctrl-K on OS X.</li> + <li><a href="http://codemirror.net/mode/markdown/">Markdown mode</a>: Fix <a href="https://github.com/codemirror/CodeMirror/issues/3787">issue</a> where the mode would keep state related to fenced code blocks in an unsafe way, leading to occasional corrupted parses.</li> + <li><a href="http://codemirror.net/mode/markdown/">Markdown mode</a>: Ignore backslashes in code fragments.</li> + <li><a href="http://codemirror.net/mode/markdown/">Markdown mode</a>: Use whichever mode is registered as <code>text/html</code> to parse HTML.</li> + <li><a href="http://codemirror.net/mode/clike/">Clike mode</a>: Improve indentation of Scala <code>=&gt;</code> functions.</li> + <li><a href="http://codemirror.net/mode/python/">Python mode</a>: Improve indentation of bracketed code.</li> + <li><a href="http://codemirror.net/mode/htmlmixed/">HTMLMixed mode</a>: Support multi-line opening tags for sub-languages (<code>&lt;script&gt;</code>, <code>&lt;style&gt;</code>, etc).</li> + <li><a href="http://codemirror.net/mode/spreadsheet/">Spreadsheet mode</a>: Fix bug where the mode did not advance the stream when finding a backslash.</li> + <li><a href="http://codemirror.net/mode/xml/">XML mode</a>: The mode now takes a <code>matchClosing</code> option to configure whether mismatched closing tags should be highlighted as errors.</li> + </ul> + + <p class="rel">20-01-2016: <a href="http://codemirror.net/codemirror-5.11.zip">Version 5.11</a>:</p> + + <ul class="rel-note"> + <li>New modes: <a href="../mode/jsx/index.html">JSX</a>, <a href="../mode/haskell-literate/index.html">literate Haskell</a></li> + <li>The editor now forwards more <a href="manual.html#event_dom">DOM events</a>: <code>cut</code>, <code>copy</code>, <code>paste</code>, and <code>touchstart</code>. It will also forward <code>mousedown</code> for drag events</li> + <li>Fixes a bug where bookmarks next to collapsed spans were not rendered</li> + <li>The <a href="../mode/swift/index.html">Swift</a> mode now supports auto-indentation</li> + <li>Frontmatters in the <a href="../mode/yaml-frontmatter/index.html">YAML frontmatter</a> mode are now optional as intended</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.10.0...5.11.0">list of patches</a></li> + </ul> + + <p class="rel">21-12-2015: <a href="http://codemirror.net/codemirror-5.10.zip">Version 5.10</a>:</p> + + <ul class="rel-note"> + <li>Modify the way <a href="manual.html#mark_atomic">atomic ranges</a> are skipped by selection to try and make it less surprising.</li> + <li>The <a href="../mode/swift/index.html">Swift mode</a> was rewritten.</li> + <li>New addon: <a href="manual.html#addon_jump-to-line">jump-to-line</a>.</li> + <li>New method: <a href="manual.html#isReadOnly"><code>isReadOnly</code></a>.</li> + <li>The <a href="manual.html#addon_show-hint">show-hint addon</a> now defaults to picking completions on single click.</li> + <li>The object passed to <a href="manual.html#event_beforeSelectionChange"><code>&quot;beforeSelectionChange&quot;</code></a> events now has an <code>origin</code> property.</li> + <li>New mode: <a href="../mode/crystal/index.html">Crystal</a>.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.9.0...5.10.0">list of patches</a></li> + </ul> + + <p class="rel">23-11-2015: <a href="http://codemirror.net/codemirror-5.9.zip">Version 5.9</a>:</p> + + <ul class="rel-note"> + <li>Improve the way overlay (OS X-style) scrollbars are handled</li> + <li>Make <a href="manual.html#addon_annotatescrollbar">annotatescrollbar</a> and scrollpastend addons work properly together</li> + <li>Make <a href="manual.html#addon_show-hint">show-hint</a> addon select options on single click by default, move selection to hovered item</li> + <li>Properly fold comments that include block-comment-start markers</li> + <li>Many small language mode fixes</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.8.0...5.9.0">list of patches</a></li> + </ul> + + <p class="rel">20-10-2015: <a href="http://codemirror.net/codemirror-5.8.zip">Version 5.8</a>:</p> + + <ul class="rel-note"> + <li>Fixes an infinite loop in + the <a href="manual.html#addon_hardwrap">hardwrap + addon</a></li> + <li>New modes: <a href="../mode/nsis/index.html">NSIS</a>, <a href="../mode/clike/index.html">Ceylon</a></li> + <li>The Kotlin mode is now a <a href="../mode/clike/index.html">clike</a> dialect, rather than a stand-alone mode</li> + <li>New option: <a href="manual.html#option_allowDropFileTypes"><code>allowDropFileTypes</code></a>. Binary files can no longer be dropped into CodeMirror</li> + <li>New themes: <a href="../demo/theme.html#bespin">bespin</a>, <a href="../demo/theme.html#hopscotch">hopscotch</a>, <a href="../demo/theme.html#isotope">isotope</a>, <a href="../demo/theme.html#railscasts">railscasts</a></li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.7.0...5.8.0">list of patches</a></li> + </ul> + + <p class="rel">20-09-2015: <a href="http://codemirror.net/codemirror-5.7.zip">Version 5.7</a>:</p> + + <ul class="rel-note"> + <li>New modes: <a href="../mode/vue/index.html">Vue</a>, <a href="../mode/oz/index.html">Oz</a>, <a href="../mode/mscgen/index.html">MscGen</a> (and dialects), <a href="../mode/css/gss.html">Closure Stylesheets</a></li> + <li>Implement <a href="http://commonmark.org">CommonMark</a>-style flexible list indent and cross-line code spans in <a href="../mode/markdown/index.html">Markdown</a> mode</li> + <li>Add a replace-all button to the <a href="manual.html#addon_search">search addon</a>, and make the persistent search dialog transparent when it obscures the match</li> + <li>Handle <code>acync</code>/<code>await</code> and ocal and binary numbers in <a href="../mode/javascript/index.html">JavaScript mode</a></li> + <li>Fix various issues with the <a href="../mode/haxe/index.html">Haxe mode</a></li> + <li>Make the <a href="manual.html#addon_closebrackets">closebrackets addon</a> select only the wrapped text when wrapping selection in brackets</li> + <li>Tokenize properties as properties in the <a href="../mode/coffeescript/index.html">CoffeeScript mode</a></li> + <li>The <a href="manual.html#addon_placeholder">placeholder addon</a> now accepts a DOM node as well as a string placeholder</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.6.0...5.7.0">list of patches</a></li> + </ul> + + <p class="rel">20-08-2015: <a href="http://codemirror.net/codemirror-5.6.zip">Version 5.6</a>:</p> + + <ul class="rel-note"> + <li>Fix bug where you could paste into a <code>readOnly</code> editor</li> + <li>Show a cursor at the drop location when dragging over the editor</li> + <li>The <a href="../mode/rust/index.html">Rust mode</a> was rewritten to handle modern Rust</li> + <li>The editor and theme CSS was cleaned up. Some selectors are now less specific than before</li> + <li>New theme: <a href="../demo/theme.html#abcdef">abcdef</a></li> + <li>Lines longer than <a href="manual.html#option_maxHighlightLength"><code>maxHighlightLength</code></a> are now less likely to mess up indentation</li> + <li>New addons: <a href="manual.html#addon_autorefresh"><code>autorefresh</code></a> for refreshing an editor the first time it becomes visible, and <code>html-lint</code> for using <a href="http://htmlhint.com/">HTMLHint</a></li> + <li>The <a href="manual.html#addon_search"><code>search</code></a> addon now recognizes <code>\r</code> and <code>\n</code> in pattern and replacement input</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.5.0...5.6.0">list of patches</a></li> + </ul> + + <p class="rel">20-07-2015: <a href="http://codemirror.net/codemirror-5.5.zip">Version 5.5</a>:</p> + + <ul class="rel-note"> + <li>New option: <a href="manual.html#option_lineSeparator"><code>lineSeparator</code></a> (with corresponding <a href="manual.html#lineSeparator">method</a>) + <li>New themes: <a href="../demo/theme.html#dracula">dracula</a>, <a href="../demo/theme.html#seti">seti</a>, <a href="../demo/theme.html#yeti">yeti</a>, <a href="../demo/theme.html#material">material</a>, and <a href="../demo/theme.html#icecoder">icecoder</a></li> + <li>New modes: <a href="../mode/brainfuck/index.html">Brainfuck</a>, <a href="../mode/vhdl/index.html">VHDL</a>, Squirrel (<a href="../mode/clike/index.html">clike</a> dialect)</li> + <li>Define a <code>findPersistent</code> command in + the <a href="../demo/search.html">search</a> addon, for a dialog + that stays open as you cycle through matches</li> + <li>From this release on, the NPM module doesn't include documentation and demos</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.4.0...5.5.0">list of patches</a></li> + </ul> + + <p class="rel">25-06-2015: <a href="http://codemirror.net/codemirror-5.4.zip">Version 5.4</a>:</p> + + <ul class="rel-note"> + <li>New modes: <a href="../mode/twig/index.html">Twig</a>, <a href="../mode/elm/index.html">Elm</a>, <a href="../mode/factor/index.html">Factor</a>, <a href="../mode/swift/index.html">Swift</a></li> + <li>Prefer clipboard API (if available) when pasting</li> + <li>Refined definition highlighting in <a href="../mode/clike/index.html">clike</a> mode</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.3.0...5.4.0">list of patches</a></li> + </ul> + + <p class="rel">20-05-2015: <a href="http://codemirror.net/codemirror-5.3.zip">Version 5.3</a>:</p> + + <ul class="rel-note"> + <li>Fix several regressions in the <a href="manual.html#addon_show-hint"><code>show-hint</code></a> addon (<code>completeSingle</code> option, <code>"shown"</code> and <code>"close"</code> events)</li> + <li>The <a href="../demo/vim.html">vim mode</a> API was <a href="manual.html#vimapi">documented</a></li> + <li>New modes: <a href="../mode/asn.1/index.html">ASN.1</a>, <a href="../mode/ttcn/index.html">TTCN</a>, and <a href="../mode/ttcn-cfg/index.html">TTCN-CFG</a></li> + <li>The <a href="../mode/clike/index.html">clike</a> mode can now deep-indent <code>switch</code> statements, and roughly recognizes types and defined identifiers</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.2.0...5.3.0">list of patches</a></li> + </ul> + + <p class="rel">20-04-2015: <a href="http://codemirror.net/codemirror-5.2.zip">Version 5.2</a>:</p> + + <ul class="rel-note"> + <li>Fix several race conditions + in <a href="manual.html#addon_show-hint"><code>show-hint</code></a>'s + asynchronous mode</li> + <li>Fix backspace binding in <a href="../demo/sublime.html">Sublime bindings</a></li> + <li>Change the way IME is handled in the <code>"textarea"</code> <a href="manual.html#option_inputStyle">input style</a></li> + + <li>New modes: <a href="../mode/mumps/index.html">MUMPS</a>, <a href="../mode/handlebars/index.html">Handlebars</a></li> + <li>Rewritten modes: <a href="../mode/django/index.html">Django</a>, <a href="../mode/z80/index.html">Z80</a></li> + <li>New theme: <a href="../demo/theme.html#liquibyte">Liquibyte</a></li> + <li>New option: <a href="manual.html#option_lineWiseCopyCut"><code>lineWiseCopyCut</code></a></li> + <li>The <a href="../demo/vim.html">Vim mode</a> now supports buffer-local options and the <code>filetype</code> setting</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.1.0...5.2.0">list of patches</a></li> + </ul> + + <p class="rel">23-03-2015: <a href="http://codemirror.net/codemirror-5.1.zip">Version 5.1</a>:</p> + + <ul class="rel-note"> + <li>New modes: <a href="../mode/asciiarmor/index.html">ASCII armor</a> (PGP data), <a href="../mode/troff/index.html">Troff</a>, and <a href="../mode/cmake/index.html">CMake</a>.</li> + <li>Remove SmartyMixed mode, rewrite <a href="../mode/smarty/index.html">Smarty</a> mode to supersede it.</li> + <li>New commands in the <a href="manual.html#addon_merge">merge + addon</a>: <code>goNextDiff</code> and <code>goPrevDiff</code>.</li> + <li>The <a href="manual.html#addon_closebrackets">closebrackets addon</a> can now be configured per mode.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/5.0.0...5.1.0">list of patches</a>.</li> + </ul> + + <p class="rel">20-02-2015: <a href="http://codemirror.net/codemirror-5.0.zip">Version 5.0</a>:</p> + + <ul class="rel-note"> + <li>Experimental mobile support (tested on iOS, Android Chrome, stock Android browser)</li> + <li>New option <a href="manual.html#option_inputStyle"><code>inputStyle</code></a> to switch between hidden textarea and contenteditable input.</li> + <li>The <a href="manual.html#getInputField"><code>getInputField</code></a> + method is no longer guaranteed to return a textarea.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.13.0...5.0.0">list of patches</a>.</li> + </ul> + +</section> + +<section id=v4 class=first> + + <h2>Version 4.x</h2> + + <p class="rel">20-02-2015: <a href="http://codemirror.net/codemirror-4.13.zip">Version 4.13</a>:</p> + + <ul class="rel-note"> + <li>Fix the way the <a href="../demo/closetag.html"><code>closetag</code></a> demo handles the slash character.</li> + <li>New modes: <a href="../mode/forth/index.html">Forth</a>, <a href="../mode/stylus/index.html">Stylus</a>.</li> + <li>Make the <a href="../mode/css/index.html">CSS mode</a> understand some modern CSS extensions.</li> + <li>Have the <a href="../mode/clike/index.html">Scala mode</a> handle symbols and triple-quoted strings.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.12.0...4.13.0">list of patches</a>.</li> + </ul> + + <p class="rel">22-01-2015: <a href="http://codemirror.net/codemirror-4.12.zip">Version 4.12</a>:</p> + + <ul class="rel-note"> + <li>The <a href="manual.html#addon_closetag"><code>closetag</code></a> + addon now defines a <code>"closeTag"</code> command.</li> + <li>Adds a <code>findModeByFileName</code> to the <a href="manual.html#addon_meta">mode metadata</a> + addon.</li> + <li><a href="../demo/simplemode.html">Simple mode</a> rules can + now contain a <code>sol</code> property to only match at the start + of a line.</li> + <li>New + addon: <a href="manual.html#addon_selection-pointer"><code>selection-pointer</code></a> + to style the mouse cursor over the selection.</li> + <li>Improvements to the <a href="../mode/sass/index.html">Sass mode</a>'s indentation.</li> + <li>The <a href="../demo/vim.html">Vim keymap</a>'s search functionality now + supports <a href="manual.html#addon_matchesonscrollbar">scrollbar + annotation</a>.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.11.0...4.12.0">list of patches</a>.</li> + </ul> + + <p class="rel">9-01-2015: <a href="http://codemirror.net/codemirror-4.11.zip">Version 4.11</a>:</p> + + <p class="rel-note">Unfortunately, 4.10 did not take care of the + Firefox scrolling issue entirely. This release adds two more patches + to address that.</p> + + <p class="rel">29-12-2014: <a href="http://codemirror.net/codemirror-4.10.zip">Version 4.10</a>:</p> + + <p class="rel-note">Emergency single-patch update to 4.9. Fixes + Firefox-specific problem where the cursor could end up behind the + horizontal scrollbar.</p> + + <p class="rel">23-12-2014: <a href="http://codemirror.net/codemirror-4.9.zip">Version 4.9</a>:</p> + + <ul class="rel-note"> + <li>Overhauled scroll bar handling. + Add pluggable <a href="../demo/simplescrollbars.html">scrollbar + implementations</a>.</li> + <li>Tweaked behavior for + the <a href="manual.html#addon_show-hint">completion addons</a> to + not take text after cursor into account.</li> + <li>Two new optional features in + the <a href="manual.html#addon_merge">merge addon</a>: aligning + editors, and folding unchanged text.</li> + <li>New + modes: <a href="../mode/dart/index.html">Dart</a>, <a href="../mode/ebnf/index.html">EBNF</a>, <a href="../mode/spreadsheet/index.html">spreadsheet</a>, + and <a href="../mode/soy/index.html">Soy</a>.</li> + <li>New <a href="../demo/panel.html">addon</a> to show persistent panels below/above an editor.</li> + <li>New themes: <a href="../demo/theme.html#zenburn">zenburn</a> + and <a href="../demo/theme.html#tomorrow-night-bright">tomorrow night + bright</a>.</li> + <li>Allow ctrl-click to clear existing cursors.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.8.0...4.9.0">list of patches</a>.</li> + </ul> + + <p class="rel">22-11-2014: <a href="http://codemirror.net/codemirror-4.8.zip">Version 4.8</a>:</p> + + <ul class="rel-note"> + <li>Built-in support for <a href="manual.html#normalizeKeyMap">multi-stroke key bindings</a>.</li> + <li>New method: <a href="manual.html#getLineTokens"><code>getLineTokens</code></a>.</li> + <li>New modes: <a href="../mode/dockerfile/index.html">dockerfile</a>, <a href="../mode/idl/index.html">IDL</a>, <a href="../mode/clike/index.html">Objective C</a> (crude).</li> + <li>Support styling of gutter backgrounds, allow <code>"gutter"</code> styles in <a href="manual.html#addLineClass"><code>addLineClass</code></a>.</li> + <li>Many improvements to the <a href="../demo/vim.html">Vim mode</a>, rewritten visual mode.</li> + <li>Improvements to modes: <a href="../mode/gfm/index.html">gfm</a> (strikethrough), <a href="../mode/sparql/index.html">SPARQL</a> (version 1.1 support), and <a href="../mode/stex/index.html">sTeX</a> (no more runaway math mode). + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.7.0...4.8.0">list of patches</a>.</li> + </ul> + + <p class="rel">20-10-2014: <a href="http://codemirror.net/codemirror-4.7.zip">Version 4.7</a>:</p> + + <ul class="rel-note"> + <li><strong>Incompatible</strong>: + The <a href="../demo/lint.html">lint addon</a> now passes the + editor's value as first argument to asynchronous lint functions, + for consistency. The editor is still passed, as fourth + argument.</li> + <li>Improved handling of unicode identifiers in modes for + languages that support them.</li> + <li>More mode + improvements: <a href="../mode/coffeescript/index.html">CoffeeScript</a> + (indentation), <a href="../mode/verilog/index.html">Verilog</a> + (indentation), <a href="../mode/clike/index.html">Scala</a> + (indentation, triple-quoted strings), + and <a href="../mode/php/index.html">PHP</a> (interpolated + variables in heredoc strings).</li> + <li>New modes: <a href="../mode/textile/index.html">Textile</a> and <a href="../mode/tornado/index.html">Tornado templates</a>.</li> + <li>Experimental new <a href="../demo/simplemode.html">way to define modes</a>.</li> + <li>Improvements to the <a href="../demo/vim.html">Vim + bindings</a>: Arbitrary insert mode key mappings are now possible, + and text objects are supported in visual mode.</li> + <li>The mode <a href="../mode/meta.js">meta-information file</a> + now includes information about file extensions, + and <a href="manual.html#addon_meta">helper + functions</a> <code>findModeByMIME</code> + and <code>findModeByExtension</code>.</li> + <li>New logo!</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.6.0...4.7.0">list of patches</a>.</li> + </ul> + + <p class="rel">19-09-2014: <a href="http://codemirror.net/codemirror-4.6.zip">Version 4.6</a>:</p> + + <ul class="rel-note"> + <li>New mode: <a href="../mode/modelica/index.html">Modelica</a></li> + <li>New method: <a href="manual.html#findWordAt"><code>findWordAt</code></a></li> + <li>Make it easier to <a href="../demo/markselection.html">use text background styling</a></li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.5.0...4.6.0">list of patches</a>.</li> + </ul> + + <p class="rel">21-08-2014: <a href="http://codemirror.net/codemirror-4.5.zip">Version 4.5</a>:</p> + + <ul class="rel-note"> + <li>Fix several serious bugs with horizontal scrolling</li> + <li>New mode: <a href="../mode/slim/index.html">Slim</a></li> + <li>New command: <a href="manual.html#command_goLineLeftSmart"><code>goLineLeftSmart</code></a></li> + <li>More fixes and extensions for the <a href="../demo/vim.html">Vim</a> visual block mode</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.4.0...4.5.0">list of patches</a>.</li> + </ul> + + <p class="rel">21-07-2014: <a href="http://codemirror.net/codemirror-4.4.zip">Version 4.4</a>:</p> + + <ul class="rel-note"> + <li><strong>Note:</strong> Some events might now fire in slightly + different order (<code>"change"</code> is still guaranteed to fire + before <code>"cursorActivity"</code>)</li> + <li>Nested operations in multiple editors are now synced (complete + at same time, reducing DOM reflows)</li> + <li>Visual block mode for <a href="../demo/vim.html">vim</a> (&lt;C-v>) is nearly complete</li> + <li>New mode: <a href="../mode/kotlin/index.html">Kotlin</a></li> + <li>Better multi-selection paste for text copied from multiple CodeMirror selections</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.3.0...4.4.0">list of patches</a>.</li> + </ul> + + <p class="rel">23-06-2014: <a href="http://codemirror.net/codemirror-4.3.zip">Version 4.3</a>:</p> + + <ul class="rel-note"> + <li>Several <a href="../demo/vim.html">vim bindings</a> + improvements: search and exCommand history, global flag + for <code>:substitute</code>, <code>:global</code> command. + <li>Allow hiding the cursor by + setting <a href="manual.html#option_cursorBlinkRate"><code>cursorBlinkRate</code></a> + to a negative value.</li> + <li>Make gutter markers themeable, use this in foldgutter.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.2.0...4.3.0">list of patches</a>.</li> + </ul> + + <p class="rel">19-05-2014: <a href="http://codemirror.net/codemirror-4.2.zip">Version 4.2</a>:</p> + + <ul class="rel-note"> + <li>Fix problem where some modes were broken by the fact that empty tokens were forbidden.</li> + <li>Several fixes to context menu handling.</li> + <li>On undo, scroll <em>change</em>, not cursor, into view.</li> + <li>Rewritten <a href="../mode/jade/index.html">Jade</a> mode.</li> + <li>Various improvements to <a href="../mode/shell/index.html">Shell</a> (support for more syntax) and <a href="../mode/python/index.html">Python</a> (better indentation) modes.</li> + <li>New mode: <a href="../mode/cypher/index.html">Cypher</a>.</li> + <li>New theme: <a href="../demo/theme.html#neo">Neo</a>.</li> + <li>Support direct styling options (color, line style, width) in the <a href="manual.html#addon_rulers">rulers</a> addon.</li> + <li>Recognize per-editor configuration for the <a href="manual.html#addon_show-hint">show-hint</a> and <a href="manual.html#addon_foldcode">foldcode</a> addons.</li> + <li>More intelligent scanning for existing close tags in <a href="manual.html#addon_closetag">closetag</a> addon.</li> + <li>In the <a href="../demo/vim.html">Vim bindings</a>: Fix bracket matching, support case conversion in visual mode, visual paste, append action.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.1.0...4.2.0">list of patches</a>.</li> + </ul> + + <p class="rel">22-04-2014: <a href="http://codemirror.net/codemirror-4.1.zip">Version 4.1</a>:</p> + + <ul class="rel-note"> + <li><em>Slightly incompatible</em>: + The <a href="manual.html#event_cursorActivity"><code>"cursorActivity"</code></a> + event now fires after all other events for the operation (and only + for handlers that were actually registered at the time the + activity happened).</li> + <li>New command: <a href="manual.html#command_insertSoftTab"><code>insertSoftTab</code></a>.</li> + <li>New mode: <a href="../mode/django/index.html">Django</a>.</li> + <li>Improved modes: <a href="../mode/verilog/index.html">Verilog</a> (rewritten), <a href="../mode/jinja2/index.html">Jinja2</a>, <a href="../mode/haxe/index.html">Haxe</a>, <a href="../mode/php/index.html">PHP</a> (string interpolation highlighted), <a href="../mode/javascript/index.html">JavaScript</a> (indentation of trailing else, template strings), <a href="../mode/livescript/index.html">LiveScript</a> (multi-line strings).</li> + <li>Many small issues from the 3.x→4.x transition were found and fixed.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/4.0.3...4.1.0">list of patches</a>.</li> + </ul> + + <p class="rel">20-03-2014: <a href="http://codemirror.net/codemirror-4.0.zip">Version 4.0</a>:</p> + + <p class="rel-note">This is a new major version of CodeMirror. There + are a few <strong>incompatible</strong> changes in the API. Upgrade + with care, and read the <a href="upgrade_v4.html">upgrading + guide</a>.</p> + + <ul class="rel-note"> + <li>Multiple selections (ctrl-click, alt-drag, <a href="manual.html#setSelections">API</a>).</li> + <li>Sublime Text <a href="../demo/sublime.html">bindings</a>.</li> + <li><a href="manual.html#modloader">Module loader shims</a> wrapped around all modules.</li> + <li>Selection <a href="manual.html#command_undoSelection">undo</a>/<a href="manual.html#command_redoSelection">redo</a>.</li> + <li>Improved character measuring (faster, handles wrapped lines more robustly).</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/3.23.0...4.0.3">list of patches</a>.</li> + </ul> + +</section> + +<section id=v3> + + <h2>Version 3.x</h2> + + <p class="rel">22-04-2014: <a href="http://codemirror.net/codemirror-3.24.zip">Version 3.24</a>:</p> + + <p class="rel-note">Merges the improvements from 4.1 that could + easily be applied to the 3.x code. Also improves the way the editor + size is updated when line widgets change.</p> + + <p class="rel">20-03-2014: <a href="http://codemirror.net/codemirror-3.23.zip">Version 3.23</a>:</p> + + <ul class="rel-note"> + <li>In the <a href="../mode/xml/index.html">XML mode</a>, + add <code>brackets</code> style to angle brackets, fix + case-sensitivity of tags for HTML.</li> + <li>New mode: <a href="../mode/dylan/index.html">Dylan</a>.</li> + <li>Many improvements to the <a href="../demo/vim.html">Vim bindings</a>.</li> + </ul> + + <p class="rel">21-02-2014: <a href="http://codemirror.net/codemirror-3.22.zip">Version 3.22</a>:</p> + + <ul class="rel-note"> + <li>Adds the <a href="manual.html#findMarks"><code>findMarks</code></a> method.</li> + <li>New addons: <a href="manual.html#addon_rulers">rulers</a>, markdown-fold, yaml-lint.</li> + <li>New theme: <a href="../demo/theme.html#mdn-like">mdn-like</a>.</li> + <li>New mode: <a href="../mode/solr/index.html">Solr</a>.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/3.21.0...3.22.0">list of patches</a>.</li> + </ul> + + <p class="rel">16-01-2014: <a href="http://codemirror.net/codemirror-3.21.zip">Version 3.21</a>:</p> + + <ul class="rel-note"> + <li>Auto-indenting a block will no longer add trailing whitespace to blank lines.</li> + <li>Marking text has a new option <a href="manual.html#markText"><code>clearWhenEmpty</code></a> to control auto-removal.</li> + <li>Several bugfixes in the handling of bidirectional text.</li> + <li>The <a href="../mode/xml/index.html">XML</a> and <a href="../mode/css/index.html">CSS</a> modes were largely rewritten. <a href="../mode/css/less.html">LESS</a> support was added to the CSS mode.</li> + <li>The OCaml mode was moved to an <a href="../mode/mllike/index.html">mllike</a> mode, F# support added.</li> + <li>Make it possible to fetch multiple applicable helper values with <a href="manual.html#getHelpers"><code>getHelpers</code></a>, and to register helpers matched on predicates with <a href="manual.html#registerGlobalHelper"><code>registerGlobalHelper</code></a>.</li> + <li>New theme <a href="../demo/theme.html#pastel-on-dark">pastel-on-dark</a>.</li> + <li>Better ECMAScript 6 support in <a href="../mode/javascript/index.html">JavaScript</a> mode.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/3.20.0...3.21.0">list of patches</a>.</li> + </ul> + + <p class="rel">21-11-2013: <a href="http://codemirror.net/codemirror-3.20.zip">Version 3.20</a>:</p> + + <ul class="rel-note"> + <li>New modes: <a href="../mode/julia/index.html">Julia</a> and <a href="../mode/pegjs/index.html">PEG.js</a>.</li> + <li>Support ECMAScript 6 in the <a href="../mode/javascript/index.html">JavaScript mode</a>.</li> + <li>Improved indentation for the <a href="../mode/coffeescript/index.html">CoffeeScript mode</a>.</li> + <li>Make non-printable-character representation <a href="manual.html#option_specialChars">configurable</a>.</li> + <li>Add ‘notification’ functionality to <a href="manual.html#addon_dialog">dialog</a> addon.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/3.19.0...3.20.0">list of patches</a>.</li> + </ul> + + <p class="rel">21-10-2013: <a href="http://codemirror.net/codemirror-3.19.zip">Version 3.19</a>:</p> + + <ul class="rel-note"> + <li>New modes: <a href="../mode/eiffel/index.html">Eiffel</a>, <a href="../mode/gherkin/index.html">Gherkin</a>, <a href="../mode/sql/?mime=text/x-mssql">MSSQL dialect</a>.</li> + <li>New addons: <a href="manual.html#addon_hardwrap">hardwrap</a>, <a href="manual.html#addon_sql-hint">sql-hint</a>.</li> + <li>New theme: <a href="../demo/theme.html#mbo">MBO</a>.</li> + <li>Add <a href="manual.html#token_style_line">support</a> for line-level styling from mode tokenizers.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/3.18.0...3.19.0">list of patches</a>.</li> + </ul> + + <p class="rel">23-09-2013: <a href="http://codemirror.net/codemirror-3.18.zip">Version 3.18</a>:</p> + + <p class="rel-note">Emergency release to fix a problem in 3.17 + where <code>.setOption("lineNumbers", false)</code> would raise an + error.</p> + + <p class="rel">23-09-2013: <a href="http://codemirror.net/codemirror-3.17.zip">Version 3.17</a>:</p> + + <ul class="rel-note"> + <li>New modes: <a href="../mode/fortran/index.html">Fortran</a>, <a href="../mode/octave/index.html">Octave</a> (Matlab), <a href="../mode/toml/index.html">TOML</a>, and <a href="../mode/dtd/index.html">DTD</a>.</li> + <li>New addons: <a href="../addon/lint/css-lint.js"><code>css-lint</code></a>, <a href="manual.html#addon_css-hint"><code>css-hint</code></a>.</li> + <li>Improve resilience to CSS 'frameworks' that globally mess up <code>box-sizing</code>.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/3.16.0...3.17.0">list of patches</a>.</li> + </ul> + + <p class="rel">21-08-2013: <a href="http://codemirror.net/codemirror-3.16.zip">Version 3.16</a>:</p> + + <ul class="rel-note"> + <li>The whole codebase is now under a single <a href="../LICENSE">license</a> file.</li> + <li>The project page was overhauled and redesigned.</li> + <li>New themes: <a href="../demo/theme.html#paraiso-dark">Paraiso</a> (<a href="../demo/theme.html#paraiso-light">light</a>), <a href="../demo/theme.html#the-matrix">The Matrix</a>.</li> + <li>Improved interaction between themes and <a href="manual.html#addon_active-line">active-line</a>/<a href="manual.html#addon_matchbrackets">matchbrackets</a> addons.</li> + <li>New <a href="manual.html#addon_foldcode">folding</a> function <code>CodeMirror.fold.comment</code>.</li> + <li>Added <a href="manual.html#addon_fullscreen">fullscreen</a> addon.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/3.15.0...3.16.0">list of patches</a>.</li> + </ul> + + <p class="rel">29-07-2013: <a href="http://codemirror.net/codemirror-3.15.zip">Version 3.15</a>:</p> + + <ul class="rel-note"> + <li>New modes: <a href="../mode/jade/index.html">Jade</a>, <a href="../mode/nginx/index.html">Nginx</a>.</li> + <li>New addons: <a href="../demo/tern.html">Tern</a>, <a href="manual.html#addon_matchtags">matchtags</a>, and <a href="manual.html#addon_foldgutter">foldgutter</a>.</li> + <li>Introduced <a href="manual.html#getHelper"><em>helper</em></a> concept (<a href="https://groups.google.com/forum/#!msg/codemirror/cOc0xvUUEUU/nLrX1-qnidgJ">context</a>).</li> + <li>New method: <a href="manual.html#getModeAt"><code>getModeAt</code></a>.</li> + <li>New themes: base16 <a href="../demo/theme.html#base16-dark">dark</a>/<a href="../demo/theme.html#base16-light">light</a>, 3024 <a href="../demo/theme.html#3024-night">dark</a>/<a href="../demo/theme.html#3024-day">light</a>, <a href="../demo/theme.html#tomorrow-night-eighties">tomorrow-night</a>.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/3.14.0...3.15.0">list of patches</a>.</li> + </ul> + + <p class="rel">20-06-2013: <a href="http://codemirror.net/codemirror-3.14.zip">Version 3.14</a>:</p> + + <ul class="rel-note"> + <li>New + addons: <a href="manual.html#addon_trailingspace">trailing + space highlight</a>, <a href="manual.html#addon_xml-hint">XML + completion</a> (rewritten), + and <a href="manual.html#addon_merge">diff merging</a>.</li> + <li><a href="manual.html#markText"><code>markText</code></a> + and <a href="manual.html#addLineWidget"><code>addLineWidget</code></a> + now take a <code>handleMouseEvents</code> option.</li> + <li>New methods: <a href="manual.html#lineAtHeight"><code>lineAtHeight</code></a>, + <a href="manual.html#getTokenTypeAt"><code>getTokenTypeAt</code></a>.</li> + <li>More precise cleanness-tracking + using <a href="manual.html#changeGeneration"><code>changeGeneration</code></a> + and <a href="manual.html#isClean"><code>isClean</code></a>.</li> + <li>Many extensions to <a href="../demo/emacs.html">Emacs</a> mode + (prefixes, more navigation units, and more).</li> + <li>New + events <a href="manual.html#event_keyHandled"><code>"keyHandled"</code></a> + and <a href="manual.html#event_inputRead"><code>"inputRead"</code></a>.</li> + <li>Various improvements to <a href="../mode/ruby/index.html">Ruby</a>, + <a href="../mode/smarty/index.html">Smarty</a>, <a href="../mode/sql/index.html">SQL</a>, + and <a href="../demo/vim.html">Vim</a> modes.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/3.13.0...3.14.0">list of patches</a>.</li> + </ul> + + <p class="rel">20-05-2013: <a href="http://codemirror.net/codemirror-3.13.zip">Version 3.13</a>:</p> + + <ul class="rel-note"> + <li>New modes: <a href="../mode/cobol/index.html">COBOL</a> and <a href="../mode/haml/index.html">HAML</a>.</li> + <li>New options: <a href="manual.html#option_cursorScrollMargin"><code>cursorScrollMargin</code></a> and <a href="manual.html#option_coverGutterNextToScrollbar"><code>coverGutterNextToScrollbar</code></a>.</li> + <li>New addon: <a href="manual.html#addon_comment">commenting</a>.</li> + <li>More features added to the <a href="../demo/vim.html">Vim keymap</a>.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/v3.12...3.13.0">list of patches</a>.</li> + </ul> + + <p class="rel">19-04-2013: <a href="http://codemirror.net/codemirror-3.12.zip">Version 3.12</a>:</p> + + <ul class="rel-note"> + <li>New mode: <a href="../mode/gas/index.html">GNU assembler</a>.</li> + <li>New + options: <a href="manual.html#option_maxHighlightLength"><code>maxHighlightLength</code></a> + and <a href="manual.html#option_historyEventDelay"><code>historyEventDelay</code></a>.</li> + <li>Added <a href="manual.html#mark_addToHistory"><code>addToHistory</code></a> + option for <code>markText</code>.</li> + <li>Various fixes to JavaScript tokenization and indentation corner cases.</li> + <li>Further improvements to the vim mode.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/v3.11...v3.12">list of patches</a>.</li> + </ul> + + <p class="rel">20-03-2013: <a href="http://codemirror.net/codemirror-3.11.zip">Version 3.11</a>:</p> + + <ul class="rel-note"> + <li><strong>Removed code:</strong> <code>collapserange</code>, + <code>formatting</code>, and <code>simple-hint</code> + addons. <code>plsql</code> and <code>mysql</code> modes + (use <a href="../mode/sql/index.html"><code>sql</code></a> mode).</li> + <li><strong>Moved code:</strong> the range-finding functions for folding now have <a href="../addon/fold/">their own files</a>.</li> + <li><strong>Changed interface:</strong> + the <a href="manual.html#addon_continuecomment"><code>continuecomment</code></a> + addon now exposes an option, rather than a command.</li> + <li>New + modes: <a href="../mode/css/scss.html">SCSS</a>, <a href="../mode/tcl/index.html">Tcl</a>, <a href="../mode/livescript/index.html">LiveScript</a>, + and <a href="../mode/mirc/index.html">mIRC</a>.</li> + <li>New addons: <a href="../demo/placeholder.html"><code>placeholder</code></a>, <a href="../demo/html5complete.html">HTML completion</a>.</li> + <li>New + methods: <a href="manual.html#hasFocus"><code>hasFocus</code></a>, <a href="manual.html#defaultCharWidth"><code>defaultCharWidth</code></a>.</li> + <li>New events: <a href="manual.html#event_beforeCursorEnter"><code>beforeCursorEnter</code></a>, <a href="manual.html#event_renderLine"><code>renderLine</code></a>.</li> + <li>Many improvements to the <a href="manual.html#addon_show-hint"><code>show-hint</code></a> completion + dialog addon.</li> + <li>Tweak behavior of by-word cursor motion.</li> + <li>Further improvements to the <a href="../demo/vim.html">vim mode</a>.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/v3.1...v3.11">list of patches</a>.</li> + </ul> + + <p class="rel">21-02-2013: <a href="http://codemirror.net/codemirror-3.1.zip">Version 3.1</a>:</p> + + <ul class="rel-note"> + <li><strong>Incompatible:</strong> key handlers may + now <em>return</em>, rather + than <em>throw</em> <code>CodeMirror.Pass</code> to signal they + didn't handle the key.</li> + <li>Make documents a <a href="manual.html#api_doc">first-class + construct</a>, support split views and subviews.</li> + <li>Add a <a href="manual.html#addon_show-hint">new module</a> + for showing completion hints. + Deprecate <code>simple-hint.js</code>.</li> + <li>Extend <a href="../mode/htmlmixed/index.html">htmlmixed mode</a> + to allow custom handling of script types.</li> + <li>Support an <code>insertLeft</code> option + to <a href="manual.html#setBookmark"><code>setBookmark</code></a>.</li> + <li>Add an <a href="manual.html#eachLine"><code>eachLine</code></a> + method to iterate over a document.</li> + <li>New addon modules: <a href="../demo/markselection.html">selection + marking</a>, <a href="../demo/lint.html">linting</a>, + and <a href="../demo/closebrackets.html">automatic bracket + closing</a>.</li> + <li>Add <a href="manual.html#event_beforeChange"><code>"beforeChange"</code></a> + and <a href="manual.html#event_beforeSelectionChange"><code>"beforeSelectionChange"</code></a> + events.</li> + <li>Add <a href="manual.html#event_hide"><code>"hide"</code></a> + and <a href="manual.html#event_unhide"><code>"unhide"</code></a> + events to marked ranges.</li> + <li>Fix <a href="manual.html#coordsChar"><code>coordsChar</code></a>'s + interpretation of its argument to match the documentation.</li> + <li>New modes: <a href="../mode/turtle/index.html">Turtle</a> + and <a href="../mode/q/index.html">Q</a>.</li> + <li>Further improvements to the <a href="../demo/vim.html">vim mode</a>.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/v3.01...v3.1">list of patches</a>.</li> + </ul> + + + <p class="rel">25-01-2013: <a href="http://codemirror.net/codemirror-3.02.zip">Version 3.02</a>:</p> + + <p class="rel-note">Single-bugfix release. Fixes a problem that + prevents CodeMirror instances from being garbage-collected after + they become unused.</p> + + <p class="rel">21-01-2013: <a href="http://codemirror.net/codemirror-3.01.zip">Version 3.01</a>:</p> + + <ul class="rel-note"> + <li>Move all add-ons into an organized directory structure + under <a href="../addon/"><code>/addon</code></a>. <strong>You might have to adjust your + paths.</strong></li> + <li>New + modes: <a href="../mode/d/index.html">D</a>, <a href="../mode/sass/index.html">Sass</a>, <a href="../mode/apl/index.html">APL</a>, <a href="../mode/sql/index.html">SQL</a> + (configurable), and <a href="../mode/asterisk/index.html">Asterisk</a>.</li> + <li>Several bugfixes in right-to-left text support.</li> + <li>Add <a href="manual.html#option_rtlMoveVisually"><code>rtlMoveVisually</code></a> option.</li> + <li>Improvements to vim keymap.</li> + <li>Add built-in (lightweight) <a href="manual.html#addOverlay">overlay mode</a> support.</li> + <li>Support <code>showIfHidden</code> option for <a href="manual.html#addLineWidget">line widgets</a>.</li> + <li>Add simple <a href="manual.html#addon_python-hint">Python hinter</a>.</li> + <li>Bring back the <a href="manual.html#option_fixedGutter"><code>fixedGutter</code></a> option.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/v3.0...v3.01">list of patches</a>.</li> + </ul> + + <p class="rel">10-12-2012: <a href="http://codemirror.net/codemirror-3.0.zip">Version 3.0</a>:</p> + + <p class="rel-note"><strong>New major version</strong>. Only + partially backwards-compatible. See + the <a href="upgrade_v3.html">upgrading guide</a> for more + information. Changes since release candidate 2:</p> + + <ul class="rel-note"> + <li>Rewritten VIM mode.</li> + <li>Fix a few minor scrolling and sizing issues.</li> + <li>Work around Safari segfault when dragging.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/v3.0rc2...v3.0">list of patches</a>.</li> + </ul> + + <p class="rel">20-11-2012: <a href="http://codemirror.net/codemirror-3.0rc2.zip">Version 3.0, release candidate 2</a>:</p> + + <ul class="rel-note"> + <li>New mode: <a href="../mode/http/index.html">HTTP</a>.</li> + <li>Improved handling of selection anchor position.</li> + <li>Improve IE performance on longer lines.</li> + <li>Reduce gutter glitches during horiz. scrolling.</li> + <li>Add <a href="manual.html#addKeyMap"><code>addKeyMap</code></a> and <a href="manual.html#removeKeyMap"><code>removeKeyMap</code></a> methods.</li> + <li>Rewrite <code>formatting</code> and <code>closetag</code> add-ons.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/v3.0rc1...v3.0rc2">list of patches</a>.</li> + </ul> + + <p class="rel">20-11-2012: <a href="http://codemirror.net/codemirror-3.0rc1.zip">Version 3.0, release candidate 1</a>:</p> + + <ul class="rel-note"> + <li>New theme: <a href="../demo/theme.html#solarized%20light">Solarized</a>.</li> + <li>Introduce <a href="manual.html#addLineClass"><code>addLineClass</code></a> + and <a href="manual.html#removeLineClass"><code>removeLineClass</code></a>, + drop <code>setLineClass</code>.</li> + <li>Add a <em>lot</em> of + new <a href="manual.html#markText">options for marked text</a> + (read-only, atomic, collapsed, widget replacement).</li> + <li>Remove the old code folding interface in favour of these new ranges.</li> + <li>Add <a href="manual.html#isClean"><code>isClean</code></a>/<a href="manual.html#markClean"><code>markClean</code></a> methods.</li> + <li>Remove <code>compoundChange</code> method, use better undo-event-combining heuristic.</li> + <li>Improve scrolling performance smoothness.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/v3.0beta2...v3.0rc1">list of patches</a>.</li> + </ul> + + <p class="rel">22-10-2012: <a href="http://codemirror.net/codemirror-3.0beta2.zip">Version 3.0, beta 2</a>:</p> + + <ul class="rel-note"> + <li>Fix page-based coordinate computation.</li> + <li>Fix firing of <a href="manual.html#event_gutterClick"><code>gutterClick</code></a> event.</li> + <li>Add <a href="manual.html#option_cursorHeight"><code>cursorHeight</code></a> option.</li> + <li>Fix bi-directional text regression.</li> + <li>Add <a href="manual.html#option_viewportMargin"><code>viewportMargin</code></a> option.</li> + <li>Directly handle mousewheel events (again, hopefully better).</li> + <li>Make vertical cursor movement more robust (through widgets, big line gaps).</li> + <li>Add <a href="manual.html#option_flattenSpans"><code>flattenSpans</code></a> option.</li> + <li>Many optimizations. Poor responsiveness should be fixed.</li> + <li>Initialization in hidden state works again.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/v3.0beta1...v3.0beta2">list of patches</a>.</li> + </ul> + + <p class="rel">19-09-2012: <a href="http://codemirror.net/codemirror-3.0beta1.zip">Version 3.0, beta 1</a>:</p> + + <ul class="rel-note"> + <li>Bi-directional text support.</li> + <li>More powerful gutter model.</li> + <li>Support for arbitrary text/widget height.</li> + <li>In-line widgets.</li> + <li>Generalized event handling.</li> + </ul> + +</section> + +<section id=v2> + + <h2>Version 2.x</h2> + + <p class="rel">21-01-2013: <a href="http://codemirror.net/codemirror-2.38.zip">Version 2.38</a>:</p> + + <p class="rel-note">Integrate some bugfixes, enhancements to the vim keymap, and new + modes + (<a href="../mode/d/index.html">D</a>, <a href="../mode/sass/index.html">Sass</a>, <a href="../mode/apl/index.html">APL</a>) + from the v3 branch.</p> + + <p class="rel">20-12-2012: <a href="http://codemirror.net/codemirror-2.37.zip">Version 2.37</a>:</p> + + <ul class="rel-note"> + <li>New mode: <a href="../mode/sql/index.html">SQL</a> (will replace <a href="../mode/plsql/index.html">plsql</a> and <a href="../mode/mysql/index.html">mysql</a> modes).</li> + <li>Further work on the new VIM mode.</li> + <li>Fix Cmd/Ctrl keys on recent Operas on OS X.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/v2.36...v2.37">list of patches</a>.</li> + </ul> + + <p class="rel">20-11-2012: <a href="http://codemirror.net/codemirror-2.36.zip">Version 2.36</a>:</p> + + <ul class="rel-note"> + <li>New mode: <a href="../mode/z80/index.html">Z80 assembly</a>.</li> + <li>New theme: <a href="../demo/theme.html#twilight">Twilight</a>.</li> + <li>Add command-line compression helper.</li> + <li>Make <a href="manual.html#scrollIntoView"><code>scrollIntoView</code></a> public.</li> + <li>Add <a href="manual.html#defaultTextHeight"><code>defaultTextHeight</code></a> method.</li> + <li>Various extensions to the vim keymap.</li> + <li>Make <a href="../mode/php/index.html">PHP mode</a> build on <a href="../mode/htmlmixed/index.html">mixed HTML mode</a>.</li> + <li>Add <a href="manual.html#addon_continuecomment">comment-continuing</a> add-on.</li> + <li>Full <a href="../https://github.com/codemirror/CodeMirror/compare/v2.35...v2.36">list of patches</a>.</li> + </ul> + + <p class="rel">22-10-2012: <a href="http://codemirror.net/codemirror-2.35.zip">Version 2.35</a>:</p> + + <ul class="rel-note"> + <li>New (sub) mode: <a href="../mode/javascript/typescript.html">TypeScript</a>.</li> + <li>Don't overwrite (insert key) when pasting.</li> + <li>Fix several bugs in <a href="manual.html#markText"><code>markText</code></a>/undo interaction.</li> + <li>Better indentation of JavaScript code without semicolons.</li> + <li>Add <a href="manual.html#defineInitHook"><code>defineInitHook</code></a> function.</li> + <li>Full <a href="https://github.com/codemirror/CodeMirror/compare/v2.34...v2.35">list of patches</a>.</li> + </ul> + + <p class="rel">19-09-2012: <a href="http://codemirror.net/codemirror-2.34.zip">Version 2.34</a>:</p> + + <ul class="rel-note"> + <li>New mode: <a href="../mode/commonlisp/index.html">Common Lisp</a>.</li> + <li>Fix right-click select-all on most browsers.</li> + <li>Change the way highlighting happens:<br>&nbsp; Saves memory and CPU cycles.<br>&nbsp; <code>compareStates</code> is no longer needed.<br>&nbsp; <code>onHighlightComplete</code> no longer works.</li> + <li>Integrate mode (Markdown, XQuery, CSS, sTex) tests in central testsuite.</li> + <li>Add a <a href="manual.html#version"><code>CodeMirror.version</code></a> property.</li> + <li>More robust handling of nested modes in <a href="../demo/formatting.html">formatting</a> and <a href="../demo/closetag.html">closetag</a> plug-ins.</li> + <li>Un/redo now preserves <a href="manual.html#markText">marked text</a> and bookmarks.</li> + <li><a href="https://github.com/codemirror/CodeMirror/compare/v2.33...v2.34">Full list</a> of patches.</li> + </ul> + + <p class="rel">23-08-2012: <a href="http://codemirror.net/codemirror-2.33.zip">Version 2.33</a>:</p> + + <ul class="rel-note"> + <li>New mode: <a href="../mode/sieve/index.html">Sieve</a>.</li> + <li>New <a href="manual.html#getViewport"><code>getViewPort</code></a> and <a href="manual.html#option_onViewportChange"><code>onViewportChange</code></a> API.</li> + <li><a href="manual.html#option_cursorBlinkRate">Configurable</a> cursor blink rate.</li> + <li>Make binding a key to <code>false</code> disabling handling (again).</li> + <li>Show non-printing characters as red dots.</li> + <li>More tweaks to the scrolling model.</li> + <li>Expanded testsuite. Basic linter added.</li> + <li>Remove most uses of <code>innerHTML</code>. Remove <code>CodeMirror.htmlEscape</code>.</li> + <li><a href="https://github.com/codemirror/CodeMirror/compare/v2.32...v2.33">Full list</a> of patches.</li> + </ul> + + <p class="rel">23-07-2012: <a href="http://codemirror.net/codemirror-2.32.zip">Version 2.32</a>:</p> + + <p class="rel-note">Emergency fix for a bug where an editor with + line wrapping on IE will break when there is <em>no</em> + scrollbar.</p> + + <p class="rel">20-07-2012: <a href="http://codemirror.net/codemirror-2.31.zip">Version 2.31</a>:</p> + + <ul class="rel-note"> + <li>New modes: <a href="../mode/ocaml/index.html">OCaml</a>, <a href="../mode/haxe/index.html">Haxe</a>, and <a href="../mode/vb/index.html">VB.NET</a>.</li> + <li>Several fixes to the new scrolling model.</li> + <li>Add a <a href="manual.html#setSize"><code>setSize</code></a> method for programmatic resizing.</li> + <li>Add <a href="manual.html#getHistory"><code>getHistory</code></a> and <a href="manual.html#setHistory"><code>setHistory</code></a> methods.</li> + <li>Allow custom line separator string in <a href="manual.html#getValue"><code>getValue</code></a> and <a href="manual.html#getRange"><code>getRange</code></a>.</li> + <li>Support double- and triple-click drag, double-clicking whitespace.</li> + <li>And more... <a href="https://github.com/codemirror/CodeMirror/compare/v2.3...v2.31">(all patches)</a></li> + </ul> + + <p class="rel">22-06-2012: <a href="http://codemirror.net/codemirror-2.3.zip">Version 2.3</a>:</p> + + <ul class="rel-note"> + <li><strong>New scrollbar implementation</strong>. Should flicker less. Changes DOM structure of the editor.</li> + <li>New theme: <a href="../demo/theme.html#vibrant-ink">vibrant-ink</a>.</li> + <li>Many extensions to the VIM keymap (including text objects).</li> + <li>Add <a href="../demo/multiplex.html">mode-multiplexing</a> utility script.</li> + <li>Fix bug where right-click paste works in read-only mode.</li> + <li>Add a <a href="manual.html#getScrollInfo"><code>getScrollInfo</code></a> method.</li> + <li>Lots of other <a href="https://github.com/codemirror/CodeMirror/compare/v2.25...v2.3">fixes</a>.</li> + </ul> + + <p class="rel">23-05-2012: <a href="http://codemirror.net/codemirror-2.25.zip">Version 2.25</a>:</p> + + <ul class="rel-note"> + <li>New mode: <a href="../mode/erlang/index.html">Erlang</a>.</li> + <li><strong>Remove xmlpure mode</strong> (use <a href="../mode/xml/index.html">xml.js</a>).</li> + <li>Fix line-wrapping in Opera.</li> + <li>Fix X Windows middle-click paste in Chrome.</li> + <li>Fix bug that broke pasting of huge documents.</li> + <li>Fix backspace and tab key repeat in Opera.</li> + </ul> + + <p class="rel">23-04-2012: <a href="http://codemirror.net/codemirror-2.24.zip">Version 2.24</a>:</p> + + <ul class="rel-note"> + <li><strong>Drop support for Internet Explorer 6</strong>.</li> + <li>New + modes: <a href="../mode/shell/index.html">Shell</a>, <a href="../mode/tiki/index.html">Tiki + wiki</a>, <a href="../mode/pig/index.html">Pig Latin</a>.</li> + <li>New themes: <a href="../demo/theme.html#ambiance">Ambiance</a>, <a href="../demo/theme.html#blackboard">Blackboard</a>.</li> + <li>More control over drag/drop + with <a href="manual.html#option_dragDrop"><code>dragDrop</code></a> + and <a href="manual.html#option_onDragEvent"><code>onDragEvent</code></a> + options.</li> + <li>Make HTML mode a bit less pedantic.</li> + <li>Add <a href="manual.html#compoundChange"><code>compoundChange</code></a> API method.</li> + <li>Several fixes in undo history and line hiding.</li> + <li>Remove (broken) support for <code>catchall</code> in key maps, + add <code>nofallthrough</code> boolean field instead.</li> + </ul> + + <p class="rel">26-03-2012: <a href="http://codemirror.net/codemirror-2.23.zip">Version 2.23</a>:</p> + + <ul class="rel-note"> + <li>Change <strong>default binding for tab</strong> <a href="javascript:void(document.getElementById('tabbinding').style.display='')">[more]</a> + <div style="display: none" id=tabbinding> + Starting in 2.23, these bindings are default: + <ul><li>Tab: Insert tab character</li> + <li>Shift-tab: Reset line indentation to default</li> + <li>Ctrl/Cmd-[: Reduce line indentation (old tab behaviour)</li> + <li>Ctrl/Cmd-]: Increase line indentation (old shift-tab behaviour)</li> + </ul> + </div> + </li> + <li>New modes: <a href="../mode/xquery/index.html">XQuery</a> and <a href="../mode/vbscript/index.html">VBScript</a>.</li> + <li>Two new themes: <a href="../mode/less/index.html">lesser-dark</a> and <a href="../mode/xquery/index.html">xq-dark</a>.</li> + <li>Differentiate between background and text styles in <a href="manual.html#setLineClass"><code>setLineClass</code></a>.</li> + <li>Fix drag-and-drop in IE9+.</li> + <li>Extend <a href="manual.html#charCoords"><code>charCoords</code></a> + and <a href="manual.html#cursorCoords"><code>cursorCoords</code></a> with a <code>mode</code> argument.</li> + <li>Add <a href="manual.html#option_autofocus"><code>autofocus</code></a> option.</li> + <li>Add <a href="manual.html#findMarksAt"><code>findMarksAt</code></a> method.</li> + </ul> + + <p class="rel">27-02-2012: <a href="http://codemirror.net/codemirror-2.22.zip">Version 2.22</a>:</p> + + <ul class="rel-note"> + <li>Allow <a href="manual.html#keymaps">key handlers</a> to pass up events, allow binding characters.</li> + <li>Add <a href="manual.html#option_autoClearEmptyLines"><code>autoClearEmptyLines</code></a> option.</li> + <li>Properly use tab stops when rendering tabs.</li> + <li>Make PHP mode more robust.</li> + <li>Support indentation blocks in <a href="manual.html#addon_foldcode">code folder</a>.</li> + <li>Add a script for <a href="manual.html#addon_match-highlighter">highlighting instances of the selection</a>.</li> + <li>New <a href="../mode/properties/index.html">.properties</a> mode.</li> + <li>Fix many bugs.</li> + </ul> + + <p class="rel">27-01-2012: <a href="http://codemirror.net/codemirror-2.21.zip">Version 2.21</a>:</p> + + <ul class="rel-note"> + <li>Added <a href="../mode/less/index.html">LESS</a>, <a href="../mode/mysql/index.html">MySQL</a>, + <a href="../mode/go/index.html">Go</a>, and <a href="../mode/verilog/index.html">Verilog</a> modes.</li> + <li>Add <a href="manual.html#option_smartIndent"><code>smartIndent</code></a> + option.</li> + <li>Support a cursor in <a href="manual.html#option_readOnly"><code>readOnly</code></a>-mode.</li> + <li>Support assigning multiple styles to a token.</li> + <li>Use a new approach to drawing the selection.</li> + <li>Add <a href="manual.html#scrollTo"><code>scrollTo</code></a> method.</li> + <li>Allow undo/redo events to span non-adjacent lines.</li> + <li>Lots and lots of bugfixes.</li> + </ul> + + <p class="rel">20-12-2011: <a href="http://codemirror.net/codemirror-2.2.zip">Version 2.2</a>:</p> + + <ul class="rel-note"> + <li>Slightly incompatible API changes. Read <a href="upgrade_v2.2.html">this</a>.</li> + <li>New approach + to <a href="manual.html#option_extraKeys">binding</a> keys, + support for <a href="manual.html#option_keyMap">custom + bindings</a>.</li> + <li>Support for overwrite (insert).</li> + <li><a href="manual.html#option_tabSize">Custom-width</a> + and <a href="../demo/visibletabs.html">stylable</a> tabs.</li> + <li>Moved more code into <a href="manual.html#addons">add-on scripts</a>.</li> + <li>Support for sane vertical cursor movement in wrapped lines.</li> + <li>More reliable handling of + editing <a href="manual.html#markText">marked text</a>.</li> + <li>Add minimal <a href="../demo/emacs.html">emacs</a> + and <a href="../demo/vim.html">vim</a> bindings.</li> + <li>Rename <code>coordsFromIndex</code> + to <a href="manual.html#posFromIndex"><code>posFromIndex</code></a>, + add <a href="manual.html#indexFromPos"><code>indexFromPos</code></a> + method.</li> + </ul> + + <p class="rel">21-11-2011: <a href="http://codemirror.net/codemirror-2.18.zip">Version 2.18</a>:</p> + <p class="rel-note">Fixes <code>TextMarker.clear</code>, which is broken in 2.17.</p> + + <p class="rel">21-11-2011: <a href="http://codemirror.net/codemirror-2.17.zip">Version 2.17</a>:</p> + <ul class="rel-note"> + <li>Add support for <a href="manual.html#option_lineWrapping">line + wrapping</a> and <a href="manual.html#hideLine">code + folding</a>.</li> + <li>Add <a href="../mode/gfm/index.html">Github-style Markdown</a> mode.</li> + <li>Add <a href="../theme/monokai.css">Monokai</a> + and <a href="../theme/rubyblue.css">Rubyblue</a> themes.</li> + <li>Add <a href="manual.html#setBookmark"><code>setBookmark</code></a> method.</li> + <li>Move some of the demo code into reusable components + under <a href="../addon/"><code>lib/util</code></a>.</li> + <li>Make screen-coord-finding code faster and more reliable.</li> + <li>Fix drag-and-drop in Firefox.</li> + <li>Improve support for IME.</li> + <li>Speed up content rendering.</li> + <li>Fix browser's built-in search in Webkit.</li> + <li>Make double- and triple-click work in IE.</li> + <li>Various fixes to modes.</li> + </ul> + + <p class="rel">27-10-2011: <a href="http://codemirror.net/codemirror-2.16.zip">Version 2.16</a>:</p> + <ul class="rel-note"> + <li>Add <a href="../mode/perl/index.html">Perl</a>, <a href="../mode/rust/index.html">Rust</a>, <a href="../mode/tiddlywiki/index.html">TiddlyWiki</a>, and <a href="../mode/groovy/index.html">Groovy</a> modes.</li> + <li>Dragging text inside the editor now moves, rather than copies.</li> + <li>Add a <a href="manual.html#coordsFromIndex"><code>coordsFromIndex</code></a> method.</li> + <li><strong>API change</strong>: <code>setValue</code> now no longer clears history. Use <a href="manual.html#clearHistory"><code>clearHistory</code></a> for that.</li> + <li><strong>API change</strong>: <a href="manual.html#markText"><code>markText</code></a> now + returns an object with <code>clear</code> and <code>find</code> + methods. Marked text is now more robust when edited.</li> + <li>Fix editing code with tabs in Internet Explorer.</li> + </ul> + + <p class="rel">26-09-2011: <a href="http://codemirror.net/codemirror-2.15.zip">Version 2.15</a>:</p> + <p class="rel-note">Fix bug that snuck into 2.14: Clicking the + character that currently has the cursor didn't re-focus the + editor.</p> + + <p class="rel">26-09-2011: <a href="http://codemirror.net/codemirror-2.14.zip">Version 2.14</a>:</p> + <ul class="rel-note"> + <li>Add <a href="../mode/clojure/index.html">Clojure</a>, <a href="../mode/pascal/index.html">Pascal</a>, <a href="../mode/ntriples/index.html">NTriples</a>, <a href="../mode/jinja2/index.html">Jinja2</a>, and <a href="../mode/markdown/index.html">Markdown</a> modes.</li> + <li>Add <a href="../theme/cobalt.css">Cobalt</a> and <a href="../theme/eclipse.css">Eclipse</a> themes.</li> + <li>Add a <a href="manual.html#option_fixedGutter"><code>fixedGutter</code></a> option.</li> + <li>Fix bug with <code>setValue</code> breaking cursor movement.</li> + <li>Make gutter updates much more efficient.</li> + <li>Allow dragging of text out of the editor (on modern browsers).</li> + </ul> + + + <p class="rel">23-08-2011: <a href="http://codemirror.net/codemirror-2.13.zip">Version 2.13</a>:</p> + <ul class="rel-note"> + <li>Add <a href="../mode/ruby/index.html">Ruby</a>, <a href="../mode/r/index.html">R</a>, <a href="../mode/coffeescript/index.html">CoffeeScript</a>, and <a href="../mode/velocity/index.html">Velocity</a> modes.</li> + <li>Add <a href="manual.html#getGutterElement"><code>getGutterElement</code></a> to API.</li> + <li>Several fixes to scrolling and positioning.</li> + <li>Add <a href="manual.html#option_smartHome"><code>smartHome</code></a> option.</li> + <li>Add an experimental <a href="../mode/xmlpure/index.html">pure XML</a> mode.</li> + </ul> + + <p class="rel">25-07-2011: <a href="http://codemirror.net/codemirror-2.12.zip">Version 2.12</a>:</p> + <ul class="rel-note"> + <li>Add a <a href="../mode/sparql/index.html">SPARQL</a> mode.</li> + <li>Fix bug with cursor jumping around in an unfocused editor in IE.</li> + <li>Allow key and mouse events to bubble out of the editor. Ignore widget clicks.</li> + <li>Solve cursor flakiness after undo/redo.</li> + <li>Fix block-reindent ignoring the last few lines.</li> + <li>Fix parsing of multi-line attrs in XML mode.</li> + <li>Use <code>innerHTML</code> for HTML-escaping.</li> + <li>Some fixes to indentation in C-like mode.</li> + <li>Shrink horiz scrollbars when long lines removed.</li> + <li>Fix width feedback loop bug that caused the width of an inner DIV to shrink.</li> + </ul> + + <p class="rel">04-07-2011: <a href="http://codemirror.net/codemirror-2.11.zip">Version 2.11</a>:</p> + <ul class="rel-note"> + <li>Add a <a href="../mode/scheme/index.html">Scheme mode</a>.</li> + <li>Add a <code>replace</code> method to search cursors, for cursor-preserving replacements.</li> + <li>Make the <a href="../mode/clike/index.html">C-like mode</a> mode more customizable.</li> + <li>Update XML mode to spot mismatched tags.</li> + <li>Add <code>getStateAfter</code> API and <code>compareState</code> mode API methods for finer-grained mode magic.</li> + <li>Add a <code>getScrollerElement</code> API method to manipulate the scrolling DIV.</li> + <li>Fix drag-and-drop for Firefox.</li> + <li>Add a C# configuration for the <a href="../mode/clike/index.html">C-like mode</a>.</li> + <li>Add <a href="../demo/fullscreen.html">full-screen editing</a> and <a href="../demo/changemode.html">mode-changing</a> demos.</li> + </ul> + + <p class="rel">07-06-2011: <a href="http://codemirror.net/codemirror-2.1.zip">Version 2.1</a>:</p> + <p class="rel-note">Add + a <a href="manual.html#option_theme">theme</a> system + (<a href="../demo/theme.html">demo</a>). Note that this is not + backwards-compatible—you'll have to update your styles and + modes!</p> + + <p class="rel">07-06-2011: <a href="http://codemirror.net/codemirror-2.02.zip">Version 2.02</a>:</p> + <ul class="rel-note"> + <li>Add a <a href="../mode/lua/index.html">Lua mode</a>.</li> + <li>Fix reverse-searching for a regexp.</li> + <li>Empty lines can no longer break highlighting.</li> + <li>Rework scrolling model (the outer wrapper no longer does the scrolling).</li> + <li>Solve horizontal jittering on long lines.</li> + <li>Add <a href="../demo/runmode.html">runmode.js</a>.</li> + <li>Immediately re-highlight text when typing.</li> + <li>Fix problem with 'sticking' horizontal scrollbar.</li> + </ul> + + <p class="rel">26-05-2011: <a href="http://codemirror.net/codemirror-2.01.zip">Version 2.01</a>:</p> + <ul class="rel-note"> + <li>Add a <a href="../mode/smalltalk/index.html">Smalltalk mode</a>.</li> + <li>Add a <a href="../mode/rst/index.html">reStructuredText mode</a>.</li> + <li>Add a <a href="../mode/python/index.html">Python mode</a>.</li> + <li>Add a <a href="../mode/plsql/index.html">PL/SQL mode</a>.</li> + <li><code>coordsChar</code> now works</li> + <li>Fix a problem where <code>onCursorActivity</code> interfered with <code>onChange</code>.</li> + <li>Fix a number of scrolling and mouse-click-position glitches.</li> + <li>Pass information about the changed lines to <code>onChange</code>.</li> + <li>Support cmd-up/down on OS X.</li> + <li>Add triple-click line selection.</li> + <li>Don't handle shift when changing the selection through the API.</li> + <li>Support <code>"nocursor"</code> mode for <code>readOnly</code> option.</li> + <li>Add an <code>onHighlightComplete</code> option.</li> + <li>Fix the context menu for Firefox.</li> + </ul> + + <p class="rel">28-03-2011: <a href="http://codemirror.net/codemirror-2.0.zip">Version 2.0</a>:</p> + <p class="rel-note">CodeMirror 2 is a complete rewrite that's + faster, smaller, simpler to use, and less dependent on browser + quirks. See <a href="internals.html">this</a> + and <a href="http://groups.google.com/group/codemirror/browse_thread/thread/5a8e894024a9f580">this</a> + for more information.</p> + + <p class="rel">22-02-2011: <a href="https://github.com/codemirror/codemirror/tree/beta2">Version 2.0 beta 2</a>:</p> + <p class="rel-note">Somewhat more mature API, lots of bugs shaken out.</p> + + <p class="rel">17-02-2011: <a href="http://codemirror.net/codemirror-0.94.zip">Version 0.94</a>:</p> + <ul class="rel-note"> + <li><code>tabMode: "spaces"</code> was modified slightly (now indents when something is selected).</li> + <li>Fixes a bug that would cause the selection code to break on some IE versions.</li> + <li>Disabling spell-check on WebKit browsers now works.</li> + </ul> + + <p class="rel">08-02-2011: <a href="http://codemirror.net/">Version 2.0 beta 1</a>:</p> + <p class="rel-note">CodeMirror 2 is a complete rewrite of + CodeMirror, no longer depending on an editable frame.</p> + + <p class="rel">19-01-2011: <a href="http://codemirror.net/codemirror-0.93.zip">Version 0.93</a>:</p> + <ul class="rel-note"> + <li>Added a <a href="contrib/regex/index.html">Regular Expression</a> parser.</li> + <li>Fixes to the PHP parser.</li> + <li>Support for regular expression in search/replace.</li> + <li>Add <code>save</code> method to instances created with <code>fromTextArea</code>.</li> + <li>Add support for MS T-SQL in the SQL parser.</li> + <li>Support use of CSS classes for highlighting brackets.</li> + <li>Fix yet another hang with line-numbering in hidden editors.</li> + </ul> +</section> + +<section id=v1> + + <h2>Version 0.x</h2> + + <p class="rel">28-03-2011: <a href="http://codemirror.net/codemirror-1.0.zip">Version 1.0</a>:</p> + <ul class="rel-note"> + <li>Fix error when debug history overflows.</li> + <li>Refine handling of C# verbatim strings.</li> + <li>Fix some issues with JavaScript indentation.</li> + </ul> + + <p class="rel">17-12-2010: <a href="http://codemirror.net/codemirror-0.92.zip">Version 0.92</a>:</p> + <ul class="rel-note"> + <li>Make CodeMirror work in XHTML documents.</li> + <li>Fix bug in handling of backslashes in Python strings.</li> + <li>The <code>styleNumbers</code> option is now officially + supported and documented.</li> + <li><code>onLineNumberClick</code> option added.</li> + <li>More consistent names <code>onLoad</code> and + <code>onCursorActivity</code> callbacks. Old names still work, but + are deprecated.</li> + <li>Add a <a href="contrib/freemarker/index.html">Freemarker</a> mode.</li> + </ul> + + <p class="rel">11-11-2010: <a + href="http://codemirror.net/codemirror-0.91.zip">Version 0.91</a>:</p> + <ul class="rel-note"> + <li>Adds support for <a href="contrib/java">Java</a>.</li> + <li>Small additions to the <a href="contrib/php">PHP</a> and <a href="contrib/sql">SQL</a> parsers.</li> + <li>Work around various <a href="https://bugs.webkit.org/show_bug.cgi?id=47806">Webkit</a> <a href="https://bugs.webkit.org/show_bug.cgi?id=23474">issues</a>.</li> + <li>Fix <code>toTextArea</code> to update the code in the textarea.</li> + <li>Add a <code>noScriptCaching</code> option (hack to ease development).</li> + <li>Make sub-modes of <a href="mixedtest.html">HTML mixed</a> mode configurable.</li> + </ul> + + <p class="rel">02-10-2010: <a + href="http://codemirror.net/codemirror-0.9.zip">Version 0.9</a>:</p> + <ul class="rel-note"> + <li>Add support for searching backwards.</li> + <li>There are now parsers for <a href="contrib/scheme/index.html">Scheme</a>, <a href="contrib/xquery/index.html">XQuery</a>, and <a href="contrib/ometa/index.html">OmetaJS</a>.</li> + <li>Makes <code>height: "dynamic"</code> more robust.</li> + <li>Fixes bug where paste did not work on OS X.</li> + <li>Add a <code>enterMode</code> and <code>electricChars</code> options to make indentation even more customizable.</li> + <li>Add <code>firstLineNumber</code> option.</li> + <li>Fix bad handling of <code>@media</code> rules by the CSS parser.</li> + <li>Take a new, more robust approach to working around the invisible-last-line bug in WebKit.</li> + </ul> + + <p class="rel">22-07-2010: <a + href="http://codemirror.net/codemirror-0.8.zip">Version 0.8</a>:</p> + <ul class="rel-note"> + <li>Add a <code>cursorCoords</code> method to find the screen + coordinates of the cursor.</li> + <li>A number of fixes and support for more syntax in the PHP parser.</li> + <li>Fix indentation problem with JSON-mode JS parser in Webkit.</li> + <li>Add a <a href="compress.html">minification</a> UI.</li> + <li>Support a <code>height: dynamic</code> mode, where the editor's + height will adjust to the size of its content.</li> + <li>Better support for IME input mode.</li> + <li>Fix JavaScript parser getting confused when seeing a no-argument + function call.</li> + <li>Have CSS parser see the difference between selectors and other + identifiers.</li> + <li>Fix scrolling bug when pasting in a horizontally-scrolled + editor.</li> + <li>Support <code>toTextArea</code> method in instances created with + <code>fromTextArea</code>.</li> + <li>Work around new Opera cursor bug that causes the cursor to jump + when pressing backspace at the end of a line.</li> + </ul> + + <p class="rel">27-04-2010: <a + href="http://codemirror.net/codemirror-0.67.zip">Version + 0.67</a>:</p> + <p class="rel-note">More consistent page-up/page-down behaviour + across browsers. Fix some issues with hidden editors looping forever + when line-numbers were enabled. Make PHP parser parse + <code>"\\"</code> correctly. Have <code>jumpToLine</code> work on + line handles, and add <code>cursorLine</code> function to fetch the + line handle where the cursor currently is. Add new + <code>setStylesheet</code> function to switch style-sheets in a + running editor.</p> + + <p class="rel">01-03-2010: <a + href="http://codemirror.net/codemirror-0.66.zip">Version + 0.66</a>:</p> + <p class="rel-note">Adds <code>removeLine</code> method to API. + Introduces the <a href="contrib/plsql/index.html">PLSQL parser</a>. + Marks XML errors by adding (rather than replacing) a CSS class, so + that they can be disabled by modifying their style. Fixes several + selection bugs, and a number of small glitches.</p> + + <p class="rel">12-11-2009: <a + href="http://codemirror.net/codemirror-0.65.zip">Version + 0.65</a>:</p> + <p class="rel-note">Add support for having both line-wrapping and + line-numbers turned on, make paren-highlighting style customisable + (<code>markParen</code> and <code>unmarkParen</code> config + options), work around a selection bug that Opera + <em>re</em>introduced in version 10.</p> + + <p class="rel">23-10-2009: <a + href="http://codemirror.net/codemirror-0.64.zip">Version + 0.64</a>:</p> + <p class="rel-note">Solves some issues introduced by the + paste-handling changes from the previous release. Adds + <code>setSpellcheck</code>, <code>setTextWrapping</code>, + <code>setIndentUnit</code>, <code>setUndoDepth</code>, + <code>setTabMode</code>, and <code>setLineNumbers</code> to + customise a running editor. Introduces an <a + href="contrib/sql/index.html">SQL</a> parser. Fixes a few small + problems in the <a href="contrib/python/index.html">Python</a> + parser. And, as usual, add workarounds for various newly discovered + browser incompatibilities.</p> + + <p class="rel">31-08-2009: <a href="http://codemirror.net/codemirror-0.63.zip">Version 0.63</a>:</p> + <p class="rel-note"> Overhaul of paste-handling (less fragile), fixes for several + serious IE8 issues (cursor jumping, end-of-document bugs) and a number + of small problems.</p> + + <p class="rel">30-05-2009: <a href="http://codemirror.net/codemirror-0.62.zip">Version 0.62</a>:</p> + <p class="rel-note">Introduces <a href="contrib/python/index.html">Python</a> + and <a href="contrib/lua/index.html">Lua</a> parsers. Add + <code>setParser</code> (on-the-fly mode changing) and + <code>clearHistory</code> methods. Make parsing passes time-based + instead of lines-based (see the <code>passTime</code> option).</p> + +</section> +</article> diff --git a/modules/editor/codemirror/index.html b/modules/editor/codemirror/index.html @@ -0,0 +1,196 @@ +<!doctype html> + +<title>CodeMirror</title> +<meta charset="utf-8"/> + +<link rel=stylesheet href="lib/codemirror.css"> +<link rel=stylesheet href="doc/docs.css"> +<script src="lib/codemirror.js"></script> +<script src="mode/xml/xml.js"></script> +<script src="mode/javascript/javascript.js"></script> +<script src="mode/css/css.js"></script> +<script src="mode/htmlmixed/htmlmixed.js"></script> +<script src="addon/edit/matchbrackets.js"></script> + +<script src="doc/activebookmark.js"></script> + +<style> + .CodeMirror { height: auto; border: 1px solid #ddd; } + .CodeMirror-scroll { max-height: 200px; } + .CodeMirror pre { padding-left: 7px; line-height: 1.25; } +</style> + +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="doc/logo.png"></a> + + <ul> + <li><a class=active data-default="true" href="#description">Home</a> + <li><a href="doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a href="#features">Features</a> + <li><a href="#community">Community</a> + <li><a href="#browsersupport">Browser support</a> + </ul> +</div> + +<article> + +<section id=description class=first> + <p><strong>CodeMirror</strong> is a versatile text editor + implemented in JavaScript for the browser. It is specialized for + editing code, and comes with a number of <a href="mode/index.html">language modes</a> and <a href="doc/manual.html#addons">addons</a> + that implement more advanced editing functionality.</p> + + <p>A rich <a href="doc/manual.html#api">programming API</a> and a + CSS <a href="doc/manual.html#styling">theming</a> system are + available for customizing CodeMirror to fit your application, and + extending it with new functionality.</p> +</section> + +<section id=demo> + <h2>This is CodeMirror</h2> + <form style="position: relative; margin-top: .5em;"><textarea id=demotext> +<!-- Create a simple CodeMirror instance --> +<link rel="stylesheet" href="lib/codemirror.css"> +<script src="lib/codemirror.js"></script> +<script> + var editor = CodeMirror.fromTextArea(myTextarea, { + lineNumbers: true + }); +</script></textarea> + <select id="demolist" onchange="document.location = this.options[this.selectedIndex].value;"> + <option value="#">Other demos...</option> + <option value="demo/complete.html">Autocompletion</option> + <option value="demo/folding.html">Code folding</option> + <option value="demo/theme.html">Themes</option> + <option value="mode/htmlmixed/index.html">Mixed language modes</option> + <option value="demo/bidi.html">Bi-directional text</option> + <option value="demo/variableheight.html">Variable font sizes</option> + <option value="demo/search.html">Search interface</option> + <option value="demo/vim.html">Vim bindings</option> + <option value="demo/emacs.html">Emacs bindings</option> + <option value="demo/sublime.html">Sublime Text bindings</option> + <option value="demo/tern.html">Tern integration</option> + <option value="demo/merge.html">Merge/diff interface</option> + <option value="demo/fullscreen.html">Full-screen editor</option> + <option value="demo/simplescrollbars.html">Custom scrollbars</option> + </select></form> + <script> + var editor = CodeMirror.fromTextArea(document.getElementById("demotext"), { + lineNumbers: true, + mode: "text/html", + matchBrackets: true + }); + </script> + + <div class=actions> + <div class=actionspicture> + <img src="doc/yinyang.png" class=yinyang> + <div class="actionlink download"> + <a href="http://codemirror.net/codemirror.zip">DOWNLOAD</a> + </div> + <div class="actionlink fund"> + <a href="https://marijnhaverbeke.nl/fund/">FUND</a> + </div> + </div> + <div class=actionsleft> + Get the current version: <a href="http://codemirror.net/codemirror.zip">5.32.0</a>.<br> + You can see the <a href="https://github.com/codemirror/codemirror" title="Github repository">code</a>,<br> + read the <a href="doc/releases.html">release notes</a>,<br> + or study the <a href="doc/manual.html">user manual</a>. + </div> + <div class=actionsright> + Software needs maintenance,<br> + maintainers need to subsist.<br> + Current funding status = <img src="https://marijnhaverbeke.nl/fund/status_s.png" title="Current maintainer happiness" style="vertical-align: middle; height: 16px; width: 16px"><br> + You can help <a href="https://marijnhaverbeke.nl/fund/" title="Set up a monthly contribution">per month</a> or + <a title="Donate with PayPal" href="https://www.paypal.me/marijnhaverbeke">once</a>. + </div> + </div> + +</section> + +<section id=features> + <h2>Features</h2> + <ul> + <li>Support for <a href="mode/index.html">over 100 languages</a> out of the box + <li>A powerful, <a href="mode/htmlmixed/index.html">composable</a> language mode <a href="doc/manual.html#modeapi">system</a> + <li><a href="doc/manual.html#addon_show-hint">Autocompletion</a> (<a href="demo/xmlcomplete.html">XML</a>) + <li><a href="doc/manual.html#addon_foldcode">Code folding</a> + <li><a href="doc/manual.html#option_extraKeys">Configurable</a> keybindings + <li><a href="demo/vim.html">Vim</a>, <a href="demo/emacs.html">Emacs</a>, and <a href="demo/sublime.html">Sublime Text</a> bindings + <li><a href="doc/manual.html#addon_search">Search and replace</a> interface + <li><a href="doc/manual.html#addon_matchbrackets">Bracket</a> and <a href="doc/manual.html#addon_matchtags">tag</a> matching + <li>Support for <a href="demo/buffers.html">split views</a> + <li><a href="doc/manual.html#addon_lint">Linter integration</a> + <li><a href="demo/variableheight.html">Mixing font sizes and styles</a> + <li><a href="demo/theme.html">Various themes</a> + <li>Able to <a href="demo/resize.html">resize to fit content</a> + <li><a href="doc/manual.html#mark_replacedWith">Inline</a> and <a href="doc/manual.html#addLineWidget">block</a> widgets + <li>Programmable <a href="demo/marker.html">gutters</a> + <li>Making ranges of text <a href="doc/manual.html#markText">styled, read-only, or atomic</a> + <li><a href="demo/bidi.html">Bi-directional text</a> support + <li>Many other <a href="doc/manual.html#api">methods</a> and <a href="doc/manual.html#addons">addons</a>... + </ul> +</section> + +<section id=community> + <h2>Community</h2> + + <p>CodeMirror is an open-source project shared under + an <a href="LICENSE">MIT license</a>. It is the editor used in the + dev tools for + <a href="https://hacks.mozilla.org/2013/11/firefox-developer-tools-episode-27-edit-as-html-codemirror-more/">Firefox</a>, + <a href="https://developers.google.com/chrome-developer-tools/">Chrome</a>, + and <a href="https://developer.apple.com/safari/tools/">Safari</a>, in <a href="http://www.lighttable.com/">Light + Table</a>, <a href="http://brackets.io/">Adobe + Brackets</a>, <a href="http://blog.bitbucket.org/2013/05/14/edit-your-code-in-the-cloud-with-bitbucket/">Bitbucket</a>, + and <a href="doc/realworld.html">many other projects</a>.</p> + + <p>Development and bug tracking happens + on <a href="https://github.com/codemirror/CodeMirror/">github</a> + (<a href="http://marijnhaverbeke.nl/git/codemirror">alternate git + repository</a>). + Please <a href="http://codemirror.net/doc/reporting.html">read these + pointers</a> before submitting a bug. Use pull requests to submit + patches. All contributions must be released under the same MIT + license that CodeMirror uses.</p> + + <p>Discussion around the project is done on + a <a href="https://discuss.codemirror.net">discussion forum</a>. + Announcements related to the project, such as new versions, are + posted in the + forum's <a href="https://discuss.codemirror.net/c/announce">"announce"</a> + category. If needed, you can + contact <a href="mailto:marijnh@gmail.com">the maintainer</a> + directly. We aim to be an inclusive, welcoming community. To make + that explicit, we have + a <a href="http://contributor-covenant.org/version/1/1/0/">code of + conduct</a> that applies to communication around the project.</p> + + <p>A list of CodeMirror-related software that is not part of the + main distribution is maintained + on <a href="https://github.com/codemirror/CodeMirror/wiki/CodeMirror-addons">our + wiki</a>. Feel free to add your project.</p> +</section> + +<section id=browsersupport> + <h2>Browser support</h2> + <p>The <em>desktop</em> versions of the following browsers, + in <em>standards mode</em> (HTML5 <code>&lt;!doctype html></code> + recommended) are supported:</p> + <table style="margin-bottom: 1em"> + <tr><th>Firefox</th><td>version 4 and up</td></tr> + <tr><th>Chrome</th><td>any version</td></tr> + <tr><th>Safari</th><td>version 5.2 and up</td></tr> + <tr><th style="padding-right: 1em;">Internet Explorer/Edge</th><td>version 8 and up</td></tr> + <tr><th>Opera</th><td>version 9 and up</td></tr> + </table> + <p>Support for modern mobile browsers is experimental. Recent + versions of the iOS browser and Chrome on Android should work + pretty well.</p> +</section> + +</article> diff --git a/modules/editor/codemirror/mode/crystal/crystal.min.js b/modules/editor/codemirror/mode/crystal/crystal.min.js @@ -0,0 +1,433 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("crystal", function(config) { + function wordRegExp(words, end) { + return new RegExp((end ? "" : "^") + "(?:" + words.join("|") + ")" + (end ? "$" : "\\b")); + } + + function chain(tokenize, stream, state) { + state.tokenize.push(tokenize); + return tokenize(stream, state); + } + + var operators = /^(?:[-+/%|&^]|\*\*?|[<>]{2})/; + var conditionalOperators = /^(?:[=!]~|===|<=>|[<>=!]=?|[|&]{2}|~)/; + var indexingOperators = /^(?:\[\][?=]?)/; + var anotherOperators = /^(?:\.(?:\.{2})?|->|[?:])/; + var idents = /^[a-z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/; + var types = /^[A-Z_\u009F-\uFFFF][a-zA-Z0-9_\u009F-\uFFFF]*/; + var keywords = wordRegExp([ + "abstract", "alias", "as", "asm", "begin", "break", "case", "class", "def", "do", + "else", "elsif", "end", "ensure", "enum", "extend", "for", "fun", "if", + "include", "instance_sizeof", "lib", "macro", "module", "next", "of", "out", "pointerof", + "private", "protected", "rescue", "return", "require", "select", "sizeof", "struct", + "super", "then", "type", "typeof", "uninitialized", "union", "unless", "until", "when", "while", "with", + "yield", "__DIR__", "__END_LINE__", "__FILE__", "__LINE__" + ]); + var atomWords = wordRegExp(["true", "false", "nil", "self"]); + var indentKeywordsArray = [ + "def", "fun", "macro", + "class", "module", "struct", "lib", "enum", "union", + "do", "for" + ]; + var indentKeywords = wordRegExp(indentKeywordsArray); + var indentExpressionKeywordsArray = ["if", "unless", "case", "while", "until", "begin", "then"]; + var indentExpressionKeywords = wordRegExp(indentExpressionKeywordsArray); + var dedentKeywordsArray = ["end", "else", "elsif", "rescue", "ensure"]; + var dedentKeywords = wordRegExp(dedentKeywordsArray); + var dedentPunctualsArray = ["\\)", "\\}", "\\]"]; + var dedentPunctuals = new RegExp("^(?:" + dedentPunctualsArray.join("|") + ")$"); + var nextTokenizer = { + "def": tokenFollowIdent, "fun": tokenFollowIdent, "macro": tokenMacroDef, + "class": tokenFollowType, "module": tokenFollowType, "struct": tokenFollowType, + "lib": tokenFollowType, "enum": tokenFollowType, "union": tokenFollowType + }; + var matching = {"[": "]", "{": "}", "(": ")", "<": ">"}; + + function tokenBase(stream, state) { + if (stream.eatSpace()) { + return null; + } + + // Macros + if (state.lastToken != "\\" && stream.match("{%", false)) { + return chain(tokenMacro("%", "%"), stream, state); + } + + if (state.lastToken != "\\" && stream.match("{{", false)) { + return chain(tokenMacro("{", "}"), stream, state); + } + + // Comments + if (stream.peek() == "#") { + stream.skipToEnd(); + return "comment"; + } + + // Variables and keywords + var matched; + if (stream.match(idents)) { + stream.eat(/[?!]/); + + matched = stream.current(); + if (stream.eat(":")) { + return "atom"; + } else if (state.lastToken == ".") { + return "property"; + } else if (keywords.test(matched)) { + if (indentKeywords.test(matched)) { + if (!(matched == "fun" && state.blocks.indexOf("lib") >= 0) && !(matched == "def" && state.lastToken == "abstract")) { + state.blocks.push(matched); + state.currentIndent += 1; + } + } else if ((state.lastStyle == "operator" || !state.lastStyle) && indentExpressionKeywords.test(matched)) { + state.blocks.push(matched); + state.currentIndent += 1; + } else if (matched == "end") { + state.blocks.pop(); + state.currentIndent -= 1; + } + + if (nextTokenizer.hasOwnProperty(matched)) { + state.tokenize.push(nextTokenizer[matched]); + } + + return "keyword"; + } else if (atomWords.test(matched)) { + return "atom"; + } + + return "variable"; + } + + // Class variables and instance variables + // or attributes + if (stream.eat("@")) { + if (stream.peek() == "[") { + return chain(tokenNest("[", "]", "meta"), stream, state); + } + + stream.eat("@"); + stream.match(idents) || stream.match(types); + return "variable-2"; + } + + // Constants and types + if (stream.match(types)) { + return "tag"; + } + + // Symbols or ':' operator + if (stream.eat(":")) { + if (stream.eat("\"")) { + return chain(tokenQuote("\"", "atom", false), stream, state); + } else if (stream.match(idents) || stream.match(types) || + stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators)) { + return "atom"; + } + stream.eat(":"); + return "operator"; + } + + // Strings + if (stream.eat("\"")) { + return chain(tokenQuote("\"", "string", true), stream, state); + } + + // Strings or regexps or macro variables or '%' operator + if (stream.peek() == "%") { + var style = "string"; + var embed = true; + var delim; + + if (stream.match("%r")) { + // Regexps + style = "string-2"; + delim = stream.next(); + } else if (stream.match("%w")) { + embed = false; + delim = stream.next(); + } else if (stream.match("%q")) { + embed = false; + delim = stream.next(); + } else { + if(delim = stream.match(/^%([^\w\s=])/)) { + delim = delim[1]; + } else if (stream.match(/^%[a-zA-Z0-9_\u009F-\uFFFF]*/)) { + // Macro variables + return "meta"; + } else { + // '%' operator + return "operator"; + } + } + + if (matching.hasOwnProperty(delim)) { + delim = matching[delim]; + } + return chain(tokenQuote(delim, style, embed), stream, state); + } + + // Here Docs + if (matched = stream.match(/^<<-('?)([A-Z]\w*)\1/)) { + return chain(tokenHereDoc(matched[2], !matched[1]), stream, state) + } + + // Characters + if (stream.eat("'")) { + stream.match(/^(?:[^']|\\(?:[befnrtv0'"]|[0-7]{3}|u(?:[0-9a-fA-F]{4}|\{[0-9a-fA-F]{1,6}\})))/); + stream.eat("'"); + return "atom"; + } + + // Numbers + if (stream.eat("0")) { + if (stream.eat("x")) { + stream.match(/^[0-9a-fA-F]+/); + } else if (stream.eat("o")) { + stream.match(/^[0-7]+/); + } else if (stream.eat("b")) { + stream.match(/^[01]+/); + } + return "number"; + } + + if (stream.eat(/^\d/)) { + stream.match(/^\d*(?:\.\d+)?(?:[eE][+-]?\d+)?/); + return "number"; + } + + // Operators + if (stream.match(operators)) { + stream.eat("="); // Operators can follow assign symbol. + return "operator"; + } + + if (stream.match(conditionalOperators) || stream.match(anotherOperators)) { + return "operator"; + } + + // Parens and braces + if (matched = stream.match(/[({[]/, false)) { + matched = matched[0]; + return chain(tokenNest(matched, matching[matched], null), stream, state); + } + + // Escapes + if (stream.eat("\\")) { + stream.next(); + return "meta"; + } + + stream.next(); + return null; + } + + function tokenNest(begin, end, style, started) { + return function (stream, state) { + if (!started && stream.match(begin)) { + state.tokenize[state.tokenize.length - 1] = tokenNest(begin, end, style, true); + state.currentIndent += 1; + return style; + } + + var nextStyle = tokenBase(stream, state); + if (stream.current() === end) { + state.tokenize.pop(); + state.currentIndent -= 1; + nextStyle = style; + } + + return nextStyle; + }; + } + + function tokenMacro(begin, end, started) { + return function (stream, state) { + if (!started && stream.match("{" + begin)) { + state.currentIndent += 1; + state.tokenize[state.tokenize.length - 1] = tokenMacro(begin, end, true); + return "meta"; + } + + if (stream.match(end + "}")) { + state.currentIndent -= 1; + state.tokenize.pop(); + return "meta"; + } + + return tokenBase(stream, state); + }; + } + + function tokenMacroDef(stream, state) { + if (stream.eatSpace()) { + return null; + } + + var matched; + if (matched = stream.match(idents)) { + if (matched == "def") { + return "keyword"; + } + stream.eat(/[?!]/); + } + + state.tokenize.pop(); + return "def"; + } + + function tokenFollowIdent(stream, state) { + if (stream.eatSpace()) { + return null; + } + + if (stream.match(idents)) { + stream.eat(/[!?]/); + } else { + stream.match(operators) || stream.match(conditionalOperators) || stream.match(indexingOperators); + } + state.tokenize.pop(); + return "def"; + } + + function tokenFollowType(stream, state) { + if (stream.eatSpace()) { + return null; + } + + stream.match(types); + state.tokenize.pop(); + return "def"; + } + + function tokenQuote(end, style, embed) { + return function (stream, state) { + var escaped = false; + + while (stream.peek()) { + if (!escaped) { + if (stream.match("{%", false)) { + state.tokenize.push(tokenMacro("%", "%")); + return style; + } + + if (stream.match("{{", false)) { + state.tokenize.push(tokenMacro("{", "}")); + return style; + } + + if (embed && stream.match("#{", false)) { + state.tokenize.push(tokenNest("#{", "}", "meta")); + return style; + } + + var ch = stream.next(); + + if (ch == end) { + state.tokenize.pop(); + return style; + } + + escaped = embed && ch == "\\"; + } else { + stream.next(); + escaped = false; + } + } + + return style; + }; + } + + function tokenHereDoc(phrase, embed) { + return function (stream, state) { + if (stream.sol()) { + stream.eatSpace() + if (stream.match(phrase)) { + state.tokenize.pop(); + return "string"; + } + } + + var escaped = false; + while (stream.peek()) { + if (!escaped) { + if (stream.match("{%", false)) { + state.tokenize.push(tokenMacro("%", "%")); + return "string"; + } + + if (stream.match("{{", false)) { + state.tokenize.push(tokenMacro("{", "}")); + return "string"; + } + + if (embed && stream.match("#{", false)) { + state.tokenize.push(tokenNest("#{", "}", "meta")); + return "string"; + } + + escaped = embed && stream.next() == "\\"; + } else { + stream.next(); + escaped = false; + } + } + + return "string"; + } + } + + return { + startState: function () { + return { + tokenize: [tokenBase], + currentIndent: 0, + lastToken: null, + lastStyle: null, + blocks: [] + }; + }, + + token: function (stream, state) { + var style = state.tokenize[state.tokenize.length - 1](stream, state); + var token = stream.current(); + + if (style && style != "comment") { + state.lastToken = token; + state.lastStyle = style; + } + + return style; + }, + + indent: function (state, textAfter) { + textAfter = textAfter.replace(/^\s*(?:\{%)?\s*|\s*(?:%\})?\s*$/g, ""); + + if (dedentKeywords.test(textAfter) || dedentPunctuals.test(textAfter)) { + return config.indentUnit * (state.currentIndent - 1); + } + + return config.indentUnit * state.currentIndent; + }, + + fold: "indent", + electricInput: wordRegExp(dedentPunctualsArray.concat(dedentKeywordsArray), true), + lineComment: '#' + }; + }); + + CodeMirror.defineMIME("text/x-crystal", "crystal"); +}); diff --git a/modules/editor/codemirror/mode/css/gss_test.min.js b/modules/editor/codemirror/mode/css/gss_test.min.js @@ -0,0 +1,17 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + "use strict"; + + var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-gss"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "gss"); } + + MT("atComponent", + "[def @component] {", + "[tag foo] {", + " [property color]: [keyword black];", + "}", + "}"); + +})(); diff --git a/modules/editor/codemirror/mode/css/scss_test.min.js b/modules/editor/codemirror/mode/css/scss_test.min.js @@ -0,0 +1,110 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-scss"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "scss"); } + + MT('url_with_quotation', + "[tag foo] { [property background]:[atom url]([string test.jpg]) }"); + + MT('url_with_double_quotes', + "[tag foo] { [property background]:[atom url]([string \"test.jpg\"]) }"); + + MT('url_with_single_quotes', + "[tag foo] { [property background]:[atom url]([string \'test.jpg\']) }"); + + MT('string', + "[def @import] [string \"compass/css3\"]"); + + MT('important_keyword', + "[tag foo] { [property background]:[atom url]([string \'test.jpg\']) [keyword !important] }"); + + MT('variable', + "[variable-2 $blue]:[atom #333]"); + + MT('variable_as_attribute', + "[tag foo] { [property color]:[variable-2 $blue] }"); + + MT('numbers', + "[tag foo] { [property padding]:[number 10px] [number 10] [number 10em] [number 8in] }"); + + MT('number_percentage', + "[tag foo] { [property width]:[number 80%] }"); + + MT('selector', + "[builtin #hello][qualifier .world]{}"); + + MT('singleline_comment', + "[comment // this is a comment]"); + + MT('multiline_comment', + "[comment /*foobar*/]"); + + MT('attribute_with_hyphen', + "[tag foo] { [property font-size]:[number 10px] }"); + + MT('string_after_attribute', + "[tag foo] { [property content]:[string \"::\"] }"); + + MT('directives', + "[def @include] [qualifier .mixin]"); + + MT('basic_structure', + "[tag p] { [property background]:[keyword red]; }"); + + MT('nested_structure', + "[tag p] { [tag a] { [property color]:[keyword red]; } }"); + + MT('mixin', + "[def @mixin] [tag table-base] {}"); + + MT('number_without_semicolon', + "[tag p] {[property width]:[number 12]}", + "[tag a] {[property color]:[keyword red];}"); + + MT('atom_in_nested_block', + "[tag p] { [tag a] { [property color]:[atom #000]; } }"); + + MT('interpolation_in_property', + "[tag foo] { #{[variable-2 $hello]}:[number 2]; }"); + + MT('interpolation_in_selector', + "[tag foo]#{[variable-2 $hello]} { [property color]:[atom #000]; }"); + + MT('interpolation_error', + "[tag foo]#{[variable foo]} { [property color]:[atom #000]; }"); + + MT("divide_operator", + "[tag foo] { [property width]:[number 4] [operator /] [number 2] }"); + + MT('nested_structure_with_id_selector', + "[tag p] { [builtin #hello] { [property color]:[keyword red]; } }"); + + MT('indent_mixin', + "[def @mixin] [tag container] (", + " [variable-2 $a]: [number 10],", + " [variable-2 $b]: [number 10])", + "{}"); + + MT('indent_nested', + "[tag foo] {", + " [tag bar] {", + " }", + "}"); + + MT('indent_parentheses', + "[tag foo] {", + " [property color]: [atom darken]([variable-2 $blue],", + " [number 9%]);", + "}"); + + MT('indent_vardef', + "[variable-2 $name]:", + " [string 'val'];", + "[tag tag] {", + " [tag inner] {", + " [property margin]: [number 3px];", + " }", + "}"); +})(); diff --git a/modules/editor/codemirror/mode/d/test.min.js b/modules/editor/codemirror/mode/d/test.min.js @@ -0,0 +1,11 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var mode = CodeMirror.getMode({indentUnit: 2}, "d"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + + MT("nested_comments", + "[comment /+]","[comment comment]","[comment +/]","[variable void] [variable main](){}"); + +})(); diff --git a/modules/editor/codemirror/mode/diff/diff.min.js b/modules/editor/codemirror/mode/diff/diff.min.js @@ -0,0 +1,47 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("diff", function() { + + var TOKEN_NAMES = { + '+': 'positive', + '-': 'negative', + '@': 'meta' + }; + + return { + token: function(stream) { + var tw_pos = stream.string.search(/[\t ]+?$/); + + if (!stream.sol() || tw_pos === 0) { + stream.skipToEnd(); + return ("error " + ( + TOKEN_NAMES[stream.string.charAt(0)] || '')).replace(/ $/, ''); + } + + var token_name = TOKEN_NAMES[stream.peek()] || stream.skipToEnd(); + + if (tw_pos === -1) { + stream.skipToEnd(); + } else { + stream.pos = tw_pos; + } + + return token_name; + } + }; +}); + +CodeMirror.defineMIME("text/x-diff", "diff"); + +}); diff --git a/modules/editor/codemirror/mode/dtd/dtd.min.js b/modules/editor/codemirror/mode/dtd/dtd.min.js @@ -0,0 +1,142 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +/* + DTD mode + Ported to CodeMirror by Peter Kroon <plakroon@gmail.com> + Report bugs/issues here: https://github.com/codemirror/CodeMirror/issues + GitHub: @peterkroon +*/ + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("dtd", function(config) { + var indentUnit = config.indentUnit, type; + function ret(style, tp) {type = tp; return style;} + + function tokenBase(stream, state) { + var ch = stream.next(); + + if (ch == "<" && stream.eat("!") ) { + if (stream.eatWhile(/[\-]/)) { + state.tokenize = tokenSGMLComment; + return tokenSGMLComment(stream, state); + } else if (stream.eatWhile(/[\w]/)) return ret("keyword", "doindent"); + } else if (ch == "<" && stream.eat("?")) { //xml declaration + state.tokenize = inBlock("meta", "?>"); + return ret("meta", ch); + } else if (ch == "#" && stream.eatWhile(/[\w]/)) return ret("atom", "tag"); + else if (ch == "|") return ret("keyword", "seperator"); + else if (ch.match(/[\(\)\[\]\-\.,\+\?>]/)) return ret(null, ch);//if(ch === ">") return ret(null, "endtag"); else + else if (ch.match(/[\[\]]/)) return ret("rule", ch); + else if (ch == "\"" || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } else if (stream.eatWhile(/[a-zA-Z\?\+\d]/)) { + var sc = stream.current(); + if( sc.substr(sc.length-1,sc.length).match(/\?|\+/) !== null )stream.backUp(1); + return ret("tag", "tag"); + } else if (ch == "%" || ch == "*" ) return ret("number", "number"); + else { + stream.eatWhile(/[\w\\\-_%.{,]/); + return ret(null, null); + } + } + + function tokenSGMLComment(stream, state) { + var dashes = 0, ch; + while ((ch = stream.next()) != null) { + if (dashes >= 2 && ch == ">") { + state.tokenize = tokenBase; + break; + } + dashes = (ch == "-") ? dashes + 1 : 0; + } + return ret("comment", "comment"); + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && ch == "\\"; + } + return ret("string", "tag"); + }; + } + + function inBlock(style, terminator) { + return function(stream, state) { + while (!stream.eol()) { + if (stream.match(terminator)) { + state.tokenize = tokenBase; + break; + } + stream.next(); + } + return style; + }; + } + + return { + startState: function(base) { + return {tokenize: tokenBase, + baseIndent: base || 0, + stack: []}; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + + var context = state.stack[state.stack.length-1]; + if (stream.current() == "[" || type === "doindent" || type == "[") state.stack.push("rule"); + else if (type === "endtag") state.stack[state.stack.length-1] = "endtag"; + else if (stream.current() == "]" || type == "]" || (type == ">" && context == "rule")) state.stack.pop(); + else if (type == "[") state.stack.push("["); + return style; + }, + + indent: function(state, textAfter) { + var n = state.stack.length; + + if( textAfter.match(/\]\s+|\]/) )n=n-1; + else if(textAfter.substr(textAfter.length-1, textAfter.length) === ">"){ + if(textAfter.substr(0,1) === "<") {} + else if( type == "doindent" && textAfter.length > 1 ) {} + else if( type == "doindent")n--; + else if( type == ">" && textAfter.length > 1) {} + else if( type == "tag" && textAfter !== ">") {} + else if( type == "tag" && state.stack[state.stack.length-1] == "rule")n--; + else if( type == "tag")n++; + else if( textAfter === ">" && state.stack[state.stack.length-1] == "rule" && type === ">")n--; + else if( textAfter === ">" && state.stack[state.stack.length-1] == "rule") {} + else if( textAfter.substr(0,1) !== "<" && textAfter.substr(0,1) === ">" )n=n-1; + else if( textAfter === ">") {} + else n=n-1; + //over rule them all + if(type == null || type == "]")n--; + } + + return state.baseIndent + n * indentUnit; + }, + + electricChars: "]>" + }; +}); + +CodeMirror.defineMIME("application/xml-dtd", "dtd"); + +}); diff --git a/modules/editor/codemirror/mode/ebnf/ebnf.min.js b/modules/editor/codemirror/mode/ebnf/ebnf.min.js @@ -0,0 +1,195 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("ebnf", function (config) { + var commentType = {slash: 0, parenthesis: 1}; + var stateType = {comment: 0, _string: 1, characterClass: 2}; + var bracesMode = null; + + if (config.bracesMode) + bracesMode = CodeMirror.getMode(config, config.bracesMode); + + return { + startState: function () { + return { + stringType: null, + commentType: null, + braced: 0, + lhs: true, + localState: null, + stack: [], + inDefinition: false + }; + }, + token: function (stream, state) { + if (!stream) return; + + //check for state changes + if (state.stack.length === 0) { + //strings + if ((stream.peek() == '"') || (stream.peek() == "'")) { + state.stringType = stream.peek(); + stream.next(); // Skip quote + state.stack.unshift(stateType._string); + } else if (stream.match(/^\/\*/)) { //comments starting with /* + state.stack.unshift(stateType.comment); + state.commentType = commentType.slash; + } else if (stream.match(/^\(\*/)) { //comments starting with (* + state.stack.unshift(stateType.comment); + state.commentType = commentType.parenthesis; + } + } + + //return state + //stack has + switch (state.stack[0]) { + case stateType._string: + while (state.stack[0] === stateType._string && !stream.eol()) { + if (stream.peek() === state.stringType) { + stream.next(); // Skip quote + state.stack.shift(); // Clear flag + } else if (stream.peek() === "\\") { + stream.next(); + stream.next(); + } else { + stream.match(/^.[^\\\"\']*/); + } + } + return state.lhs ? "property string" : "string"; // Token style + + case stateType.comment: + while (state.stack[0] === stateType.comment && !stream.eol()) { + if (state.commentType === commentType.slash && stream.match(/\*\//)) { + state.stack.shift(); // Clear flag + state.commentType = null; + } else if (state.commentType === commentType.parenthesis && stream.match(/\*\)/)) { + state.stack.shift(); // Clear flag + state.commentType = null; + } else { + stream.match(/^.[^\*]*/); + } + } + return "comment"; + + case stateType.characterClass: + while (state.stack[0] === stateType.characterClass && !stream.eol()) { + if (!(stream.match(/^[^\]\\]+/) || stream.match(/^\\./))) { + state.stack.shift(); + } + } + return "operator"; + } + + var peek = stream.peek(); + + if (bracesMode !== null && (state.braced || peek === "{")) { + if (state.localState === null) + state.localState = CodeMirror.startState(bracesMode); + + var token = bracesMode.token(stream, state.localState), + text = stream.current(); + + if (!token) { + for (var i = 0; i < text.length; i++) { + if (text[i] === "{") { + if (state.braced === 0) { + token = "matchingbracket"; + } + state.braced++; + } else if (text[i] === "}") { + state.braced--; + if (state.braced === 0) { + token = "matchingbracket"; + } + } + } + } + return token; + } + + //no stack + switch (peek) { + case "[": + stream.next(); + state.stack.unshift(stateType.characterClass); + return "bracket"; + case ":": + case "|": + case ";": + stream.next(); + return "operator"; + case "%": + if (stream.match("%%")) { + return "header"; + } else if (stream.match(/[%][A-Za-z]+/)) { + return "keyword"; + } else if (stream.match(/[%][}]/)) { + return "matchingbracket"; + } + break; + case "/": + if (stream.match(/[\/][A-Za-z]+/)) { + return "keyword"; + } + case "\\": + if (stream.match(/[\][a-z]+/)) { + return "string-2"; + } + case ".": + if (stream.match(".")) { + return "atom"; + } + case "*": + case "-": + case "+": + case "^": + if (stream.match(peek)) { + return "atom"; + } + case "$": + if (stream.match("$$")) { + return "builtin"; + } else if (stream.match(/[$][0-9]+/)) { + return "variable-3"; + } + case "<": + if (stream.match(/<<[a-zA-Z_]+>>/)) { + return "builtin"; + } + } + + if (stream.match(/^\/\//)) { + stream.skipToEnd(); + return "comment"; + } else if (stream.match(/return/)) { + return "operator"; + } else if (stream.match(/^[a-zA-Z_][a-zA-Z0-9_]*/)) { + if (stream.match(/(?=[\(.])/)) { + return "variable"; + } else if (stream.match(/(?=[\s\n]*[:=])/)) { + return "def"; + } + return "variable-2"; + } else if (["[", "]", "(", ")"].indexOf(stream.peek()) != -1) { + stream.next(); + return "bracket"; + } else if (!stream.eatSpace()) { + stream.next(); + } + return null; + } + }; + }); + + CodeMirror.defineMIME("text/x-ebnf", "ebnf"); +}); diff --git a/modules/editor/codemirror/mode/gfm/gfm.min.js b/modules/editor/codemirror/mode/gfm/gfm.min.js @@ -0,0 +1,129 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../markdown/markdown"), require("../../addon/mode/overlay")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../markdown/markdown", "../../addon/mode/overlay"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +var urlRE = /^((?:(?:aaas?|about|acap|adiumxtra|af[ps]|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|cap|chrome(?:-extension)?|cid|coap|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-(?:playcontainer|playsingle)|dns|doi|dtn|dvb|ed2k|facetime|feed|file|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|hcp|https?|iax|icap|icon|im|imap|info|ipn|ipp|irc[6s]?|iris(?:\.beep|\.lwz|\.xpc|\.xpcs)?|itms|jar|javascript|jms|keyparc|lastfm|ldaps?|magnet|mailto|maps|market|message|mid|mms|ms-help|msnim|msrps?|mtqp|mumble|mupdate|mvn|news|nfs|nih?|nntp|notes|oid|opaquelocktoken|palm|paparazzi|platform|pop|pres|proxy|psyc|query|res(?:ource)?|rmi|rsync|rtmp|rtsp|secondlife|service|session|sftp|sgn|shttp|sieve|sips?|skype|sm[bs]|snmp|soap\.beeps?|soldat|spotify|ssh|steam|svn|tag|teamspeak|tel(?:net)?|tftp|things|thismessage|tip|tn3270|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|view-source|webcal|wss?|wtai|wyciwyg|xcon(?:-userid)?|xfire|xmlrpc\.beeps?|xmpp|xri|ymsgr|z39\.50[rs]?):(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i + +CodeMirror.defineMode("gfm", function(config, modeConfig) { + var codeDepth = 0; + function blankLine(state) { + state.code = false; + return null; + } + var gfmOverlay = { + startState: function() { + return { + code: false, + codeBlock: false, + ateSpace: false + }; + }, + copyState: function(s) { + return { + code: s.code, + codeBlock: s.codeBlock, + ateSpace: s.ateSpace + }; + }, + token: function(stream, state) { + state.combineTokens = null; + + // Hack to prevent formatting override inside code blocks (block and inline) + if (state.codeBlock) { + if (stream.match(/^```+/)) { + state.codeBlock = false; + return null; + } + stream.skipToEnd(); + return null; + } + if (stream.sol()) { + state.code = false; + } + if (stream.sol() && stream.match(/^```+/)) { + stream.skipToEnd(); + state.codeBlock = true; + return null; + } + // If this block is changed, it may need to be updated in Markdown mode + if (stream.peek() === '`') { + stream.next(); + var before = stream.pos; + stream.eatWhile('`'); + var difference = 1 + stream.pos - before; + if (!state.code) { + codeDepth = difference; + state.code = true; + } else { + if (difference === codeDepth) { // Must be exact + state.code = false; + } + } + return null; + } else if (state.code) { + stream.next(); + return null; + } + // Check if space. If so, links can be formatted later on + if (stream.eatSpace()) { + state.ateSpace = true; + return null; + } + if (stream.sol() || state.ateSpace) { + state.ateSpace = false; + if (modeConfig.gitHubSpice !== false) { + if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?=.{0,6}\d)(?:[a-f0-9]{7,40}\b)/)) { + // User/Project@SHA + // User@SHA + // SHA + state.combineTokens = true; + return "link"; + } else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) { + // User/Project#Num + // User#Num + // #Num + state.combineTokens = true; + return "link"; + } + } + } + if (stream.match(urlRE) && + stream.string.slice(stream.start - 2, stream.start) != "](" && + (stream.start == 0 || /\W/.test(stream.string.charAt(stream.start - 1)))) { + // URLs + // Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls + // And then (issue #1160) simplified to make it not crash the Chrome Regexp engine + // And then limited url schemes to the CommonMark list, so foo:bar isn't matched as a URL + state.combineTokens = true; + return "link"; + } + stream.next(); + return null; + }, + blankLine: blankLine + }; + + var markdownConfig = { + taskLists: true, + strikethrough: true, + emoji: true + }; + for (var attr in modeConfig) { + markdownConfig[attr] = modeConfig[attr]; + } + markdownConfig.name = "markdown"; + return CodeMirror.overlayMode(CodeMirror.getMode(config, markdownConfig), gfmOverlay); + +}, "markdown"); + + CodeMirror.defineMIME("text/x-gfm", "gfm"); +}); diff --git a/modules/editor/codemirror/mode/haskell-literate/haskell-literate.min.js b/modules/editor/codemirror/mode/haskell-literate/haskell-literate.min.js @@ -0,0 +1,43 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function (mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../haskell/haskell")) + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../haskell/haskell"], mod) + else // Plain browser env + mod(CodeMirror) +})(function (CodeMirror) { + "use strict" + + CodeMirror.defineMode("haskell-literate", function (config, parserConfig) { + var baseMode = CodeMirror.getMode(config, (parserConfig && parserConfig.base) || "haskell") + + return { + startState: function () { + return { + inCode: false, + baseState: CodeMirror.startState(baseMode) + } + }, + token: function (stream, state) { + if (stream.sol()) { + if (state.inCode = stream.eat(">")) + return "meta" + } + if (state.inCode) { + return baseMode.token(stream, state.baseState) + } else { + stream.skipToEnd() + return "comment" + } + }, + innerMode: function (state) { + return state.inCode ? {state: state.baseState, mode: baseMode} : null + } + } + }, "haskell") + + CodeMirror.defineMIME("text/x-literate-haskell", "haskell-literate") +}); diff --git a/modules/editor/codemirror/mode/haxe/haxe.min.js b/modules/editor/codemirror/mode/haxe/haxe.min.js @@ -0,0 +1,515 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("haxe", function(config, parserConfig) { + var indentUnit = config.indentUnit; + + // Tokenizer + + function kw(type) {return {type: type, style: "keyword"};} + var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); + var operator = kw("operator"), atom = {type: "atom", style: "atom"}, attribute = {type:"attribute", style: "attribute"}; + var type = kw("typedef"); + var keywords = { + "if": A, "while": A, "else": B, "do": B, "try": B, + "return": C, "break": C, "continue": C, "new": C, "throw": C, + "var": kw("var"), "inline":attribute, "static": attribute, "using":kw("import"), + "public": attribute, "private": attribute, "cast": kw("cast"), "import": kw("import"), "macro": kw("macro"), + "function": kw("function"), "catch": kw("catch"), "untyped": kw("untyped"), "callback": kw("cb"), + "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), + "in": operator, "never": kw("property_access"), "trace":kw("trace"), + "class": type, "abstract":type, "enum":type, "interface":type, "typedef":type, "extends":type, "implements":type, "dynamic":type, + "true": atom, "false": atom, "null": atom + }; + + var isOperatorChar = /[+\-*&%=<>!?|]/; + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + function toUnescaped(stream, end) { + var escaped = false, next; + while ((next = stream.next()) != null) { + if (next == end && !escaped) + return true; + escaped = !escaped && next == "\\"; + } + } + + // Used as scratch variables to communicate multiple values without + // consing up tons of objects. + var type, content; + function ret(tp, style, cont) { + type = tp; content = cont; + return style; + } + + function haxeTokenBase(stream, state) { + var ch = stream.next(); + if (ch == '"' || ch == "'") { + return chain(stream, state, haxeTokenString(ch)); + } else if (/[\[\]{}\(\),;\:\.]/.test(ch)) { + return ret(ch); + } else if (ch == "0" && stream.eat(/x/i)) { + stream.eatWhile(/[\da-f]/i); + return ret("number", "number"); + } else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) { + stream.match(/^\d*(?:\.\d*(?!\.))?(?:[eE][+\-]?\d+)?/); + return ret("number", "number"); + } else if (state.reAllowed && (ch == "~" && stream.eat(/\//))) { + toUnescaped(stream, "/"); + stream.eatWhile(/[gimsu]/); + return ret("regexp", "string-2"); + } else if (ch == "/") { + if (stream.eat("*")) { + return chain(stream, state, haxeTokenComment); + } else if (stream.eat("/")) { + stream.skipToEnd(); + return ret("comment", "comment"); + } else { + stream.eatWhile(isOperatorChar); + return ret("operator", null, stream.current()); + } + } else if (ch == "#") { + stream.skipToEnd(); + return ret("conditional", "meta"); + } else if (ch == "@") { + stream.eat(/:/); + stream.eatWhile(/[\w_]/); + return ret ("metadata", "meta"); + } else if (isOperatorChar.test(ch)) { + stream.eatWhile(isOperatorChar); + return ret("operator", null, stream.current()); + } else { + var word; + if(/[A-Z]/.test(ch)) { + stream.eatWhile(/[\w_<>]/); + word = stream.current(); + return ret("type", "variable-3", word); + } else { + stream.eatWhile(/[\w_]/); + var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; + return (known && state.kwAllowed) ? ret(known.type, known.style, word) : + ret("variable", "variable", word); + } + } + } + + function haxeTokenString(quote) { + return function(stream, state) { + if (toUnescaped(stream, quote)) + state.tokenize = haxeTokenBase; + return ret("string", "string"); + }; + } + + function haxeTokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = haxeTokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return ret("comment", "comment"); + } + + // Parser + + var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true}; + + function HaxeLexical(indented, column, type, align, prev, info) { + this.indented = indented; + this.column = column; + this.type = type; + this.prev = prev; + this.info = info; + if (align != null) this.align = align; + } + + function inScope(state, varname) { + for (var v = state.localVars; v; v = v.next) + if (v.name == varname) return true; + } + + function parseHaxe(state, style, type, content, stream) { + var cc = state.cc; + // Communicate our context to the combinators. + // (Less wasteful than consing up a hundred closures on every call.) + cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; + + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = true; + + while(true) { + var combinator = cc.length ? cc.pop() : statement; + if (combinator(type, content)) { + while(cc.length && cc[cc.length - 1].lex) + cc.pop()(); + if (cx.marked) return cx.marked; + if (type == "variable" && inScope(state, content)) return "variable-2"; + if (type == "variable" && imported(state, content)) return "variable-3"; + return style; + } + } + } + + function imported(state, typename) { + if (/[a-z]/.test(typename.charAt(0))) + return false; + var len = state.importedtypes.length; + for (var i = 0; i<len; i++) + if(state.importedtypes[i]==typename) return true; + } + + function registerimport(importname) { + var state = cx.state; + for (var t = state.importedtypes; t; t = t.next) + if(t.name == importname) return; + state.importedtypes = { name: importname, next: state.importedtypes }; + } + // Combinator utils + + var cx = {state: null, column: null, marked: null, cc: null}; + function pass() { + for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]); + } + function cont() { + pass.apply(null, arguments); + return true; + } + function inList(name, list) { + for (var v = list; v; v = v.next) + if (v.name == name) return true; + return false; + } + function register(varname) { + var state = cx.state; + if (state.context) { + cx.marked = "def"; + if (inList(varname, state.localVars)) return; + state.localVars = {name: varname, next: state.localVars}; + } else if (state.globalVars) { + if (inList(varname, state.globalVars)) return; + state.globalVars = {name: varname, next: state.globalVars}; + } + } + + // Combinators + + var defaultVars = {name: "this", next: null}; + function pushcontext() { + if (!cx.state.context) cx.state.localVars = defaultVars; + cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; + } + function popcontext() { + cx.state.localVars = cx.state.context.vars; + cx.state.context = cx.state.context.prev; + } + popcontext.lex = true; + function pushlex(type, info) { + var result = function() { + var state = cx.state; + state.lexical = new HaxeLexical(state.indented, cx.stream.column(), type, null, state.lexical, info); + }; + result.lex = true; + return result; + } + function poplex() { + var state = cx.state; + if (state.lexical.prev) { + if (state.lexical.type == ")") + state.indented = state.lexical.indented; + state.lexical = state.lexical.prev; + } + } + poplex.lex = true; + + function expect(wanted) { + function f(type) { + if (type == wanted) return cont(); + else if (wanted == ";") return pass(); + else return cont(f); + } + return f; + } + + function statement(type) { + if (type == "@") return cont(metadef); + if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex); + if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); + if (type == "keyword b") return cont(pushlex("form"), statement, poplex); + if (type == "{") return cont(pushlex("}"), pushcontext, block, poplex, popcontext); + if (type == ";") return cont(); + if (type == "attribute") return cont(maybeattribute); + if (type == "function") return cont(functiondef); + if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), + poplex, statement, poplex); + if (type == "variable") return cont(pushlex("stat"), maybelabel); + if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), + block, poplex, poplex); + if (type == "case") return cont(expression, expect(":")); + if (type == "default") return cont(expect(":")); + if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), + statement, poplex, popcontext); + if (type == "import") return cont(importdef, expect(";")); + if (type == "typedef") return cont(typedef); + return pass(pushlex("stat"), expression, expect(";"), poplex); + } + function expression(type) { + if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator); + if (type == "type" ) return cont(maybeoperator); + if (type == "function") return cont(functiondef); + if (type == "keyword c") return cont(maybeexpression); + if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator); + if (type == "operator") return cont(expression); + if (type == "[") return cont(pushlex("]"), commasep(maybeexpression, "]"), poplex, maybeoperator); + if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator); + return cont(); + } + function maybeexpression(type) { + if (type.match(/[;\}\)\],]/)) return pass(); + return pass(expression); + } + + function maybeoperator(type, value) { + if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator); + if (type == "operator" || type == ":") return cont(expression); + if (type == ";") return; + if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator); + if (type == ".") return cont(property, maybeoperator); + if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator); + } + + function maybeattribute(type) { + if (type == "attribute") return cont(maybeattribute); + if (type == "function") return cont(functiondef); + if (type == "var") return cont(vardef1); + } + + function metadef(type) { + if(type == ":") return cont(metadef); + if(type == "variable") return cont(metadef); + if(type == "(") return cont(pushlex(")"), commasep(metaargs, ")"), poplex, statement); + } + function metaargs(type) { + if(type == "variable") return cont(); + } + + function importdef (type, value) { + if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); } + else if(type == "variable" || type == "property" || type == "." || value == "*") return cont(importdef); + } + + function typedef (type, value) + { + if(type == "variable" && /[A-Z]/.test(value.charAt(0))) { registerimport(value); return cont(); } + else if (type == "type" && /[A-Z]/.test(value.charAt(0))) { return cont(); } + } + + function maybelabel(type) { + if (type == ":") return cont(poplex, statement); + return pass(maybeoperator, expect(";"), poplex); + } + function property(type) { + if (type == "variable") {cx.marked = "property"; return cont();} + } + function objprop(type) { + if (type == "variable") cx.marked = "property"; + if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression); + } + function commasep(what, end) { + function proceed(type) { + if (type == ",") return cont(what, proceed); + if (type == end) return cont(); + return cont(expect(end)); + } + return function(type) { + if (type == end) return cont(); + else return pass(what, proceed); + }; + } + function block(type) { + if (type == "}") return cont(); + return pass(statement, block); + } + function vardef1(type, value) { + if (type == "variable"){register(value); return cont(typeuse, vardef2);} + return cont(); + } + function vardef2(type, value) { + if (value == "=") return cont(expression, vardef2); + if (type == ",") return cont(vardef1); + } + function forspec1(type, value) { + if (type == "variable") { + register(value); + return cont(forin, expression) + } else { + return pass() + } + } + function forin(_type, value) { + if (value == "in") return cont(); + } + function functiondef(type, value) { + //function names starting with upper-case letters are recognised as types, so cludging them together here. + if (type == "variable" || type == "type") {register(value); return cont(functiondef);} + if (value == "new") return cont(functiondef); + if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, typeuse, statement, popcontext); + } + function typeuse(type) { + if(type == ":") return cont(typestring); + } + function typestring(type) { + if(type == "type") return cont(); + if(type == "variable") return cont(); + if(type == "{") return cont(pushlex("}"), commasep(typeprop, "}"), poplex); + } + function typeprop(type) { + if(type == "variable") return cont(typeuse); + } + function funarg(type, value) { + if (type == "variable") {register(value); return cont(typeuse);} + } + + // Interface + return { + startState: function(basecolumn) { + var defaulttypes = ["Int", "Float", "String", "Void", "Std", "Bool", "Dynamic", "Array"]; + var state = { + tokenize: haxeTokenBase, + reAllowed: true, + kwAllowed: true, + cc: [], + lexical: new HaxeLexical((basecolumn || 0) - indentUnit, 0, "block", false), + localVars: parserConfig.localVars, + importedtypes: defaulttypes, + context: parserConfig.localVars && {vars: parserConfig.localVars}, + indented: 0 + }; + if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") + state.globalVars = parserConfig.globalVars; + return state; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (!state.lexical.hasOwnProperty("align")) + state.lexical.align = false; + state.indented = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + if (type == "comment") return style; + state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/)); + state.kwAllowed = type != '.'; + return parseHaxe(state, style, type, content, stream); + }, + + indent: function(state, textAfter) { + if (state.tokenize != haxeTokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; + if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; + var type = lexical.type, closing = firstChar == type; + if (type == "vardef") return lexical.indented + 4; + else if (type == "form" && firstChar == "{") return lexical.indented; + else if (type == "stat" || type == "form") return lexical.indented + indentUnit; + else if (lexical.info == "switch" && !closing) + return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit); + else if (lexical.align) return lexical.column + (closing ? 0 : 1); + else return lexical.indented + (closing ? 0 : indentUnit); + }, + + electricChars: "{}", + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//" + }; +}); + +CodeMirror.defineMIME("text/x-haxe", "haxe"); + +CodeMirror.defineMode("hxml", function () { + + return { + startState: function () { + return { + define: false, + inString: false + }; + }, + token: function (stream, state) { + var ch = stream.peek(); + var sol = stream.sol(); + + ///* comments */ + if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } + if (sol && ch == "-") { + var style = "variable-2"; + + stream.eat(/-/); + + if (stream.peek() == "-") { + stream.eat(/-/); + style = "keyword a"; + } + + if (stream.peek() == "D") { + stream.eat(/[D]/); + style = "keyword c"; + state.define = true; + } + + stream.eatWhile(/[A-Z]/i); + return style; + } + + var ch = stream.peek(); + + if (state.inString == false && ch == "'") { + state.inString = true; + stream.next(); + } + + if (state.inString == true) { + if (stream.skipTo("'")) { + + } else { + stream.skipToEnd(); + } + + if (stream.peek() == "'") { + stream.next(); + state.inString = false; + } + + return "string"; + } + + stream.next(); + return null; + }, + lineComment: "#" + }; +}); + +CodeMirror.defineMIME("text/x-hxml", "hxml"); + +}); diff --git a/modules/editor/codemirror/mode/julia/julia.min.js b/modules/editor/codemirror/mode/julia/julia.min.js @@ -0,0 +1,418 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("julia", function(config, parserConf) { + function wordRegexp(words, end) { + if (typeof end === "undefined") { end = "\\b"; } + return new RegExp("^((" + words.join(")|(") + "))" + end); + } + + var octChar = "\\\\[0-7]{1,3}"; + var hexChar = "\\\\x[A-Fa-f0-9]{1,2}"; + var sChar = "\\\\[abefnrtv0%?'\"\\\\]"; + var uChar = "([^\\u0027\\u005C\\uD800-\\uDFFF]|[\\uD800-\\uDFFF][\\uDC00-\\uDFFF])"; + + var operators = parserConf.operators || wordRegexp([ + "[<>]:", "[<>=]=", "<<=?", ">>>?=?", "=>", "->", "\\/\\/", + "[\\\\%*+\\-<>!=\\/^|&\\u00F7\\u22BB]=?", "\\?", "\\$", "~", ":", + "\\u00D7", "\\u2208", "\\u2209", "\\u220B", "\\u220C", "\\u2218", + "\\u221A", "\\u221B", "\\u2229", "\\u222A", "\\u2260", "\\u2264", + "\\u2265", "\\u2286", "\\u2288", "\\u228A", "\\u22C5", + "\\b(in|isa)\\b(?!\.?\\()"], ""); + var delimiters = parserConf.delimiters || /^[;,()[\]{}]/; + var identifiers = parserConf.identifiers || + /^[_A-Za-z\u00A1-\u2217\u2219-\uFFFF][\w\u00A1-\u2217\u2219-\uFFFF]*!*/; + + var chars = wordRegexp([octChar, hexChar, sChar, uChar], "'"); + var openers = wordRegexp(["begin", "function", "type", "struct", "immutable", + "let", "macro", "for", "while", "quote", "if", "else", "elseif", "try", + "finally", "catch", "do"]); + var closers = wordRegexp(["end", "else", "elseif", "catch", "finally"]); + var keywords = wordRegexp(["if", "else", "elseif", "while", "for", "begin", + "let", "end", "do", "try", "catch", "finally", "return", "break", + "continue", "global", "local", "const", "export", "import", "importall", + "using", "function", "where", "macro", "module", "baremodule", "struct", + "type", "mutable", "immutable", "quote", "typealias", "abstract", + "primitive", "bitstype"]); + var builtins = wordRegexp(["true", "false", "nothing", "NaN", "Inf"]); + + var macro = /^@[_A-Za-z][\w]*/; + var symbol = /^:[_A-Za-z\u00A1-\uFFFF][\w\u00A1-\uFFFF]*!*/; + var stringPrefixes = /^(`|([_A-Za-z\u00A1-\uFFFF]*"("")?))/; + + function inArray(state) { + return inGenerator(state, '[') + } + + function inGenerator(state, bracket) { + var curr = currentScope(state), + prev = currentScope(state, 1); + if (typeof(bracket) === "undefined") { bracket = '('; } + if (curr === bracket || (prev === bracket && curr === "for")) { + return true; + } + return false; + } + + function currentScope(state, n) { + if (typeof(n) === "undefined") { n = 0; } + if (state.scopes.length <= n) { + return null; + } + return state.scopes[state.scopes.length - (n + 1)]; + } + + // tokenizers + function tokenBase(stream, state) { + // Handle multiline comments + if (stream.match(/^#=/, false)) { + state.tokenize = tokenComment; + return state.tokenize(stream, state); + } + + // Handle scope changes + var leavingExpr = state.leavingExpr; + if (stream.sol()) { + leavingExpr = false; + } + state.leavingExpr = false; + + if (leavingExpr) { + if (stream.match(/^'+/)) { + return "operator"; + } + } + + if (stream.match(/\.{4,}/)) { + return "error"; + } else if (stream.match(/\.{1,3}/)) { + return "operator"; + } + + if (stream.eatSpace()) { + return null; + } + + var ch = stream.peek(); + + // Handle single line comments + if (ch === '#') { + stream.skipToEnd(); + return "comment"; + } + + if (ch === '[') { + state.scopes.push('['); + } + + if (ch === '(') { + state.scopes.push('('); + } + + var scope = currentScope(state); + + if (inArray(state) && ch === ']') { + if (scope === "for") { state.scopes.pop(); } + state.scopes.pop(); + state.leavingExpr = true; + } + + if (inGenerator(state) && ch === ')') { + if (scope === "for") { state.scopes.pop(); } + state.scopes.pop(); + state.leavingExpr = true; + } + + if (inArray(state)) { + if (state.lastToken == "end" && stream.match(/^:/)) { + return "operator"; + } + if (stream.match(/^end/)) { + return "number"; + } + } + + var match; + if (match = stream.match(openers, false)) { + state.scopes.push(match[0]); + } + + if (stream.match(closers, false)) { + state.scopes.pop(); + } + + // Handle type annotations + if (stream.match(/^::(?![:\$])/)) { + state.tokenize = tokenAnnotation; + return state.tokenize(stream, state); + } + + // Handle symbols + if (!leavingExpr && stream.match(symbol) || + stream.match(/:([<>]:|<<=?|>>>?=?|->|\/\/|\.{2,3}|[\.\\%*+\-<>!\/^|&]=?|[~\?\$])/)) { + return "builtin"; + } + + // Handle parametric types + //if (stream.match(/^{[^}]*}(?=\()/)) { + // return "builtin"; + //} + + // Handle operators and Delimiters + if (stream.match(operators)) { + return "operator"; + } + + // Handle Number Literals + if (stream.match(/^\.?\d/, false)) { + var imMatcher = RegExp(/^im\b/); + var numberLiteral = false; + // Floats + if (stream.match(/^\d*\.(?!\.)\d*([Eef][\+\-]?\d+)?/i)) { numberLiteral = true; } + if (stream.match(/^\d+\.(?!\.)\d*/)) { numberLiteral = true; } + if (stream.match(/^\.\d+/)) { numberLiteral = true; } + if (stream.match(/^0x\.[0-9a-f]+p[\+\-]?\d+/i)) { numberLiteral = true; } + // Integers + if (stream.match(/^0x[0-9a-f]+/i)) { numberLiteral = true; } // Hex + if (stream.match(/^0b[01]+/i)) { numberLiteral = true; } // Binary + if (stream.match(/^0o[0-7]+/i)) { numberLiteral = true; } // Octal + if (stream.match(/^[1-9]\d*(e[\+\-]?\d+)?/)) { numberLiteral = true; } // Decimal + // Zero by itself with no other piece of number. + if (stream.match(/^0(?![\dx])/i)) { numberLiteral = true; } + if (numberLiteral) { + // Integer literals may be "long" + stream.match(imMatcher); + state.leavingExpr = true; + return "number"; + } + } + + // Handle Chars + if (stream.match(/^'/)) { + state.tokenize = tokenChar; + return state.tokenize(stream, state); + } + + // Handle Strings + if (stream.match(stringPrefixes)) { + state.tokenize = tokenStringFactory(stream.current()); + return state.tokenize(stream, state); + } + + if (stream.match(macro)) { + return "meta"; + } + + if (stream.match(delimiters)) { + return null; + } + + if (stream.match(keywords)) { + return "keyword"; + } + + if (stream.match(builtins)) { + return "builtin"; + } + + var isDefinition = state.isDefinition || state.lastToken == "function" || + state.lastToken == "macro" || state.lastToken == "type" || + state.lastToken == "struct" || state.lastToken == "immutable"; + + if (stream.match(identifiers)) { + if (isDefinition) { + if (stream.peek() === '.') { + state.isDefinition = true; + return "variable"; + } + state.isDefinition = false; + return "def"; + } + if (stream.match(/^({[^}]*})*\(/, false)) { + state.tokenize = tokenCallOrDef; + return state.tokenize(stream, state); + } + state.leavingExpr = true; + return "variable"; + } + + // Handle non-detected items + stream.next(); + return "error"; + } + + function tokenCallOrDef(stream, state) { + var match = stream.match(/^(\(\s*)/); + if (match) { + if (state.firstParenPos < 0) + state.firstParenPos = state.scopes.length; + state.scopes.push('('); + state.charsAdvanced += match[1].length; + } + if (currentScope(state) == '(' && stream.match(/^\)/)) { + state.scopes.pop(); + state.charsAdvanced += 1; + if (state.scopes.length <= state.firstParenPos) { + var isDefinition = stream.match(/^(\s*where\s+[^\s=]+)*\s*?=(?!=)/, false); + stream.backUp(state.charsAdvanced); + state.firstParenPos = -1; + state.charsAdvanced = 0; + state.tokenize = tokenBase; + if (isDefinition) + return "def"; + return "builtin"; + } + } + // Unfortunately javascript does not support multiline strings, so we have + // to undo anything done upto here if a function call or definition splits + // over two or more lines. + if (stream.match(/^$/g, false)) { + stream.backUp(state.charsAdvanced); + while (state.scopes.length > state.firstParenPos) + state.scopes.pop(); + state.firstParenPos = -1; + state.charsAdvanced = 0; + state.tokenize = tokenBase; + return "builtin"; + } + state.charsAdvanced += stream.match(/^([^()]*)/)[1].length; + return state.tokenize(stream, state); + } + + function tokenAnnotation(stream, state) { + stream.match(/.*?(?=,|;|{|}|\(|\)|=|$|\s)/); + if (stream.match(/^{/)) { + state.nestedLevels++; + } else if (stream.match(/^}/)) { + state.nestedLevels--; + } + if (state.nestedLevels > 0) { + stream.match(/.*?(?={|})/) || stream.next(); + } else if (state.nestedLevels == 0) { + state.tokenize = tokenBase; + } + return "builtin"; + } + + function tokenComment(stream, state) { + if (stream.match(/^#=/)) { + state.nestedLevels++; + } + if (!stream.match(/.*?(?=(#=|=#))/)) { + stream.skipToEnd(); + } + if (stream.match(/^=#/)) { + state.nestedLevels--; + if (state.nestedLevels == 0) + state.tokenize = tokenBase; + } + return "comment"; + } + + function tokenChar(stream, state) { + var isChar = false, match; + if (stream.match(chars)) { + isChar = true; + } else if (match = stream.match(/\\u([a-f0-9]{1,4})(?=')/i)) { + var value = parseInt(match[1], 16); + if (value <= 55295 || value >= 57344) { // (U+0,U+D7FF), (U+E000,U+FFFF) + isChar = true; + stream.next(); + } + } else if (match = stream.match(/\\U([A-Fa-f0-9]{5,8})(?=')/)) { + var value = parseInt(match[1], 16); + if (value <= 1114111) { // U+10FFFF + isChar = true; + stream.next(); + } + } + if (isChar) { + state.leavingExpr = true; + state.tokenize = tokenBase; + return "string"; + } + if (!stream.match(/^[^']+(?=')/)) { stream.skipToEnd(); } + if (stream.match(/^'/)) { state.tokenize = tokenBase; } + return "error"; + } + + function tokenStringFactory(delimiter) { + if (delimiter.substr(-3) === '"""') { + delimiter = '"""'; + } else if (delimiter.substr(-1) === '"') { + delimiter = '"'; + } + function tokenString(stream, state) { + if (stream.eat('\\')) { + stream.next(); + } else if (stream.match(delimiter)) { + state.tokenize = tokenBase; + state.leavingExpr = true; + return "string"; + } else { + stream.eat(/[`"]/); + } + stream.eatWhile(/[^\\`"]/); + return "string"; + } + return tokenString; + } + + var external = { + startState: function() { + return { + tokenize: tokenBase, + scopes: [], + lastToken: null, + leavingExpr: false, + isDefinition: false, + nestedLevels: 0, + charsAdvanced: 0, + firstParenPos: -1 + }; + }, + + token: function(stream, state) { + var style = state.tokenize(stream, state); + var current = stream.current(); + + if (current && style) { + state.lastToken = current; + } + + return style; + }, + + indent: function(state, textAfter) { + var delta = 0; + if ( textAfter === ']' || textAfter === ')' || textAfter === "end" || + textAfter === "else" || textAfter === "catch" || textAfter === "elseif" || + textAfter === "finally" ) { + delta = -1; + } + return (state.scopes.length + delta) * config.indentUnit; + }, + + electricInput: /\b(end|else|catch|finally)\b/, + blockCommentStart: "#=", + blockCommentEnd: "=#", + lineComment: "#", + fold: "indent" + }; + return external; +}); + + +CodeMirror.defineMIME("text/x-julia", "julia"); + +}); diff --git a/modules/editor/codemirror/mode/markdown/markdown.min.js b/modules/editor/codemirror/mode/markdown/markdown.min.js @@ -0,0 +1,861 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../xml/xml"), require("../meta")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../xml/xml", "../meta"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) { + + var htmlMode = CodeMirror.getMode(cmCfg, "text/html"); + var htmlModeMissing = htmlMode.name == "null" + + function getMode(name) { + if (CodeMirror.findModeByName) { + var found = CodeMirror.findModeByName(name); + if (found) name = found.mime || found.mimes[0]; + } + var mode = CodeMirror.getMode(cmCfg, name); + return mode.name == "null" ? null : mode; + } + + // Should characters that affect highlighting be highlighted separate? + // Does not include characters that will be output (such as `1.` and `-` for lists) + if (modeCfg.highlightFormatting === undefined) + modeCfg.highlightFormatting = false; + + // Maximum number of nested blockquotes. Set to 0 for infinite nesting. + // Excess `>` will emit `error` token. + if (modeCfg.maxBlockquoteDepth === undefined) + modeCfg.maxBlockquoteDepth = 0; + + // Turn on task lists? ("- [ ] " and "- [x] ") + if (modeCfg.taskLists === undefined) modeCfg.taskLists = false; + + // Turn on strikethrough syntax + if (modeCfg.strikethrough === undefined) + modeCfg.strikethrough = false; + + if (modeCfg.emoji === undefined) + modeCfg.emoji = false; + + if (modeCfg.fencedCodeBlockHighlighting === undefined) + modeCfg.fencedCodeBlockHighlighting = true; + + if (modeCfg.xml === undefined) + modeCfg.xml = true; + + // Allow token types to be overridden by user-provided token types. + if (modeCfg.tokenTypeOverrides === undefined) + modeCfg.tokenTypeOverrides = {}; + + var tokenTypes = { + header: "header", + code: "comment", + quote: "quote", + list1: "variable-2", + list2: "variable-3", + list3: "keyword", + hr: "hr", + image: "image", + imageAltText: "image-alt-text", + imageMarker: "image-marker", + formatting: "formatting", + linkInline: "link", + linkEmail: "link", + linkText: "link", + linkHref: "string", + em: "em", + strong: "strong", + strikethrough: "strikethrough", + emoji: "builtin" + }; + + for (var tokenType in tokenTypes) { + if (tokenTypes.hasOwnProperty(tokenType) && modeCfg.tokenTypeOverrides[tokenType]) { + tokenTypes[tokenType] = modeCfg.tokenTypeOverrides[tokenType]; + } + } + + var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/ + , listRE = /^(?:[*\-+]|^[0-9]+([.)]))\s+/ + , taskListRE = /^\[(x| )\](?=\s)/i // Must follow listRE + , atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/ + , setextHeaderRE = /^ *(?:\={1,}|-{1,})\s*$/ + , textRE = /^[^#!\[\]*_\\<>` "'(~:]+/ + , fencedCodeRE = /^(~~~+|```+)[ \t]*([\w+#-]*)[^\n`]*$/ + , linkDefRE = /^\s*\[[^\]]+?\]:\s*\S+(\s*\S*\s*)?$/ // naive link-definition + , punctuation = /[!\"#$%&\'()*+,\-\.\/:;<=>?@\[\\\]^_`{|}~—]/ + , expandedTab = " " // CommonMark specifies tab as 4 spaces + + function switchInline(stream, state, f) { + state.f = state.inline = f; + return f(stream, state); + } + + function switchBlock(stream, state, f) { + state.f = state.block = f; + return f(stream, state); + } + + function lineIsEmpty(line) { + return !line || !/\S/.test(line.string) + } + + // Blocks + + function blankLine(state) { + // Reset linkTitle state + state.linkTitle = false; + // Reset EM state + state.em = false; + // Reset STRONG state + state.strong = false; + // Reset strikethrough state + state.strikethrough = false; + // Reset state.quote + state.quote = 0; + // Reset state.indentedCode + state.indentedCode = false; + if (state.f == htmlBlock) { + state.f = inlineNormal; + state.block = blockNormal; + } + // Reset state.trailingSpace + state.trailingSpace = 0; + state.trailingSpaceNewLine = false; + // Mark this line as blank + state.prevLine = state.thisLine + state.thisLine = {stream: null} + return null; + } + + function blockNormal(stream, state) { + var firstTokenOnLine = stream.column() === state.indentation; + var prevLineLineIsEmpty = lineIsEmpty(state.prevLine.stream); + var prevLineIsIndentedCode = state.indentedCode; + var prevLineIsHr = state.prevLine.hr; + var prevLineIsList = state.list !== false; + var maxNonCodeIndentation = (state.listStack[state.listStack.length - 1] || 0) + 3; + + state.indentedCode = false; + + var lineIndentation = state.indentation; + // compute once per line (on first token) + if (state.indentationDiff === null) { + state.indentationDiff = state.indentation; + if (prevLineIsList) { + state.list = null; + // While this list item's marker's indentation is less than the deepest + // list item's content's indentation,pop the deepest list item + // indentation off the stack, and update block indentation state + while (lineIndentation < state.listStack[state.listStack.length - 1]) { + state.listStack.pop(); + if (state.listStack.length) { + state.indentation = state.listStack[state.listStack.length - 1]; + // less than the first list's indent -> the line is no longer a list + } else { + state.list = false; + } + } + if (state.list !== false) { + state.indentationDiff = lineIndentation - state.listStack[state.listStack.length - 1] + } + } + } + + // not comprehensive (currently only for setext detection purposes) + var allowsInlineContinuation = ( + !prevLineLineIsEmpty && !prevLineIsHr && !state.prevLine.header && + (!prevLineIsList || !prevLineIsIndentedCode) && + !state.prevLine.fencedCodeEnd + ); + + var isHr = (state.list === false || prevLineIsHr || prevLineLineIsEmpty) && + state.indentation <= maxNonCodeIndentation && stream.match(hrRE); + + var match = null; + if (state.indentationDiff >= 4 && (prevLineIsIndentedCode || state.prevLine.fencedCodeEnd || + state.prevLine.header || prevLineLineIsEmpty)) { + stream.skipToEnd(); + state.indentedCode = true; + return tokenTypes.code; + } else if (stream.eatSpace()) { + return null; + } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(atxHeaderRE)) && match[1].length <= 6) { + state.quote = 0; + state.header = match[1].length; + state.thisLine.header = true; + if (modeCfg.highlightFormatting) state.formatting = "header"; + state.f = state.inline; + return getType(state); + } else if (state.indentation <= maxNonCodeIndentation && stream.eat('>')) { + state.quote = firstTokenOnLine ? 1 : state.quote + 1; + if (modeCfg.highlightFormatting) state.formatting = "quote"; + stream.eatSpace(); + return getType(state); + } else if (!isHr && !state.setext && firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(listRE))) { + var listType = match[1] ? "ol" : "ul"; + + state.indentation = lineIndentation + stream.current().length; + state.list = true; + state.quote = 0; + + // Add this list item's content's indentation to the stack + state.listStack.push(state.indentation); + + if (modeCfg.taskLists && stream.match(taskListRE, false)) { + state.taskList = true; + } + state.f = state.inline; + if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType]; + return getType(state); + } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(fencedCodeRE, true))) { + state.quote = 0; + state.fencedEndRE = new RegExp(match[1] + "+ *$"); + // try switching mode + state.localMode = modeCfg.fencedCodeBlockHighlighting && getMode(match[2]); + if (state.localMode) state.localState = CodeMirror.startState(state.localMode); + state.f = state.block = local; + if (modeCfg.highlightFormatting) state.formatting = "code-block"; + state.code = -1 + return getType(state); + // SETEXT has lowest block-scope precedence after HR, so check it after + // the others (code, blockquote, list...) + } else if ( + // if setext set, indicates line after ---/=== + state.setext || ( + // line before ---/=== + (!allowsInlineContinuation || !prevLineIsList) && !state.quote && state.list === false && + !state.code && !isHr && !linkDefRE.test(stream.string) && + (match = stream.lookAhead(1)) && (match = match.match(setextHeaderRE)) + ) + ) { + if ( !state.setext ) { + state.header = match[0].charAt(0) == '=' ? 1 : 2; + state.setext = state.header; + } else { + state.header = state.setext; + // has no effect on type so we can reset it now + state.setext = 0; + stream.skipToEnd(); + if (modeCfg.highlightFormatting) state.formatting = "header"; + } + state.thisLine.header = true; + state.f = state.inline; + return getType(state); + } else if (isHr) { + stream.skipToEnd(); + state.hr = true; + state.thisLine.hr = true; + return tokenTypes.hr; + } else if (stream.peek() === '[') { + return switchInline(stream, state, footnoteLink); + } + + return switchInline(stream, state, state.inline); + } + + function htmlBlock(stream, state) { + var style = htmlMode.token(stream, state.htmlState); + if (!htmlModeMissing) { + var inner = CodeMirror.innerMode(htmlMode, state.htmlState) + if ((inner.mode.name == "xml" && inner.state.tagStart === null && + (!inner.state.context && inner.state.tokenize.isInText)) || + (state.md_inside && stream.current().indexOf(">") > -1)) { + state.f = inlineNormal; + state.block = blockNormal; + state.htmlState = null; + } + } + return style; + } + + function local(stream, state) { + var currListInd = state.listStack[state.listStack.length - 1] || 0; + var hasExitedList = state.indentation < currListInd; + var maxFencedEndInd = currListInd + 3; + if (state.fencedEndRE && state.indentation <= maxFencedEndInd && (hasExitedList || stream.match(state.fencedEndRE))) { + if (modeCfg.highlightFormatting) state.formatting = "code-block"; + var returnType; + if (!hasExitedList) returnType = getType(state) + state.localMode = state.localState = null; + state.block = blockNormal; + state.f = inlineNormal; + state.fencedEndRE = null; + state.code = 0 + state.thisLine.fencedCodeEnd = true; + if (hasExitedList) return switchBlock(stream, state, state.block); + return returnType; + } else if (state.localMode) { + return state.localMode.token(stream, state.localState); + } else { + stream.skipToEnd(); + return tokenTypes.code; + } + } + + // Inline + function getType(state) { + var styles = []; + + if (state.formatting) { + styles.push(tokenTypes.formatting); + + if (typeof state.formatting === "string") state.formatting = [state.formatting]; + + for (var i = 0; i < state.formatting.length; i++) { + styles.push(tokenTypes.formatting + "-" + state.formatting[i]); + + if (state.formatting[i] === "header") { + styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.header); + } + + // Add `formatting-quote` and `formatting-quote-#` for blockquotes + // Add `error` instead if the maximum blockquote nesting depth is passed + if (state.formatting[i] === "quote") { + if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) { + styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.quote); + } else { + styles.push("error"); + } + } + } + } + + if (state.taskOpen) { + styles.push("meta"); + return styles.length ? styles.join(' ') : null; + } + if (state.taskClosed) { + styles.push("property"); + return styles.length ? styles.join(' ') : null; + } + + if (state.linkHref) { + styles.push(tokenTypes.linkHref, "url"); + } else { // Only apply inline styles to non-url text + if (state.strong) { styles.push(tokenTypes.strong); } + if (state.em) { styles.push(tokenTypes.em); } + if (state.strikethrough) { styles.push(tokenTypes.strikethrough); } + if (state.emoji) { styles.push(tokenTypes.emoji); } + if (state.linkText) { styles.push(tokenTypes.linkText); } + if (state.code) { styles.push(tokenTypes.code); } + if (state.image) { styles.push(tokenTypes.image); } + if (state.imageAltText) { styles.push(tokenTypes.imageAltText, "link"); } + if (state.imageMarker) { styles.push(tokenTypes.imageMarker); } + } + + if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + "-" + state.header); } + + if (state.quote) { + styles.push(tokenTypes.quote); + + // Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth + if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) { + styles.push(tokenTypes.quote + "-" + state.quote); + } else { + styles.push(tokenTypes.quote + "-" + modeCfg.maxBlockquoteDepth); + } + } + + if (state.list !== false) { + var listMod = (state.listStack.length - 1) % 3; + if (!listMod) { + styles.push(tokenTypes.list1); + } else if (listMod === 1) { + styles.push(tokenTypes.list2); + } else { + styles.push(tokenTypes.list3); + } + } + + if (state.trailingSpaceNewLine) { + styles.push("trailing-space-new-line"); + } else if (state.trailingSpace) { + styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b")); + } + + return styles.length ? styles.join(' ') : null; + } + + function handleText(stream, state) { + if (stream.match(textRE, true)) { + return getType(state); + } + return undefined; + } + + function inlineNormal(stream, state) { + var style = state.text(stream, state); + if (typeof style !== 'undefined') + return style; + + if (state.list) { // List marker (*, +, -, 1., etc) + state.list = null; + return getType(state); + } + + if (state.taskList) { + var taskOpen = stream.match(taskListRE, true)[1] === " "; + if (taskOpen) state.taskOpen = true; + else state.taskClosed = true; + if (modeCfg.highlightFormatting) state.formatting = "task"; + state.taskList = false; + return getType(state); + } + + state.taskOpen = false; + state.taskClosed = false; + + if (state.header && stream.match(/^#+$/, true)) { + if (modeCfg.highlightFormatting) state.formatting = "header"; + return getType(state); + } + + var ch = stream.next(); + + // Matches link titles present on next line + if (state.linkTitle) { + state.linkTitle = false; + var matchCh = ch; + if (ch === '(') { + matchCh = ')'; + } + matchCh = (matchCh+'').replace(/([.?*+^\[\]\\(){}|-])/g, "\\$1"); + var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; + if (stream.match(new RegExp(regex), true)) { + return tokenTypes.linkHref; + } + } + + // If this block is changed, it may need to be updated in GFM mode + if (ch === '`') { + var previousFormatting = state.formatting; + if (modeCfg.highlightFormatting) state.formatting = "code"; + stream.eatWhile('`'); + var count = stream.current().length + if (state.code == 0 && (!state.quote || count == 1)) { + state.code = count + return getType(state) + } else if (count == state.code) { // Must be exact + var t = getType(state) + state.code = 0 + return t + } else { + state.formatting = previousFormatting + return getType(state) + } + } else if (state.code) { + return getType(state); + } + + if (ch === '\\') { + stream.next(); + if (modeCfg.highlightFormatting) { + var type = getType(state); + var formattingEscape = tokenTypes.formatting + "-escape"; + return type ? type + " " + formattingEscape : formattingEscape; + } + } + + if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) { + state.imageMarker = true; + state.image = true; + if (modeCfg.highlightFormatting) state.formatting = "image"; + return getType(state); + } + + if (ch === '[' && state.imageMarker && stream.match(/[^\]]*\](\(.*?\)| ?\[.*?\])/, false)) { + state.imageMarker = false; + state.imageAltText = true + if (modeCfg.highlightFormatting) state.formatting = "image"; + return getType(state); + } + + if (ch === ']' && state.imageAltText) { + if (modeCfg.highlightFormatting) state.formatting = "image"; + var type = getType(state); + state.imageAltText = false; + state.image = false; + state.inline = state.f = linkHref; + return type; + } + + if (ch === '[' && !state.image) { + state.linkText = true; + if (modeCfg.highlightFormatting) state.formatting = "link"; + return getType(state); + } + + if (ch === ']' && state.linkText) { + if (modeCfg.highlightFormatting) state.formatting = "link"; + var type = getType(state); + state.linkText = false; + state.inline = state.f = stream.match(/\(.*?\)| ?\[.*?\]/, false) ? linkHref : inlineNormal + return type; + } + + if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) { + state.f = state.inline = linkInline; + if (modeCfg.highlightFormatting) state.formatting = "link"; + var type = getType(state); + if (type){ + type += " "; + } else { + type = ""; + } + return type + tokenTypes.linkInline; + } + + if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) { + state.f = state.inline = linkInline; + if (modeCfg.highlightFormatting) state.formatting = "link"; + var type = getType(state); + if (type){ + type += " "; + } else { + type = ""; + } + return type + tokenTypes.linkEmail; + } + + if (modeCfg.xml && ch === '<' && stream.match(/^(!--|[a-z]+(?:\s+[a-z_:.\-]+(?:\s*=\s*[^ >]+)?)*\s*>)/i, false)) { + var end = stream.string.indexOf(">", stream.pos); + if (end != -1) { + var atts = stream.string.substring(stream.start, end); + if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) state.md_inside = true; + } + stream.backUp(1); + state.htmlState = CodeMirror.startState(htmlMode); + return switchBlock(stream, state, htmlBlock); + } + + if (modeCfg.xml && ch === '<' && stream.match(/^\/\w*?>/)) { + state.md_inside = false; + return "tag"; + } else if (ch === "*" || ch === "_") { + var len = 1, before = stream.pos == 1 ? " " : stream.string.charAt(stream.pos - 2) + while (len < 3 && stream.eat(ch)) len++ + var after = stream.peek() || " " + // See http://spec.commonmark.org/0.27/#emphasis-and-strong-emphasis + var leftFlanking = !/\s/.test(after) && (!punctuation.test(after) || /\s/.test(before) || punctuation.test(before)) + var rightFlanking = !/\s/.test(before) && (!punctuation.test(before) || /\s/.test(after) || punctuation.test(after)) + var setEm = null, setStrong = null + if (len % 2) { // Em + if (!state.em && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before))) + setEm = true + else if (state.em == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after))) + setEm = false + } + if (len > 1) { // Strong + if (!state.strong && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before))) + setStrong = true + else if (state.strong == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after))) + setStrong = false + } + if (setStrong != null || setEm != null) { + if (modeCfg.highlightFormatting) state.formatting = setEm == null ? "strong" : setStrong == null ? "em" : "strong em" + if (setEm === true) state.em = ch + if (setStrong === true) state.strong = ch + var t = getType(state) + if (setEm === false) state.em = false + if (setStrong === false) state.strong = false + return t + } + } else if (ch === ' ') { + if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces + if (stream.peek() === ' ') { // Surrounded by spaces, ignore + return getType(state); + } else { // Not surrounded by spaces, back up pointer + stream.backUp(1); + } + } + } + + if (modeCfg.strikethrough) { + if (ch === '~' && stream.eatWhile(ch)) { + if (state.strikethrough) {// Remove strikethrough + if (modeCfg.highlightFormatting) state.formatting = "strikethrough"; + var t = getType(state); + state.strikethrough = false; + return t; + } else if (stream.match(/^[^\s]/, false)) {// Add strikethrough + state.strikethrough = true; + if (modeCfg.highlightFormatting) state.formatting = "strikethrough"; + return getType(state); + } + } else if (ch === ' ') { + if (stream.match(/^~~/, true)) { // Probably surrounded by space + if (stream.peek() === ' ') { // Surrounded by spaces, ignore + return getType(state); + } else { // Not surrounded by spaces, back up pointer + stream.backUp(2); + } + } + } + } + + if (modeCfg.emoji && ch === ":" && stream.match(/^[a-z_\d+-]+:/)) { + state.emoji = true; + if (modeCfg.highlightFormatting) state.formatting = "emoji"; + var retType = getType(state); + state.emoji = false; + return retType; + } + + if (ch === ' ') { + if (stream.match(/ +$/, false)) { + state.trailingSpace++; + } else if (state.trailingSpace) { + state.trailingSpaceNewLine = true; + } + } + + return getType(state); + } + + function linkInline(stream, state) { + var ch = stream.next(); + + if (ch === ">") { + state.f = state.inline = inlineNormal; + if (modeCfg.highlightFormatting) state.formatting = "link"; + var type = getType(state); + if (type){ + type += " "; + } else { + type = ""; + } + return type + tokenTypes.linkInline; + } + + stream.match(/^[^>]+/, true); + + return tokenTypes.linkInline; + } + + function linkHref(stream, state) { + // Check if space, and return NULL if so (to avoid marking the space) + if(stream.eatSpace()){ + return null; + } + var ch = stream.next(); + if (ch === '(' || ch === '[') { + state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]"); + if (modeCfg.highlightFormatting) state.formatting = "link-string"; + state.linkHref = true; + return getType(state); + } + return 'error'; + } + + var linkRE = { + ")": /^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/, + "]": /^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\]]|\\.)*\])*?(?=\])/ + } + + function getLinkHrefInside(endChar) { + return function(stream, state) { + var ch = stream.next(); + + if (ch === endChar) { + state.f = state.inline = inlineNormal; + if (modeCfg.highlightFormatting) state.formatting = "link-string"; + var returnState = getType(state); + state.linkHref = false; + return returnState; + } + + stream.match(linkRE[endChar]) + state.linkHref = true; + return getType(state); + }; + } + + function footnoteLink(stream, state) { + if (stream.match(/^([^\]\\]|\\.)*\]:/, false)) { + state.f = footnoteLinkInside; + stream.next(); // Consume [ + if (modeCfg.highlightFormatting) state.formatting = "link"; + state.linkText = true; + return getType(state); + } + return switchInline(stream, state, inlineNormal); + } + + function footnoteLinkInside(stream, state) { + if (stream.match(/^\]:/, true)) { + state.f = state.inline = footnoteUrl; + if (modeCfg.highlightFormatting) state.formatting = "link"; + var returnType = getType(state); + state.linkText = false; + return returnType; + } + + stream.match(/^([^\]\\]|\\.)+/, true); + + return tokenTypes.linkText; + } + + function footnoteUrl(stream, state) { + // Check if space, and return NULL if so (to avoid marking the space) + if(stream.eatSpace()){ + return null; + } + // Match URL + stream.match(/^[^\s]+/, true); + // Check for link title + if (stream.peek() === undefined) { // End of line, set flag to check next line + state.linkTitle = true; + } else { // More content on line, check if link title + stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); + } + state.f = state.inline = inlineNormal; + return tokenTypes.linkHref + " url"; + } + + var mode = { + startState: function() { + return { + f: blockNormal, + + prevLine: {stream: null}, + thisLine: {stream: null}, + + block: blockNormal, + htmlState: null, + indentation: 0, + + inline: inlineNormal, + text: handleText, + + formatting: false, + linkText: false, + linkHref: false, + linkTitle: false, + code: 0, + em: false, + strong: false, + header: 0, + setext: 0, + hr: false, + taskList: false, + list: false, + listStack: [], + quote: 0, + trailingSpace: 0, + trailingSpaceNewLine: false, + strikethrough: false, + emoji: false, + fencedEndRE: null + }; + }, + + copyState: function(s) { + return { + f: s.f, + + prevLine: s.prevLine, + thisLine: s.thisLine, + + block: s.block, + htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState), + indentation: s.indentation, + + localMode: s.localMode, + localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null, + + inline: s.inline, + text: s.text, + formatting: false, + linkText: s.linkText, + linkTitle: s.linkTitle, + code: s.code, + em: s.em, + strong: s.strong, + strikethrough: s.strikethrough, + emoji: s.emoji, + header: s.header, + setext: s.setext, + hr: s.hr, + taskList: s.taskList, + list: s.list, + listStack: s.listStack.slice(0), + quote: s.quote, + indentedCode: s.indentedCode, + trailingSpace: s.trailingSpace, + trailingSpaceNewLine: s.trailingSpaceNewLine, + md_inside: s.md_inside, + fencedEndRE: s.fencedEndRE + }; + }, + + token: function(stream, state) { + + // Reset state.formatting + state.formatting = false; + + if (stream != state.thisLine.stream) { + state.header = 0; + state.hr = false; + + if (stream.match(/^\s*$/, true)) { + blankLine(state); + return null; + } + + state.prevLine = state.thisLine + state.thisLine = {stream: stream} + + // Reset state.taskList + state.taskList = false; + + // Reset state.trailingSpace + state.trailingSpace = 0; + state.trailingSpaceNewLine = false; + + if (!state.localState) { + state.f = state.block; + if (state.f != htmlBlock) { + var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, expandedTab).length; + state.indentation = indentation; + state.indentationDiff = null; + if (indentation > 0) return null; + } + } + } + return state.f(stream, state); + }, + + innerMode: function(state) { + if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode}; + if (state.localState) return {state: state.localState, mode: state.localMode}; + return {state: state, mode: mode}; + }, + + indent: function(state, textAfter, line) { + if (state.block == htmlBlock && htmlMode.indent) return htmlMode.indent(state.htmlState, textAfter, line) + if (state.localState && state.localMode.indent) return state.localMode.indent(state.localState, textAfter, line) + return CodeMirror.Pass + }, + + blankLine: blankLine, + + getType: getType, + + closeBrackets: "()[]{}''\"\"``", + fold: "markdown" + }; + return mode; +}, "xml"); + +CodeMirror.defineMIME("text/x-markdown", "markdown"); + +}); diff --git a/modules/editor/codemirror/mode/markdown/test.min.js b/modules/editor/codemirror/mode/markdown/test.min.js @@ -0,0 +1,1289 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var config = {tabSize: 4, indentUnit: 2} + var mode = CodeMirror.getMode(config, "markdown"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + var modeHighlightFormatting = CodeMirror.getMode(config, {name: "markdown", highlightFormatting: true}); + function FT(name) { test.mode(name, modeHighlightFormatting, Array.prototype.slice.call(arguments, 1)); } + var modeMT_noXml = CodeMirror.getMode(config, {name: "markdown", xml: false}); + function MT_noXml(name) { test.mode(name, modeMT_noXml, Array.prototype.slice.call(arguments, 1)); } + var modeMT_noFencedHighlight = CodeMirror.getMode(config, {name: "markdown", fencedCodeBlockHighlighting: false}); + function MT_noFencedHighlight(name) { test.mode(name, modeMT_noFencedHighlight, Array.prototype.slice.call(arguments, 1)); } + var modeAtxNoSpace = CodeMirror.getMode(config, {name: "markdown", allowAtxHeaderWithoutSpace: true}); + function AtxNoSpaceTest(name) { test.mode(name, modeAtxNoSpace, Array.prototype.slice.call(arguments, 1)); } + var modeOverrideClasses = CodeMirror.getMode(config, { + name: "markdown", + strikethrough: true, + emoji: true, + tokenTypeOverrides: { + "header" : "override-header", + "code" : "override-code", + "quote" : "override-quote", + "list1" : "override-list1", + "list2" : "override-list2", + "list3" : "override-list3", + "hr" : "override-hr", + "image" : "override-image", + "imageAltText": "override-image-alt-text", + "imageMarker": "override-image-marker", + "linkInline" : "override-link-inline", + "linkEmail" : "override-link-email", + "linkText" : "override-link-text", + "linkHref" : "override-link-href", + "em" : "override-em", + "strong" : "override-strong", + "strikethrough" : "override-strikethrough", + "emoji" : "override-emoji" + }}); + function TokenTypeOverrideTest(name) { test.mode(name, modeOverrideClasses, Array.prototype.slice.call(arguments, 1)); } + var modeFormattingOverride = CodeMirror.getMode(config, { + name: "markdown", + highlightFormatting: true, + tokenTypeOverrides: { + "formatting" : "override-formatting" + }}); + function FormatTokenTypeOverrideTest(name) { test.mode(name, modeFormattingOverride, Array.prototype.slice.call(arguments, 1)); } + + + FT("formatting_emAsterisk", + "[em&formatting&formatting-em *][em foo][em&formatting&formatting-em *]"); + + FT("formatting_emUnderscore", + "[em&formatting&formatting-em _][em foo][em&formatting&formatting-em _]"); + + FT("formatting_strongAsterisk", + "[strong&formatting&formatting-strong **][strong foo][strong&formatting&formatting-strong **]"); + + FT("formatting_strongUnderscore", + "[strong&formatting&formatting-strong __][strong foo][strong&formatting&formatting-strong __]"); + + FT("formatting_codeBackticks", + "[comment&formatting&formatting-code `][comment foo][comment&formatting&formatting-code `]"); + + FT("formatting_doubleBackticks", + "[comment&formatting&formatting-code ``][comment foo ` bar][comment&formatting&formatting-code ``]"); + + FT("formatting_atxHeader", + "[header&header-1&formatting&formatting-header&formatting-header-1 # ][header&header-1 foo # bar ][header&header-1&formatting&formatting-header&formatting-header-1 #]"); + + FT("formatting_setextHeader", + "[header&header-1 foo]", + "[header&header-1&formatting&formatting-header&formatting-header-1 =]"); + + FT("formatting_blockquote", + "[quote&quote-1&formatting&formatting-quote&formatting-quote-1 > ][quote&quote-1 foo]"); + + FT("formatting_list", + "[variable-2&formatting&formatting-list&formatting-list-ul - ][variable-2 foo]"); + FT("formatting_list", + "[variable-2&formatting&formatting-list&formatting-list-ol 1. ][variable-2 foo]"); + + FT("formatting_link", + "[link&formatting&formatting-link [][link foo][link&formatting&formatting-link ]]][string&formatting&formatting-link-string&url (][string&url http://example.com/][string&formatting&formatting-link-string&url )]"); + + FT("formatting_linkReference", + "[link&formatting&formatting-link [][link foo][link&formatting&formatting-link ]]][string&formatting&formatting-link-string&url [][string&url bar][string&formatting&formatting-link-string&url ]]]", + "[link&formatting&formatting-link [][link bar][link&formatting&formatting-link ]]:] [string&url http://example.com/]"); + + FT("formatting_linkWeb", + "[link&formatting&formatting-link <][link http://example.com/][link&formatting&formatting-link >]"); + + FT("formatting_linkEmail", + "[link&formatting&formatting-link <][link user@example.com][link&formatting&formatting-link >]"); + + FT("formatting_escape", + "[formatting-escape \\*]"); + + FT("formatting_image", + "[formatting&formatting-image&image&image-marker !][formatting&formatting-image&image&image-alt-text&link [[][image&image-alt-text&link alt text][formatting&formatting-image&image&image-alt-text&link ]]][formatting&formatting-link-string&string&url (][url&string http://link.to/image.jpg][formatting&formatting-link-string&string&url )]"); + + FT("codeBlock", + "[comment&formatting&formatting-code-block ```css]", + "[tag foo]", + "[comment&formatting&formatting-code-block ```]"); + + MT("plainText", + "foo"); + + // Don't style single trailing space + MT("trailingSpace1", + "foo "); + + // Two or more trailing spaces should be styled with line break character + MT("trailingSpace2", + "foo[trailing-space-a ][trailing-space-new-line ]"); + + MT("trailingSpace3", + "foo[trailing-space-a ][trailing-space-b ][trailing-space-new-line ]"); + + MT("trailingSpace4", + "foo[trailing-space-a ][trailing-space-b ][trailing-space-a ][trailing-space-new-line ]"); + + // Code blocks using 4 spaces (regardless of CodeMirror.tabSize value) + MT("codeBlocksUsing4Spaces", + " [comment foo]"); + + // Code blocks using 4 spaces with internal indentation + MT("codeBlocksUsing4SpacesIndentation", + " [comment bar]", + " [comment hello]", + " [comment world]", + " [comment foo]", + "bar"); + + // Code blocks should end even after extra indented lines + MT("codeBlocksWithTrailingIndentedLine", + " [comment foo]", + " [comment bar]", + " [comment baz]", + " ", + "hello"); + + // Code blocks using 1 tab (regardless of CodeMirror.indentWithTabs value) + MT("codeBlocksUsing1Tab", + "\t[comment foo]"); + + // No code blocks directly after paragraph + // http://spec.commonmark.org/0.19/#example-65 + MT("noCodeBlocksAfterParagraph", + "Foo", + " Bar"); + + MT("codeBlocksAfterATX", + "[header&header-1 # foo]", + " [comment code]"); + + MT("codeBlocksAfterSetext", + "[header&header-2 foo]", + "[header&header-2 ---]", + " [comment code]"); + + MT("codeBlocksAfterFencedCode", + "[comment ```]", + "[comment foo]", + "[comment ```]", + " [comment code]"); + + // Inline code using backticks + MT("inlineCodeUsingBackticks", + "foo [comment `bar`]"); + + // Block code using single backtick (shouldn't work) + MT("blockCodeSingleBacktick", + "[comment `]", + "[comment foo]", + "[comment `]"); + + // Unclosed backticks + // Instead of simply marking as CODE, it would be nice to have an + // incomplete flag for CODE, that is styled slightly different. + MT("unclosedBackticks", + "foo [comment `bar]"); + + // Per documentation: "To include a literal backtick character within a + // code span, you can use multiple backticks as the opening and closing + // delimiters" + MT("doubleBackticks", + "[comment ``foo ` bar``]"); + + // Tests based on Dingus + // http://daringfireball.net/projects/markdown/dingus + // + // Multiple backticks within an inline code block + MT("consecutiveBackticks", + "[comment `foo```bar`]"); + + // Multiple backticks within an inline code block with a second code block + MT("consecutiveBackticks", + "[comment `foo```bar`] hello [comment `world`]"); + + // Unclosed with several different groups of backticks + MT("unclosedBackticks", + "[comment ``foo ``` bar` hello]"); + + // Closed with several different groups of backticks + MT("closedBackticks", + "[comment ``foo ``` bar` hello``] world"); + + // info string cannot contain backtick, thus should result in inline code + MT("closingFencedMarksOnSameLine", + "[comment ``` code ```] foo"); + + // atx headers + // http://daringfireball.net/projects/markdown/syntax#header + + MT("atxH1", + "[header&header-1 # foo]"); + + MT("atxH2", + "[header&header-2 ## foo]"); + + MT("atxH3", + "[header&header-3 ### foo]"); + + MT("atxH4", + "[header&header-4 #### foo]"); + + MT("atxH5", + "[header&header-5 ##### foo]"); + + MT("atxH6", + "[header&header-6 ###### foo]"); + + // http://spec.commonmark.org/0.19/#example-24 + MT("noAtxH7", + "####### foo"); + + // http://spec.commonmark.org/0.19/#example-25 + MT("noAtxH1WithoutSpace", + "#5 bolt"); + + // CommonMark requires a space after # but most parsers don't + AtxNoSpaceTest("atxNoSpaceAllowed_H1NoSpace", + "[header&header-1 #foo]"); + + AtxNoSpaceTest("atxNoSpaceAllowed_H4NoSpace", + "[header&header-4 ####foo]"); + + AtxNoSpaceTest("atxNoSpaceAllowed_H1Space", + "[header&header-1 # foo]"); + + // Inline styles should be parsed inside headers + MT("atxH1inline", + "[header&header-1 # foo ][header&header-1&em *bar*]"); + + MT("atxIndentedTooMuch", + "[header&header-1 # foo]", + " [comment # bar]"); + + // disable atx inside blockquote until we implement proper blockquote inner mode + // TODO: fix to be CommonMark-compliant + MT("atxNestedInsideBlockquote", + "[quote&quote-1 > # foo]"); + + MT("atxAfterBlockquote", + "[quote&quote-1 > foo]", + "[header&header-1 # bar]"); + + // Setext headers - H1, H2 + // Per documentation, "Any number of underlining =’s or -’s will work." + // http://daringfireball.net/projects/markdown/syntax#header + // Ideally, the text would be marked as `header` as well, but this is + // not really feasible at the moment. So, instead, we're testing against + // what works today, to avoid any regressions. + // + // Check if single underlining = works + MT("setextH1", + "[header&header-1 foo]", + "[header&header-1 =]"); + + // Check if 3+ ='s work + MT("setextH1", + "[header&header-1 foo]", + "[header&header-1 ===]"); + + // Check if single underlining - works + MT("setextH2", + "[header&header-2 foo]", + "[header&header-2 -]"); + + // Check if 3+ -'s work + MT("setextH2", + "[header&header-2 foo]", + "[header&header-2 ---]"); + + // http://spec.commonmark.org/0.19/#example-45 + MT("setextH2AllowSpaces", + "[header&header-2 foo]", + " [header&header-2 ---- ]"); + + // http://spec.commonmark.org/0.19/#example-44 + MT("noSetextAfterIndentedCodeBlock", + " [comment foo]", + "[hr ---]"); + + MT("setextAfterFencedCode", + "[comment ```]", + "[comment foo]", + "[comment ```]", + "[header&header-2 bar]", + "[header&header-2 ---]"); + + MT("setextAferATX", + "[header&header-1 # foo]", + "[header&header-2 bar]", + "[header&header-2 ---]"); + + // http://spec.commonmark.org/0.19/#example-51 + MT("noSetextAfterQuote", + "[quote&quote-1 > foo]", + "[hr ---]", + "", + "[quote&quote-1 > foo]", + "[quote&quote-1 bar]", + "[hr ---]"); + + MT("noSetextAfterList", + "[variable-2 - foo]", + "[hr ---]"); + + MT("noSetextAfterList_listContinuation", + "[variable-2 - foo]", + "bar", + "[hr ---]"); + + MT("setextAfterList_afterIndentedCode", + "[variable-2 - foo]", + "", + " [comment bar]", + "[header&header-2 baz]", + "[header&header-2 ---]"); + + MT("setextAfterList_afterFencedCodeBlocks", + "[variable-2 - foo]", + "", + " [comment ```]", + " [comment bar]", + " [comment ```]", + "[header&header-2 baz]", + "[header&header-2 ---]"); + + MT("setextAfterList_afterHeader", + "[variable-2 - foo]", + " [variable-2&header&header-1 # bar]", + "[header&header-2 baz]", + "[header&header-2 ---]"); + + MT("setextAfterList_afterHr", + "[variable-2 - foo]", + "", + " [hr ---]", + "[header&header-2 bar]", + "[header&header-2 ---]"); + + MT("setext_nestedInlineMarkup", + "[header&header-1 foo ][em&header&header-1 *bar*]", + "[header&header-1 =]"); + + MT("setext_linkDef", + "[link [[aaa]]:] [string&url http://google.com 'title']", + "[hr ---]"); + + // currently, looks max one line ahead, thus won't catch valid CommonMark + // markup + MT("setext_oneLineLookahead", + "foo", + "[header&header-1 bar]", + "[header&header-1 =]"); + + // ensure we don't regard space after dash as a list + MT("setext_emptyList", + "[header&header-2 foo]", + "[header&header-2 - ]", + "foo"); + + // Single-line blockquote with trailing space + MT("blockquoteSpace", + "[quote&quote-1 > foo]"); + + // Single-line blockquote + MT("blockquoteNoSpace", + "[quote&quote-1 >foo]"); + + // No blank line before blockquote + MT("blockquoteNoBlankLine", + "foo", + "[quote&quote-1 > bar]"); + + MT("blockquoteNested", + "[quote&quote-1 > foo]", + "[quote&quote-1 >][quote&quote-2 > foo]", + "[quote&quote-1 >][quote&quote-2 >][quote&quote-3 > foo]"); + + // ensure quote-level is inferred correctly even if indented + MT("blockquoteNestedIndented", + " [quote&quote-1 > foo]", + " [quote&quote-1 >][quote&quote-2 > foo]", + " [quote&quote-1 >][quote&quote-2 >][quote&quote-3 > foo]"); + + // ensure quote-level is inferred correctly even if indented + MT("blockquoteIndentedTooMuch", + "foo", + " > bar"); + + // Single-line blockquote followed by normal paragraph + MT("blockquoteThenParagraph", + "[quote&quote-1 >foo]", + "", + "bar"); + + // Multi-line blockquote (lazy mode) + MT("multiBlockquoteLazy", + "[quote&quote-1 >foo]", + "[quote&quote-1 bar]"); + + // Multi-line blockquote followed by normal paragraph (lazy mode) + MT("multiBlockquoteLazyThenParagraph", + "[quote&quote-1 >foo]", + "[quote&quote-1 bar]", + "", + "hello"); + + // Multi-line blockquote (non-lazy mode) + MT("multiBlockquote", + "[quote&quote-1 >foo]", + "[quote&quote-1 >bar]"); + + // Multi-line blockquote followed by normal paragraph (non-lazy mode) + MT("multiBlockquoteThenParagraph", + "[quote&quote-1 >foo]", + "[quote&quote-1 >bar]", + "", + "hello"); + + // disallow lists inside blockquote for now because it causes problems outside blockquote + // TODO: fix to be CommonMark-compliant + MT("listNestedInBlockquote", + "[quote&quote-1 > - foo]"); + + // disallow fenced blocks inside blockquote because it causes problems outside blockquote + // TODO: fix to be CommonMark-compliant + MT("fencedBlockNestedInBlockquote", + "[quote&quote-1 > ```]", + "[quote&quote-1 > code]", + "[quote&quote-1 > ```]", + // ensure we still allow inline code + "[quote&quote-1 > ][quote&quote-1&comment `code`]"); + + // Header with leading space after continued blockquote (#3287, negative indentation) + MT("headerAfterContinuedBlockquote", + "[quote&quote-1 > foo]", + "[quote&quote-1 bar]", + "", + " [header&header-1 # hello]"); + + // Check list types + + MT("listAsterisk", + "foo", + "bar", + "", + "[variable-2 * foo]", + "[variable-2 * bar]"); + + MT("listPlus", + "foo", + "bar", + "", + "[variable-2 + foo]", + "[variable-2 + bar]"); + + MT("listDash", + "foo", + "bar", + "", + "[variable-2 - foo]", + "[variable-2 - bar]"); + + MT("listNumber", + "foo", + "bar", + "", + "[variable-2 1. foo]", + "[variable-2 2. bar]"); + + MT("listFromParagraph", + "foo", + "[variable-2 1. bar]", + "[variable-2 2. hello]"); + + // List after hr + MT("listAfterHr", + "[hr ---]", + "[variable-2 - bar]"); + + // List after header + MT("listAfterHeader", + "[header&header-1 # foo]", + "[variable-2 - bar]"); + + // hr after list + MT("hrAfterList", + "[variable-2 - foo]", + "[hr -----]"); + + MT("hrAfterFencedCode", + "[comment ```]", + "[comment code]", + "[comment ```]", + "[hr ---]"); + + // allow hr inside lists + // (require prev line to be empty or hr, TODO: non-CommonMark-compliant) + MT("hrInsideList", + "[variable-2 - foo]", + "", + " [hr ---]", + " [hr ---]", + "", + " [comment ---]"); + + MT("consecutiveHr", + "[hr ---]", + "[hr ---]", + "[hr ---]"); + + // Formatting in lists (*) + MT("listAsteriskFormatting", + "[variable-2 * ][variable-2&em *foo*][variable-2 bar]", + "[variable-2 * ][variable-2&strong **foo**][variable-2 bar]", + "[variable-2 * ][variable-2&em&strong ***foo***][variable-2 bar]", + "[variable-2 * ][variable-2&comment `foo`][variable-2 bar]"); + + // Formatting in lists (+) + MT("listPlusFormatting", + "[variable-2 + ][variable-2&em *foo*][variable-2 bar]", + "[variable-2 + ][variable-2&strong **foo**][variable-2 bar]", + "[variable-2 + ][variable-2&em&strong ***foo***][variable-2 bar]", + "[variable-2 + ][variable-2&comment `foo`][variable-2 bar]"); + + // Formatting in lists (-) + MT("listDashFormatting", + "[variable-2 - ][variable-2&em *foo*][variable-2 bar]", + "[variable-2 - ][variable-2&strong **foo**][variable-2 bar]", + "[variable-2 - ][variable-2&em&strong ***foo***][variable-2 bar]", + "[variable-2 - ][variable-2&comment `foo`][variable-2 bar]"); + + // Formatting in lists (1.) + MT("listNumberFormatting", + "[variable-2 1. ][variable-2&em *foo*][variable-2 bar]", + "[variable-2 2. ][variable-2&strong **foo**][variable-2 bar]", + "[variable-2 3. ][variable-2&em&strong ***foo***][variable-2 bar]", + "[variable-2 4. ][variable-2&comment `foo`][variable-2 bar]"); + + // Paragraph lists + MT("listParagraph", + "[variable-2 * foo]", + "", + "[variable-2 * bar]"); + + // Multi-paragraph lists + // + // 4 spaces + MT("listMultiParagraph", + "[variable-2 * foo]", + "", + "[variable-2 * bar]", + "", + " [variable-2 hello]"); + + // 4 spaces, extra blank lines (should still be list, per Dingus) + MT("listMultiParagraphExtra", + "[variable-2 * foo]", + "", + "[variable-2 * bar]", + "", + "", + " [variable-2 hello]"); + + // 4 spaces, plus 1 space (should still be list, per Dingus) + MT("listMultiParagraphExtraSpace", + "[variable-2 * foo]", + "", + "[variable-2 * bar]", + "", + " [variable-2 hello]", + "", + " [variable-2 world]"); + + // 1 tab + MT("listTab", + "[variable-2 * foo]", + "", + "[variable-2 * bar]", + "", + "\t[variable-2 hello]"); + + // No indent + MT("listNoIndent", + "[variable-2 * foo]", + "", + "[variable-2 * bar]", + "", + "hello"); + + MT("listCommonMarkIndentationCode", + "[variable-2 * Code blocks also affect]", + " [variable-3 * The next level starts where the contents start.]", + " [variable-3 * Anything less than that will keep the item on the same level.]", + " [variable-3 * Each list item can indent the first level further and further.]", + " [variable-3 * For the most part, this makes sense while writing a list.]", + " [keyword * This means two items with same indentation can be different levels.]", + " [keyword * Each level has an indent requirement that can change between items.]", + " [keyword * A list item that meets this will be part of the next level.]", + " [variable-3 * Otherwise, it will be part of the level where it does meet this.]", + " [variable-2 * World]"); + + // should handle nested and un-nested lists + MT("listCommonMark_MixedIndents", + "[variable-2 * list1]", + " [variable-2 list1]", + " [variable-2&header&header-1 # heading still part of list1]", + " [variable-2 text after heading still part of list1]", + "", + " [comment indented codeblock]", + " [variable-2 list1 after code block]", + " [variable-3 * list2]", + // amount of spaces on empty lines between lists doesn't matter + " ", + // extra empty lines irrelevant + "", + "", + " [variable-3 indented text part of list2]", + " [keyword * list3]", + "", + " [variable-3 text at level of list2]", + "", + " [variable-2 de-indented text part of list1 again]", + "", + " [variable-2&comment ```]", + " [comment code]", + " [variable-2&comment ```]", + "", + " [variable-2 text after fenced code]"); + + // should correctly parse numbered list content indentation + MT("listCommonMark_NumeberedListIndent", + "[variable-2 1000. list with base indent of 6]", + "", + " [variable-2 text must be indented 6 spaces at minimum]", + "", + " [variable-2 9-spaces indented text still part of list]", + "", + " [comment indented codeblock starts at 10 spaces]", + "", + " [comment text indented by 5 spaces no longer belong to list]"); + + // should consider tab as 4 spaces + MT("listCommonMark_TabIndented", + "[variable-2 * list]", + "\t[variable-3 * list2]", + "", + "\t\t[variable-3 part of list2]"); + + MT("listAfterBlockquote", + "[quote&quote-1 > foo]", + "[variable-2 - bar]"); + + // shouldn't create sublist if it's indented more than allowed + MT("nestedListIndentedTooMuch", + "[variable-2 - foo]", + " [variable-2 - bar]"); + + MT("listIndentedTooMuchAfterParagraph", + "foo", + " - bar"); + + // Blockquote + MT("blockquote", + "[variable-2 * foo]", + "", + "[variable-2 * bar]", + "", + " [variable-2&quote&quote-1 > hello]"); + + // Code block + MT("blockquoteCode", + "[variable-2 * foo]", + "", + "[variable-2 * bar]", + "", + " [comment > hello]", + "", + " [variable-2 world]"); + + // Code block followed by text + MT("blockquoteCodeText", + "[variable-2 * foo]", + "", + " [variable-2 bar]", + "", + " [comment hello]", + "", + " [variable-2 world]"); + + // Nested list + + MT("listAsteriskNested", + "[variable-2 * foo]", + "", + " [variable-3 * bar]"); + + MT("listPlusNested", + "[variable-2 + foo]", + "", + " [variable-3 + bar]"); + + MT("listDashNested", + "[variable-2 - foo]", + "", + " [variable-3 - bar]"); + + MT("listNumberNested", + "[variable-2 1. foo]", + "", + " [variable-3 2. bar]"); + + MT("listMixed", + "[variable-2 * foo]", + "", + " [variable-3 + bar]", + "", + " [keyword - hello]", + "", + " [variable-2 1. world]"); + + MT("listBlockquote", + "[variable-2 * foo]", + "", + " [variable-3 + bar]", + "", + " [quote&quote-1&variable-3 > hello]"); + + MT("listCode", + "[variable-2 * foo]", + "", + " [variable-3 + bar]", + "", + " [comment hello]"); + + // Code with internal indentation + MT("listCodeIndentation", + "[variable-2 * foo]", + "", + " [comment bar]", + " [comment hello]", + " [comment world]", + " [comment foo]", + " [variable-2 bar]"); + + // List nesting edge cases + MT("listNested", + "[variable-2 * foo]", + "", + " [variable-3 * bar]", + "", + " [variable-3 hello]" + ); + MT("listNested", + "[variable-2 * foo]", + "", + " [variable-3 * bar]", + "", + " [keyword * foo]" + ); + + // Code followed by text + MT("listCodeText", + "[variable-2 * foo]", + "", + " [comment bar]", + "", + "hello"); + + // Following tests directly from official Markdown documentation + // http://daringfireball.net/projects/markdown/syntax#hr + + MT("hrSpace", + "[hr * * *]"); + + MT("hr", + "[hr ***]"); + + MT("hrLong", + "[hr *****]"); + + MT("hrSpaceDash", + "[hr - - -]"); + + MT("hrDashLong", + "[hr ---------------------------------------]"); + + //Images + MT("Images", + "[image&image-marker !][image&image-alt-text&link [[alt text]]][string&url (http://link.to/image.jpg)]") + + //Images with highlight alt text + MT("imageEm", + "[image&image-marker !][image&image-alt-text&link [[][image-alt-text&em&image&link *alt text*][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)]"); + + MT("imageStrong", + "[image&image-marker !][image&image-alt-text&link [[][image-alt-text&strong&image&link **alt text**][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)]"); + + MT("imageEmStrong", + "[image&image-marker !][image&image-alt-text&link [[][image&image-alt-text&em&strong&link ***alt text***][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)]"); + + // Inline link with title + MT("linkTitle", + "[link [[foo]]][string&url (http://example.com/ \"bar\")] hello"); + + // Inline link without title + MT("linkNoTitle", + "[link [[foo]]][string&url (http://example.com/)] bar"); + + // Inline link with image + MT("linkImage", + "[link [[][link&image&image-marker !][link&image&image-alt-text&link [[alt text]]][string&url (http://link.to/image.jpg)][link ]]][string&url (http://example.com/)] bar"); + + // Inline link with Em + MT("linkEm", + "[link [[][link&em *foo*][link ]]][string&url (http://example.com/)] bar"); + + // Inline link with Strong + MT("linkStrong", + "[link [[][link&strong **foo**][link ]]][string&url (http://example.com/)] bar"); + + // Inline link with EmStrong + MT("linkEmStrong", + "[link [[][link&em&strong ***foo***][link ]]][string&url (http://example.com/)] bar"); + + MT("multilineLink", + "[link [[foo]", + "[link bar]]][string&url (https://foo#_a)]", + "should not be italics") + + // Image with title + MT("imageTitle", + "[image&image-marker !][image&image-alt-text&link [[alt text]]][string&url (http://example.com/ \"bar\")] hello"); + + // Image without title + MT("imageNoTitle", + "[image&image-marker !][image&image-alt-text&link [[alt text]]][string&url (http://example.com/)] bar"); + + // Image with asterisks + MT("imageAsterisks", + "[image&image-marker !][image&image-alt-text&link [[ ][image&image-alt-text&em&link *alt text*][image&image-alt-text&link ]]][string&url (http://link.to/image.jpg)] bar"); + + // Not a link. Should be normal text due to square brackets being used + // regularly in text, especially in quoted material, and no space is allowed + // between square brackets and parentheses (per Dingus). + MT("notALink", + "[link [[foo]]] (bar)"); + + // Reference-style links + MT("linkReference", + "[link [[foo]]][string&url [[bar]]] hello"); + + // Reference-style links with Em + MT("linkReferenceEm", + "[link [[][link&em *foo*][link ]]][string&url [[bar]]] hello"); + + // Reference-style links with Strong + MT("linkReferenceStrong", + "[link [[][link&strong **foo**][link ]]][string&url [[bar]]] hello"); + + // Reference-style links with EmStrong + MT("linkReferenceEmStrong", + "[link [[][link&em&strong ***foo***][link ]]][string&url [[bar]]] hello"); + + // Reference-style links with optional space separator (per documentation) + // "You can optionally use a space to separate the sets of brackets" + MT("linkReferenceSpace", + "[link [[foo]]] [string&url [[bar]]] hello"); + + // Should only allow a single space ("...use *a* space...") + MT("linkReferenceDoubleSpace", + "[link [[foo]]] [link [[bar]]] hello"); + + // Reference-style links with implicit link name + MT("linkImplicit", + "[link [[foo]]][string&url [[]]] hello"); + + // @todo It would be nice if, at some point, the document was actually + // checked to see if the referenced link exists + + // Link label, for reference-style links (taken from documentation) + + MT("labelNoTitle", + "[link [[foo]]:] [string&url http://example.com/]"); + + MT("labelIndented", + " [link [[foo]]:] [string&url http://example.com/]"); + + MT("labelSpaceTitle", + "[link [[foo bar]]:] [string&url http://example.com/ \"hello\"]"); + + MT("labelDoubleTitle", + "[link [[foo bar]]:] [string&url http://example.com/ \"hello\"] \"world\""); + + MT("labelTitleDoubleQuotes", + "[link [[foo]]:] [string&url http://example.com/ \"bar\"]"); + + MT("labelTitleSingleQuotes", + "[link [[foo]]:] [string&url http://example.com/ 'bar']"); + + MT("labelTitleParentheses", + "[link [[foo]]:] [string&url http://example.com/ (bar)]"); + + MT("labelTitleInvalid", + "[link [[foo]]:] [string&url http://example.com/] bar"); + + MT("labelLinkAngleBrackets", + "[link [[foo]]:] [string&url <http://example.com/> \"bar\"]"); + + MT("labelTitleNextDoubleQuotes", + "[link [[foo]]:] [string&url http://example.com/]", + "[string \"bar\"] hello"); + + MT("labelTitleNextSingleQuotes", + "[link [[foo]]:] [string&url http://example.com/]", + "[string 'bar'] hello"); + + MT("labelTitleNextParentheses", + "[link [[foo]]:] [string&url http://example.com/]", + "[string (bar)] hello"); + + MT("labelTitleNextMixed", + "[link [[foo]]:] [string&url http://example.com/]", + "(bar\" hello"); + + MT("labelEscape", + "[link [[foo \\]] ]]:] [string&url http://example.com/]"); + + MT("labelEscapeColon", + "[link [[foo \\]]: bar]]:] [string&url http://example.com/]"); + + MT("labelEscapeEnd", + "\\[[foo\\]]: http://example.com/"); + + MT("linkWeb", + "[link <http://example.com/>] foo"); + + MT("linkWebDouble", + "[link <http://example.com/>] foo [link <http://example.com/>]"); + + MT("linkEmail", + "[link <user@example.com>] foo"); + + MT("linkEmailDouble", + "[link <user@example.com>] foo [link <user@example.com>]"); + + MT("emAsterisk", + "[em *foo*] bar"); + + MT("emUnderscore", + "[em _foo_] bar"); + + MT("emInWordAsterisk", + "foo[em *bar*]hello"); + + MT("emInWordUnderscore", + "foo_bar_hello"); + + // Per documentation: "...surround an * or _ with spaces, it’ll be + // treated as a literal asterisk or underscore." + + MT("emEscapedBySpaceIn", + "foo [em _bar _ hello_] world"); + + MT("emEscapedBySpaceOut", + "foo _ bar [em _hello_] world"); + + MT("emEscapedByNewline", + "foo", + "_ bar [em _hello_] world"); + + // Unclosed emphasis characters + // Instead of simply marking as EM / STRONG, it would be nice to have an + // incomplete flag for EM and STRONG, that is styled slightly different. + MT("emIncompleteAsterisk", + "foo [em *bar]"); + + MT("emIncompleteUnderscore", + "foo [em _bar]"); + + MT("strongAsterisk", + "[strong **foo**] bar"); + + MT("strongUnderscore", + "[strong __foo__] bar"); + + MT("emStrongAsterisk", + "[em *foo][em&strong **bar*][strong hello**] world"); + + MT("emStrongUnderscore", + "[em _foo ][em&strong __bar_][strong hello__] world"); + + // "...same character must be used to open and close an emphasis span."" + MT("emStrongMixed", + "[em _foo][em&strong **bar*hello__ world]"); + + MT("emStrongMixed", + "[em *foo ][em&strong __bar_hello** world]"); + + MT("linkWithNestedParens", + "[link [[foo]]][string&url (bar(baz))]") + + // These characters should be escaped: + // \ backslash + // ` backtick + // * asterisk + // _ underscore + // {} curly braces + // [] square brackets + // () parentheses + // # hash mark + // + plus sign + // - minus sign (hyphen) + // . dot + // ! exclamation mark + + MT("escapeBacktick", + "foo \\`bar\\`"); + + MT("doubleEscapeBacktick", + "foo \\\\[comment `bar\\\\`]"); + + MT("escapeAsterisk", + "foo \\*bar\\*"); + + MT("doubleEscapeAsterisk", + "foo \\\\[em *bar\\\\*]"); + + MT("escapeUnderscore", + "foo \\_bar\\_"); + + MT("doubleEscapeUnderscore", + "foo \\\\[em _bar\\\\_]"); + + MT("escapeHash", + "\\# foo"); + + MT("doubleEscapeHash", + "\\\\# foo"); + + MT("escapeNewline", + "\\", + "[em *foo*]"); + + // Class override tests + TokenTypeOverrideTest("overrideHeader1", + "[override-header&override-header-1 # Foo]"); + + TokenTypeOverrideTest("overrideHeader2", + "[override-header&override-header-2 ## Foo]"); + + TokenTypeOverrideTest("overrideHeader3", + "[override-header&override-header-3 ### Foo]"); + + TokenTypeOverrideTest("overrideHeader4", + "[override-header&override-header-4 #### Foo]"); + + TokenTypeOverrideTest("overrideHeader5", + "[override-header&override-header-5 ##### Foo]"); + + TokenTypeOverrideTest("overrideHeader6", + "[override-header&override-header-6 ###### Foo]"); + + TokenTypeOverrideTest("overrideCode", + "[override-code `foo`]"); + + TokenTypeOverrideTest("overrideCodeBlock", + "[override-code ```]", + "[override-code foo]", + "[override-code ```]"); + + TokenTypeOverrideTest("overrideQuote", + "[override-quote&override-quote-1 > foo]", + "[override-quote&override-quote-1 > bar]"); + + TokenTypeOverrideTest("overrideQuoteNested", + "[override-quote&override-quote-1 > foo]", + "[override-quote&override-quote-1 >][override-quote&override-quote-2 > bar]", + "[override-quote&override-quote-1 >][override-quote&override-quote-2 >][override-quote&override-quote-3 > baz]"); + + TokenTypeOverrideTest("overrideLists", + "[override-list1 - foo]", + "", + " [override-list2 + bar]", + "", + " [override-list3 * baz]", + "", + " [override-list1 1. qux]", + "", + " [override-list2 - quux]"); + + TokenTypeOverrideTest("overrideHr", + "[override-hr * * *]"); + + TokenTypeOverrideTest("overrideImage", + "[override-image&override-image-marker !][override-image&override-image-alt-text&link [[alt text]]][override-link-href&url (http://link.to/image.jpg)]"); + + TokenTypeOverrideTest("overrideLinkText", + "[override-link-text [[foo]]][override-link-href&url (http://example.com)]"); + + TokenTypeOverrideTest("overrideLinkEmailAndInline", + "[override-link-email <][override-link-inline foo@example.com>]"); + + TokenTypeOverrideTest("overrideEm", + "[override-em *foo*]"); + + TokenTypeOverrideTest("overrideStrong", + "[override-strong **foo**]"); + + TokenTypeOverrideTest("overrideStrikethrough", + "[override-strikethrough ~~foo~~]"); + + TokenTypeOverrideTest("overrideEmoji", + "[override-emoji :foo:]"); + + FormatTokenTypeOverrideTest("overrideFormatting", + "[override-formatting-escape \\*]"); + + // Tests to make sure GFM-specific things aren't getting through + + MT("taskList", + "[variable-2 * ][link&variable-2 [[ ]]][variable-2 bar]"); + + MT("fencedCodeBlocks", + "[comment ```]", + "[comment foo]", + "", + "[comment bar]", + "[comment ```]", + "baz"); + + MT("fencedCodeBlocks_invalidClosingFence_trailingText", + "[comment ```]", + "[comment foo]", + "[comment ``` must not have trailing text]", + "[comment baz]"); + + MT("fencedCodeBlocks_invalidClosingFence_trailingTabs", + "[comment ```]", + "[comment foo]", + "[comment ```\t]", + "[comment baz]"); + + MT("fencedCodeBlocks_validClosingFence", + "[comment ```]", + "[comment foo]", + // may have trailing spaces + "[comment ``` ]", + "baz"); + + MT("fencedCodeBlocksInList_closingFenceIndented", + "[variable-2 - list]", + " [variable-2&comment ```]", + " [comment foo]", + " [variable-2&comment ```]", + " [variable-2 baz]"); + + MT("fencedCodeBlocksInList_closingFenceIndentedTooMuch", + "[variable-2 - list]", + " [variable-2&comment ```]", + " [comment foo]", + " [comment ```]", + " [comment baz]"); + + MT("fencedCodeBlockModeSwitching", + "[comment ```javascript]", + "[variable foo]", + "", + "[comment ```]", + "bar"); + + MT_noFencedHighlight("fencedCodeBlock_noHighlight", + "[comment ```javascript]", + "[comment foo]", + "[comment ```]"); + + MT("fencedCodeBlockModeSwitchingObjc", + "[comment ```objective-c]", + "[keyword @property] [variable NSString] [operator *] [variable foo];", + "[comment ```]", + "bar"); + + MT("fencedCodeBlocksMultipleChars", + "[comment `````]", + "[comment foo]", + "[comment ```]", + "[comment foo]", + "[comment `````]", + "bar"); + + MT("fencedCodeBlocksTildes", + "[comment ~~~]", + "[comment foo]", + "[comment ~~~]", + "bar"); + + MT("fencedCodeBlocksTildesMultipleChars", + "[comment ~~~~~]", + "[comment ~~~]", + "[comment foo]", + "[comment ~~~~~]", + "bar"); + + MT("fencedCodeBlocksMultipleChars", + "[comment `````]", + "[comment foo]", + "[comment ```]", + "[comment foo]", + "[comment `````]", + "bar"); + + MT("fencedCodeBlocksMixed", + "[comment ~~~]", + "[comment ```]", + "[comment foo]", + "[comment ~~~]", + "bar"); + + MT("fencedCodeBlocksAfterBlockquote", + "[quote&quote-1 > foo]", + "[comment ```]", + "[comment bar]", + "[comment ```]"); + + // fencedCode indented too much should act as simple indentedCode + // (hence has no highlight formatting) + FT("tooMuchIndentedFencedCode", + " [comment ```]", + " [comment code]", + " [comment ```]"); + + MT("autoTerminateFencedCodeWhenLeavingList", + "[variable-2 - list1]", + " [variable-3 - list2]", + " [variable-3&comment ```]", + " [comment code]", + " [variable-3 - list2]", + " [variable-2&comment ```]", + " [comment code]", + "[quote&quote-1 > foo]"); + + // Tests that require XML mode + + MT("xmlMode", + "[tag&bracket <][tag div][tag&bracket >]", + " *foo*", + " [tag&bracket <][tag http://github.com][tag&bracket />]", + "[tag&bracket </][tag div][tag&bracket >]", + "[link <http://github.com/>]"); + + MT("xmlModeWithMarkdownInside", + "[tag&bracket <][tag div] [attribute markdown]=[string 1][tag&bracket >]", + "[em *foo*]", + "[link <http://github.com/>]", + "[tag </div>]", + "[link <http://github.com/>]", + "[tag&bracket <][tag div][tag&bracket >]", + "[tag&bracket </][tag div][tag&bracket >]"); + + MT_noXml("xmlHighlightDisabled", + "<div>foo</div>"); + +})(); diff --git a/modules/editor/codemirror/mode/meta.min.js b/modules/editor/codemirror/mode/meta.min.js @@ -0,0 +1,216 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.modeInfo = [ + {name: "APL", mime: "text/apl", mode: "apl", ext: ["dyalog", "apl"]}, + {name: "PGP", mimes: ["application/pgp", "application/pgp-encrypted", "application/pgp-keys", "application/pgp-signature"], mode: "asciiarmor", ext: ["asc", "pgp", "sig"]}, + {name: "ASN.1", mime: "text/x-ttcn-asn", mode: "asn.1", ext: ["asn", "asn1"]}, + {name: "Asterisk", mime: "text/x-asterisk", mode: "asterisk", file: /^extensions\.conf$/i}, + {name: "Brainfuck", mime: "text/x-brainfuck", mode: "brainfuck", ext: ["b", "bf"]}, + {name: "C", mime: "text/x-csrc", mode: "clike", ext: ["c", "h"]}, + {name: "C++", mime: "text/x-c++src", mode: "clike", ext: ["cpp", "c++", "cc", "cxx", "hpp", "h++", "hh", "hxx"], alias: ["cpp"]}, + {name: "Cobol", mime: "text/x-cobol", mode: "cobol", ext: ["cob", "cpy"]}, + {name: "C#", mime: "text/x-csharp", mode: "clike", ext: ["cs"], alias: ["csharp"]}, + {name: "Clojure", mime: "text/x-clojure", mode: "clojure", ext: ["clj", "cljc", "cljx"]}, + {name: "ClojureScript", mime: "text/x-clojurescript", mode: "clojure", ext: ["cljs"]}, + {name: "Closure Stylesheets (GSS)", mime: "text/x-gss", mode: "css", ext: ["gss"]}, + {name: "CMake", mime: "text/x-cmake", mode: "cmake", ext: ["cmake", "cmake.in"], file: /^CMakeLists.txt$/}, + {name: "CoffeeScript", mimes: ["application/vnd.coffeescript", "text/coffeescript", "text/x-coffeescript"], mode: "coffeescript", ext: ["coffee"], alias: ["coffee", "coffee-script"]}, + {name: "Common Lisp", mime: "text/x-common-lisp", mode: "commonlisp", ext: ["cl", "lisp", "el"], alias: ["lisp"]}, + {name: "Cypher", mime: "application/x-cypher-query", mode: "cypher", ext: ["cyp", "cypher"]}, + {name: "Cython", mime: "text/x-cython", mode: "python", ext: ["pyx", "pxd", "pxi"]}, + {name: "Crystal", mime: "text/x-crystal", mode: "crystal", ext: ["cr"]}, + {name: "CSS", mime: "text/css", mode: "css", ext: ["css"]}, + {name: "CQL", mime: "text/x-cassandra", mode: "sql", ext: ["cql"]}, + {name: "D", mime: "text/x-d", mode: "d", ext: ["d"]}, + {name: "Dart", mimes: ["application/dart", "text/x-dart"], mode: "dart", ext: ["dart"]}, + {name: "diff", mime: "text/x-diff", mode: "diff", ext: ["diff", "patch"]}, + {name: "Django", mime: "text/x-django", mode: "django"}, + {name: "Dockerfile", mime: "text/x-dockerfile", mode: "dockerfile", file: /^Dockerfile$/}, + {name: "DTD", mime: "application/xml-dtd", mode: "dtd", ext: ["dtd"]}, + {name: "Dylan", mime: "text/x-dylan", mode: "dylan", ext: ["dylan", "dyl", "intr"]}, + {name: "EBNF", mime: "text/x-ebnf", mode: "ebnf"}, + {name: "ECL", mime: "text/x-ecl", mode: "ecl", ext: ["ecl"]}, + {name: "edn", mime: "application/edn", mode: "clojure", ext: ["edn"]}, + {name: "Eiffel", mime: "text/x-eiffel", mode: "eiffel", ext: ["e"]}, + {name: "Elm", mime: "text/x-elm", mode: "elm", ext: ["elm"]}, + {name: "Embedded Javascript", mime: "application/x-ejs", mode: "htmlembedded", ext: ["ejs"]}, + {name: "Embedded Ruby", mime: "application/x-erb", mode: "htmlembedded", ext: ["erb"]}, + {name: "Erlang", mime: "text/x-erlang", mode: "erlang", ext: ["erl"]}, + {name: "Esper", mime: "text/x-esper", mode: "sql"}, + {name: "Factor", mime: "text/x-factor", mode: "factor", ext: ["factor"]}, + {name: "FCL", mime: "text/x-fcl", mode: "fcl"}, + {name: "Forth", mime: "text/x-forth", mode: "forth", ext: ["forth", "fth", "4th"]}, + {name: "Fortran", mime: "text/x-fortran", mode: "fortran", ext: ["f", "for", "f77", "f90"]}, + {name: "F#", mime: "text/x-fsharp", mode: "mllike", ext: ["fs"], alias: ["fsharp"]}, + {name: "Gas", mime: "text/x-gas", mode: "gas", ext: ["s"]}, + {name: "Gherkin", mime: "text/x-feature", mode: "gherkin", ext: ["feature"]}, + {name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm", file: /^(readme|contributing|history).md$/i}, + {name: "Go", mime: "text/x-go", mode: "go", ext: ["go"]}, + {name: "Groovy", mime: "text/x-groovy", mode: "groovy", ext: ["groovy", "gradle"], file: /^Jenkinsfile$/}, + {name: "HAML", mime: "text/x-haml", mode: "haml", ext: ["haml"]}, + {name: "Haskell", mime: "text/x-haskell", mode: "haskell", ext: ["hs"]}, + {name: "Haskell (Literate)", mime: "text/x-literate-haskell", mode: "haskell-literate", ext: ["lhs"]}, + {name: "Haxe", mime: "text/x-haxe", mode: "haxe", ext: ["hx"]}, + {name: "HXML", mime: "text/x-hxml", mode: "haxe", ext: ["hxml"]}, + {name: "ASP.NET", mime: "application/x-aspx", mode: "htmlembedded", ext: ["aspx"], alias: ["asp", "aspx"]}, + {name: "HTML", mime: "text/html", mode: "htmlmixed", ext: ["html", "htm"], alias: ["xhtml"]}, + {name: "HTTP", mime: "message/http", mode: "http"}, + {name: "IDL", mime: "text/x-idl", mode: "idl", ext: ["pro"]}, + {name: "Pug", mime: "text/x-pug", mode: "pug", ext: ["jade", "pug"], alias: ["jade"]}, + {name: "Java", mime: "text/x-java", mode: "clike", ext: ["java"]}, + {name: "Java Server Pages", mime: "application/x-jsp", mode: "htmlembedded", ext: ["jsp"], alias: ["jsp"]}, + {name: "JavaScript", mimes: ["text/javascript", "text/ecmascript", "application/javascript", "application/x-javascript", "application/ecmascript"], + mode: "javascript", ext: ["js"], alias: ["ecmascript", "js", "node"]}, + {name: "JSON", mimes: ["application/json", "application/x-json"], mode: "javascript", ext: ["json", "map"], alias: ["json5"]}, + {name: "JSON-LD", mime: "application/ld+json", mode: "javascript", ext: ["jsonld"], alias: ["jsonld"]}, + {name: "JSX", mime: "text/jsx", mode: "jsx", ext: ["jsx"]}, + {name: "Jinja2", mime: "null", mode: "jinja2"}, + {name: "Julia", mime: "text/x-julia", mode: "julia", ext: ["jl"]}, + {name: "Kotlin", mime: "text/x-kotlin", mode: "clike", ext: ["kt"]}, + {name: "LESS", mime: "text/x-less", mode: "css", ext: ["less"]}, + {name: "LiveScript", mime: "text/x-livescript", mode: "livescript", ext: ["ls"], alias: ["ls"]}, + {name: "Lua", mime: "text/x-lua", mode: "lua", ext: ["lua"]}, + {name: "Markdown", mime: "text/x-markdown", mode: "markdown", ext: ["markdown", "md", "mkd"]}, + {name: "mIRC", mime: "text/mirc", mode: "mirc"}, + {name: "MariaDB SQL", mime: "text/x-mariadb", mode: "sql"}, + {name: "Mathematica", mime: "text/x-mathematica", mode: "mathematica", ext: ["m", "nb"]}, + {name: "Modelica", mime: "text/x-modelica", mode: "modelica", ext: ["mo"]}, + {name: "MUMPS", mime: "text/x-mumps", mode: "mumps", ext: ["mps"]}, + {name: "MS SQL", mime: "text/x-mssql", mode: "sql"}, + {name: "mbox", mime: "application/mbox", mode: "mbox", ext: ["mbox"]}, + {name: "MySQL", mime: "text/x-mysql", mode: "sql"}, + {name: "Nginx", mime: "text/x-nginx-conf", mode: "nginx", file: /nginx.*\.conf$/i}, + {name: "NSIS", mime: "text/x-nsis", mode: "nsis", ext: ["nsh", "nsi"]}, + {name: "NTriples", mimes: ["application/n-triples", "application/n-quads", "text/n-triples"], + mode: "ntriples", ext: ["nt", "nq"]}, + {name: "Objective C", mime: "text/x-objectivec", mode: "clike", ext: ["m", "mm"], alias: ["objective-c", "objc"]}, + {name: "OCaml", mime: "text/x-ocaml", mode: "mllike", ext: ["ml", "mli", "mll", "mly"]}, + {name: "Octave", mime: "text/x-octave", mode: "octave", ext: ["m"]}, + {name: "Oz", mime: "text/x-oz", mode: "oz", ext: ["oz"]}, + {name: "Pascal", mime: "text/x-pascal", mode: "pascal", ext: ["p", "pas"]}, + {name: "PEG.js", mime: "null", mode: "pegjs", ext: ["jsonld"]}, + {name: "Perl", mime: "text/x-perl", mode: "perl", ext: ["pl", "pm"]}, + {name: "PHP", mime: ["application/x-httpd-php", "text/x-php"], mode: "php", ext: ["php", "php3", "php4", "php5", "php7", "phtml"]}, + {name: "Pig", mime: "text/x-pig", mode: "pig", ext: ["pig"]}, + {name: "Plain Text", mime: "text/plain", mode: "null", ext: ["txt", "text", "conf", "def", "list", "log"]}, + {name: "PLSQL", mime: "text/x-plsql", mode: "sql", ext: ["pls"]}, + {name: "PowerShell", mime: "application/x-powershell", mode: "powershell", ext: ["ps1", "psd1", "psm1"]}, + {name: "Properties files", mime: "text/x-properties", mode: "properties", ext: ["properties", "ini", "in"], alias: ["ini", "properties"]}, + {name: "ProtoBuf", mime: "text/x-protobuf", mode: "protobuf", ext: ["proto"]}, + {name: "Python", mime: "text/x-python", mode: "python", ext: ["BUILD", "bzl", "py", "pyw"], file: /^(BUCK|BUILD)$/}, + {name: "Puppet", mime: "text/x-puppet", mode: "puppet", ext: ["pp"]}, + {name: "Q", mime: "text/x-q", mode: "q", ext: ["q"]}, + {name: "R", mime: "text/x-rsrc", mode: "r", ext: ["r", "R"], alias: ["rscript"]}, + {name: "reStructuredText", mime: "text/x-rst", mode: "rst", ext: ["rst"], alias: ["rst"]}, + {name: "RPM Changes", mime: "text/x-rpm-changes", mode: "rpm"}, + {name: "RPM Spec", mime: "text/x-rpm-spec", mode: "rpm", ext: ["spec"]}, + {name: "Ruby", mime: "text/x-ruby", mode: "ruby", ext: ["rb"], alias: ["jruby", "macruby", "rake", "rb", "rbx"]}, + {name: "Rust", mime: "text/x-rustsrc", mode: "rust", ext: ["rs"]}, + {name: "SAS", mime: "text/x-sas", mode: "sas", ext: ["sas"]}, + {name: "Sass", mime: "text/x-sass", mode: "sass", ext: ["sass"]}, + {name: "Scala", mime: "text/x-scala", mode: "clike", ext: ["scala"]}, + {name: "Scheme", mime: "text/x-scheme", mode: "scheme", ext: ["scm", "ss"]}, + {name: "SCSS", mime: "text/x-scss", mode: "css", ext: ["scss"]}, + {name: "Shell", mimes: ["text/x-sh", "application/x-sh"], mode: "shell", ext: ["sh", "ksh", "bash"], alias: ["bash", "sh", "zsh"], file: /^PKGBUILD$/}, + {name: "Sieve", mime: "application/sieve", mode: "sieve", ext: ["siv", "sieve"]}, + {name: "Slim", mimes: ["text/x-slim", "application/x-slim"], mode: "slim", ext: ["slim"]}, + {name: "Smalltalk", mime: "text/x-stsrc", mode: "smalltalk", ext: ["st"]}, + {name: "Smarty", mime: "text/x-smarty", mode: "smarty", ext: ["tpl"]}, + {name: "Solr", mime: "text/x-solr", mode: "solr"}, + {name: "Soy", mime: "text/x-soy", mode: "soy", ext: ["soy"], alias: ["closure template"]}, + {name: "SPARQL", mime: "application/sparql-query", mode: "sparql", ext: ["rq", "sparql"], alias: ["sparul"]}, + {name: "Spreadsheet", mime: "text/x-spreadsheet", mode: "spreadsheet", alias: ["excel", "formula"]}, + {name: "SQL", mime: "text/x-sql", mode: "sql", ext: ["sql"]}, + {name: "SQLite", mime: "text/x-sqlite", mode: "sql"}, + {name: "Squirrel", mime: "text/x-squirrel", mode: "clike", ext: ["nut"]}, + {name: "Stylus", mime: "text/x-styl", mode: "stylus", ext: ["styl"]}, + {name: "Swift", mime: "text/x-swift", mode: "swift", ext: ["swift"]}, + {name: "sTeX", mime: "text/x-stex", mode: "stex"}, + {name: "LaTeX", mime: "text/x-latex", mode: "stex", ext: ["text", "ltx"], alias: ["tex"]}, + {name: "SystemVerilog", mime: "text/x-systemverilog", mode: "verilog", ext: ["v", "sv", "svh"]}, + {name: "Tcl", mime: "text/x-tcl", mode: "tcl", ext: ["tcl"]}, + {name: "Textile", mime: "text/x-textile", mode: "textile", ext: ["textile"]}, + {name: "TiddlyWiki ", mime: "text/x-tiddlywiki", mode: "tiddlywiki"}, + {name: "Tiki wiki", mime: "text/tiki", mode: "tiki"}, + {name: "TOML", mime: "text/x-toml", mode: "toml", ext: ["toml"]}, + {name: "Tornado", mime: "text/x-tornado", mode: "tornado"}, + {name: "troff", mime: "text/troff", mode: "troff", ext: ["1", "2", "3", "4", "5", "6", "7", "8", "9"]}, + {name: "TTCN", mime: "text/x-ttcn", mode: "ttcn", ext: ["ttcn", "ttcn3", "ttcnpp"]}, + {name: "TTCN_CFG", mime: "text/x-ttcn-cfg", mode: "ttcn-cfg", ext: ["cfg"]}, + {name: "Turtle", mime: "text/turtle", mode: "turtle", ext: ["ttl"]}, + {name: "TypeScript", mime: "application/typescript", mode: "javascript", ext: ["ts"], alias: ["ts"]}, + {name: "TypeScript-JSX", mime: "text/typescript-jsx", mode: "jsx", ext: ["tsx"], alias: ["tsx"]}, + {name: "Twig", mime: "text/x-twig", mode: "twig"}, + {name: "Web IDL", mime: "text/x-webidl", mode: "webidl", ext: ["webidl"]}, + {name: "VB.NET", mime: "text/x-vb", mode: "vb", ext: ["vb"]}, + {name: "VBScript", mime: "text/vbscript", mode: "vbscript", ext: ["vbs"]}, + {name: "Velocity", mime: "text/velocity", mode: "velocity", ext: ["vtl"]}, + {name: "Verilog", mime: "text/x-verilog", mode: "verilog", ext: ["v"]}, + {name: "VHDL", mime: "text/x-vhdl", mode: "vhdl", ext: ["vhd", "vhdl"]}, + {name: "Vue.js Component", mimes: ["script/x-vue", "text/x-vue"], mode: "vue", ext: ["vue"]}, + {name: "XML", mimes: ["application/xml", "text/xml"], mode: "xml", ext: ["xml", "xsl", "xsd", "svg"], alias: ["rss", "wsdl", "xsd"]}, + {name: "XQuery", mime: "application/xquery", mode: "xquery", ext: ["xy", "xquery"]}, + {name: "Yacas", mime: "text/x-yacas", mode: "yacas", ext: ["ys"]}, + {name: "YAML", mimes: ["text/x-yaml", "text/yaml"], mode: "yaml", ext: ["yaml", "yml"], alias: ["yml"]}, + {name: "Z80", mime: "text/x-z80", mode: "z80", ext: ["z80"]}, + {name: "mscgen", mime: "text/x-mscgen", mode: "mscgen", ext: ["mscgen", "mscin", "msc"]}, + {name: "xu", mime: "text/x-xu", mode: "mscgen", ext: ["xu"]}, + {name: "msgenny", mime: "text/x-msgenny", mode: "mscgen", ext: ["msgenny"]} + ]; + // Ensure all modes have a mime property for backwards compatibility + for (var i = 0; i < CodeMirror.modeInfo.length; i++) { + var info = CodeMirror.modeInfo[i]; + if (info.mimes) info.mime = info.mimes[0]; + } + + CodeMirror.findModeByMIME = function(mime) { + mime = mime.toLowerCase(); + for (var i = 0; i < CodeMirror.modeInfo.length; i++) { + var info = CodeMirror.modeInfo[i]; + if (info.mime == mime) return info; + if (info.mimes) for (var j = 0; j < info.mimes.length; j++) + if (info.mimes[j] == mime) return info; + } + if (/\+xml$/.test(mime)) return CodeMirror.findModeByMIME("application/xml") + if (/\+json$/.test(mime)) return CodeMirror.findModeByMIME("application/json") + }; + + CodeMirror.findModeByExtension = function(ext) { + for (var i = 0; i < CodeMirror.modeInfo.length; i++) { + var info = CodeMirror.modeInfo[i]; + if (info.ext) for (var j = 0; j < info.ext.length; j++) + if (info.ext[j] == ext) return info; + } + }; + + CodeMirror.findModeByFileName = function(filename) { + for (var i = 0; i < CodeMirror.modeInfo.length; i++) { + var info = CodeMirror.modeInfo[i]; + if (info.file && info.file.test(filename)) return info; + } + var dot = filename.lastIndexOf("."); + var ext = dot > -1 && filename.substring(dot + 1, filename.length); + if (ext) return CodeMirror.findModeByExtension(ext); + }; + + CodeMirror.findModeByName = function(name) { + name = name.toLowerCase(); + for (var i = 0; i < CodeMirror.modeInfo.length; i++) { + var info = CodeMirror.modeInfo[i]; + if (info.name.toLowerCase() == name) return info; + if (info.alias) for (var j = 0; j < info.alias.length; j++) + if (info.alias[j].toLowerCase() == name) return info; + } + }; +}); diff --git a/modules/editor/codemirror/mode/octave/octave.min.js b/modules/editor/codemirror/mode/octave/octave.min.js @@ -0,0 +1,139 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("octave", function() { + function wordRegexp(words) { + return new RegExp("^((" + words.join(")|(") + "))\\b"); + } + + var singleOperators = new RegExp("^[\\+\\-\\*/&|\\^~<>!@'\\\\]"); + var singleDelimiters = new RegExp('^[\\(\\[\\{\\},:=;]'); + var doubleOperators = new RegExp("^((==)|(~=)|(<=)|(>=)|(<<)|(>>)|(\\.[\\+\\-\\*/\\^\\\\]))"); + var doubleDelimiters = new RegExp("^((!=)|(\\+=)|(\\-=)|(\\*=)|(/=)|(&=)|(\\|=)|(\\^=))"); + var tripleDelimiters = new RegExp("^((>>=)|(<<=))"); + var expressionEnd = new RegExp("^[\\]\\)]"); + var identifiers = new RegExp("^[_A-Za-z\xa1-\uffff][_A-Za-z0-9\xa1-\uffff]*"); + + var builtins = wordRegexp([ + 'error', 'eval', 'function', 'abs', 'acos', 'atan', 'asin', 'cos', + 'cosh', 'exp', 'log', 'prod', 'sum', 'log10', 'max', 'min', 'sign', 'sin', 'sinh', + 'sqrt', 'tan', 'reshape', 'break', 'zeros', 'default', 'margin', 'round', 'ones', + 'rand', 'syn', 'ceil', 'floor', 'size', 'clear', 'zeros', 'eye', 'mean', 'std', 'cov', + 'det', 'eig', 'inv', 'norm', 'rank', 'trace', 'expm', 'logm', 'sqrtm', 'linspace', 'plot', + 'title', 'xlabel', 'ylabel', 'legend', 'text', 'grid', 'meshgrid', 'mesh', 'num2str', + 'fft', 'ifft', 'arrayfun', 'cellfun', 'input', 'fliplr', 'flipud', 'ismember' + ]); + + var keywords = wordRegexp([ + 'return', 'case', 'switch', 'else', 'elseif', 'end', 'endif', 'endfunction', + 'if', 'otherwise', 'do', 'for', 'while', 'try', 'catch', 'classdef', 'properties', 'events', + 'methods', 'global', 'persistent', 'endfor', 'endwhile', 'printf', 'sprintf', 'disp', 'until', + 'continue', 'pkg' + ]); + + + // tokenizers + function tokenTranspose(stream, state) { + if (!stream.sol() && stream.peek() === '\'') { + stream.next(); + state.tokenize = tokenBase; + return 'operator'; + } + state.tokenize = tokenBase; + return tokenBase(stream, state); + } + + + function tokenComment(stream, state) { + if (stream.match(/^.*%}/)) { + state.tokenize = tokenBase; + return 'comment'; + }; + stream.skipToEnd(); + return 'comment'; + } + + function tokenBase(stream, state) { + // whitespaces + if (stream.eatSpace()) return null; + + // Handle one line Comments + if (stream.match('%{')){ + state.tokenize = tokenComment; + stream.skipToEnd(); + return 'comment'; + } + + if (stream.match(/^[%#]/)){ + stream.skipToEnd(); + return 'comment'; + } + + // Handle Number Literals + if (stream.match(/^[0-9\.+-]/, false)) { + if (stream.match(/^[+-]?0x[0-9a-fA-F]+[ij]?/)) { + stream.tokenize = tokenBase; + return 'number'; }; + if (stream.match(/^[+-]?\d*\.\d+([EeDd][+-]?\d+)?[ij]?/)) { return 'number'; }; + if (stream.match(/^[+-]?\d+([EeDd][+-]?\d+)?[ij]?/)) { return 'number'; }; + } + if (stream.match(wordRegexp(['nan','NaN','inf','Inf']))) { return 'number'; }; + + // Handle Strings + var m = stream.match(/^"(?:[^"]|"")*("|$)/) || stream.match(/^'(?:[^']|'')*('|$)/) + if (m) { return m[1] ? 'string' : "string error"; } + + // Handle words + if (stream.match(keywords)) { return 'keyword'; } ; + if (stream.match(builtins)) { return 'builtin'; } ; + if (stream.match(identifiers)) { return 'variable'; } ; + + if (stream.match(singleOperators) || stream.match(doubleOperators)) { return 'operator'; }; + if (stream.match(singleDelimiters) || stream.match(doubleDelimiters) || stream.match(tripleDelimiters)) { return null; }; + + if (stream.match(expressionEnd)) { + state.tokenize = tokenTranspose; + return null; + }; + + + // Handle non-detected items + stream.next(); + return 'error'; + }; + + + return { + startState: function() { + return { + tokenize: tokenBase + }; + }, + + token: function(stream, state) { + var style = state.tokenize(stream, state); + if (style === 'number' || style === 'variable'){ + state.tokenize = tokenTranspose; + } + return style; + }, + + lineComment: '%', + + fold: 'indent' + }; +}); + +CodeMirror.defineMIME("text/x-octave", "octave"); + +}); diff --git a/modules/editor/codemirror/mode/pegjs/pegjs.min.js b/modules/editor/codemirror/mode/pegjs/pegjs.min.js @@ -0,0 +1,114 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../javascript/javascript")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../javascript/javascript"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("pegjs", function (config) { + var jsMode = CodeMirror.getMode(config, "javascript"); + + function identifier(stream) { + return stream.match(/^[a-zA-Z_][a-zA-Z0-9_]*/); + } + + return { + startState: function () { + return { + inString: false, + stringType: null, + inComment: false, + inCharacterClass: false, + braced: 0, + lhs: true, + localState: null + }; + }, + token: function (stream, state) { + if (stream) + + //check for state changes + if (!state.inString && !state.inComment && ((stream.peek() == '"') || (stream.peek() == "'"))) { + state.stringType = stream.peek(); + stream.next(); // Skip quote + state.inString = true; // Update state + } + if (!state.inString && !state.inComment && stream.match(/^\/\*/)) { + state.inComment = true; + } + + //return state + if (state.inString) { + while (state.inString && !stream.eol()) { + if (stream.peek() === state.stringType) { + stream.next(); // Skip quote + state.inString = false; // Clear flag + } else if (stream.peek() === '\\') { + stream.next(); + stream.next(); + } else { + stream.match(/^.[^\\\"\']*/); + } + } + return state.lhs ? "property string" : "string"; // Token style + } else if (state.inComment) { + while (state.inComment && !stream.eol()) { + if (stream.match(/\*\//)) { + state.inComment = false; // Clear flag + } else { + stream.match(/^.[^\*]*/); + } + } + return "comment"; + } else if (state.inCharacterClass) { + while (state.inCharacterClass && !stream.eol()) { + if (!(stream.match(/^[^\]\\]+/) || stream.match(/^\\./))) { + state.inCharacterClass = false; + } + } + } else if (stream.peek() === '[') { + stream.next(); + state.inCharacterClass = true; + return 'bracket'; + } else if (stream.match(/^\/\//)) { + stream.skipToEnd(); + return "comment"; + } else if (state.braced || stream.peek() === '{') { + if (state.localState === null) { + state.localState = CodeMirror.startState(jsMode); + } + var token = jsMode.token(stream, state.localState); + var text = stream.current(); + if (!token) { + for (var i = 0; i < text.length; i++) { + if (text[i] === '{') { + state.braced++; + } else if (text[i] === '}') { + state.braced--; + } + }; + } + return token; + } else if (identifier(stream)) { + if (stream.peek() === ':') { + return 'variable'; + } + return 'variable-2'; + } else if (['[', ']', '(', ')'].indexOf(stream.peek()) != -1) { + stream.next(); + return 'bracket'; + } else if (!stream.eatSpace()) { + stream.next(); + } + return null; + } + }; +}, "javascript"); + +}); diff --git a/modules/editor/codemirror/mode/php/test.min.js b/modules/editor/codemirror/mode/php/test.min.js @@ -0,0 +1,154 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var mode = CodeMirror.getMode({indentUnit: 2}, "php"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); } + + MT('simple_test', + '[meta <?php] ' + + '[keyword echo] [string "aaa"]; ' + + '[meta ?>]'); + + MT('variable_interpolation_non_alphanumeric', + '[meta <?php]', + '[keyword echo] [string "aaa$~$!$@$#$$$%$^$&$*$($)$.$<$>$/$\\$}$\\\"$:$;$?$|$[[$]]$+$=aaa"]', + '[meta ?>]'); + + MT('variable_interpolation_digits', + '[meta <?php]', + '[keyword echo] [string "aaa$1$2$3$4$5$6$7$8$9$0aaa"]', + '[meta ?>]'); + + MT('variable_interpolation_simple_syntax_1', + '[meta <?php]', + '[keyword echo] [string "aaa][variable-2 $aaa][string .aaa"];', + '[meta ?>]'); + + MT('variable_interpolation_simple_syntax_2', + '[meta <?php]', + '[keyword echo] [string "][variable-2 $aaaa][[','[number 2]', ']][string aa"];', + '[keyword echo] [string "][variable-2 $aaaa][[','[number 2345]', ']][string aa"];', + '[keyword echo] [string "][variable-2 $aaaa][[','[number 2.3]', ']][string aa"];', + '[keyword echo] [string "][variable-2 $aaaa][[','[variable aaaaa]', ']][string aa"];', + '[keyword echo] [string "][variable-2 $aaaa][[','[variable-2 $aaaaa]',']][string aa"];', + + '[keyword echo] [string "1aaa][variable-2 $aaaa][[','[number 2]', ']][string aa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa][[','[number 2345]', ']][string aa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa][[','[number 2.3]', ']][string aa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa][[','[variable aaaaa]', ']][string aa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa][[','[variable-2 $aaaaa]',']][string aa"];', + '[meta ?>]'); + + MT('variable_interpolation_simple_syntax_3', + '[meta <?php]', + '[keyword echo] [string "aaa][variable-2 $aaaa]->[variable aaaaa][string .aaaaaa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa][string ->][variable-2 $aaaaa][string .aaaaaa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa]->[variable aaaaa][string [[2]].aaaaaa"];', + '[keyword echo] [string "aaa][variable-2 $aaaa]->[variable aaaaa][string ->aaaa2.aaaaaa"];', + '[meta ?>]'); + + MT('variable_interpolation_escaping', + '[meta <?php] [comment /* Escaping */]', + '[keyword echo] [string "aaa\\$aaaa->aaa.aaa"];', + '[keyword echo] [string "aaa\\$aaaa[[2]]aaa.aaa"];', + '[keyword echo] [string "aaa\\$aaaa[[asd]]aaa.aaa"];', + '[keyword echo] [string "aaa{\\$aaaa->aaa.aaa"];', + '[keyword echo] [string "aaa{\\$aaaa[[2]]aaa.aaa"];', + '[keyword echo] [string "aaa{\\aaaaa[[asd]]aaa.aaa"];', + '[keyword echo] [string "aaa\\${aaaa->aaa.aaa"];', + '[keyword echo] [string "aaa\\${aaaa[[2]]aaa.aaa"];', + '[keyword echo] [string "aaa\\${aaaa[[asd]]aaa.aaa"];', + '[meta ?>]'); + + MT('variable_interpolation_complex_syntax_1', + '[meta <?php]', + '[keyword echo] [string "aaa][variable-2 $]{[variable aaaa]}[string ->aaa.aaa"];', + '[keyword echo] [string "aaa][variable-2 $]{[variable-2 $aaaa]}[string ->aaa.aaa"];', + '[keyword echo] [string "aaa][variable-2 $]{[variable-2 $aaaa][[',' [number 42]',']]}[string ->aaa.aaa"];', + '[keyword echo] [string "aaa][variable-2 $]{[variable aaaa][meta ?>]aaaaaa'); + + MT('variable_interpolation_complex_syntax_2', + '[meta <?php] [comment /* Monsters */]', + '[keyword echo] [string "][variable-2 $]{[variable aaa][comment /*}?>} $aaa<?php } */]}[string ->aaa.aaa"];', + '[keyword echo] [string "][variable-2 $]{[variable aaa][comment /*}?>*/][[',' [string "aaa][variable-2 $aaa][string {}][variable-2 $]{[variable aaa]}[string "]',']]}[string ->aaa.aaa"];', + '[keyword echo] [string "][variable-2 $]{[variable aaa][comment /*} } $aaa } */]}[string ->aaa.aaa"];'); + + + function build_recursive_monsters(nt, t, n){ + var monsters = [t]; + for (var i = 1; i <= n; ++i) + monsters[i] = nt.join(monsters[i - 1]); + return monsters; + } + + var m1 = build_recursive_monsters( + ['[string "][variable-2 $]{[variable aaa] [operator +] ', '}[string "]'], + '[comment /* }?>} */] [string "aaa][variable-2 $aaa][string .aaa"]', + 10 + ); + + MT('variable_interpolation_complex_syntax_3_1', + '[meta <?php] [comment /* Recursive monsters */]', + '[keyword echo] ' + m1[4] + ';', + '[keyword echo] ' + m1[7] + ';', + '[keyword echo] ' + m1[8] + ';', + '[keyword echo] ' + m1[5] + ';', + '[keyword echo] ' + m1[1] + ';', + '[keyword echo] ' + m1[6] + ';', + '[keyword echo] ' + m1[9] + ';', + '[keyword echo] ' + m1[0] + ';', + '[keyword echo] ' + m1[10] + ';', + '[keyword echo] ' + m1[2] + ';', + '[keyword echo] ' + m1[3] + ';', + '[keyword echo] [string "end"];', + '[meta ?>]'); + + var m2 = build_recursive_monsters( + ['[string "a][variable-2 $]{[variable aaa] [operator +] ', ' [operator +] ', '}[string .a"]'], + '[comment /* }?>{{ */] [string "a?>}{{aa][variable-2 $aaa][string .a}a?>a"]', + 5 + ); + + MT('variable_interpolation_complex_syntax_3_2', + '[meta <?php] [comment /* Recursive monsters 2 */]', + '[keyword echo] ' + m2[0] + ';', + '[keyword echo] ' + m2[1] + ';', + '[keyword echo] ' + m2[5] + ';', + '[keyword echo] ' + m2[4] + ';', + '[keyword echo] ' + m2[2] + ';', + '[keyword echo] ' + m2[3] + ';', + '[keyword echo] [string "end"];', + '[meta ?>]'); + + function build_recursive_monsters_2(mf1, mf2, nt, t, n){ + var monsters = [t]; + for (var i = 1; i <= n; ++i) + monsters[i] = nt[0] + mf1[i - 1] + nt[1] + mf2[i - 1] + nt[2] + monsters[i - 1] + nt[3]; + return monsters; + } + + var m3 = build_recursive_monsters_2( + m1, + m2, + ['[string "a][variable-2 $]{[variable aaa] [operator +] ', ' [operator +] ', ' [operator +] ', '}[string .a"]'], + '[comment /* }?>{{ */] [string "a?>}{{aa][variable-2 $aaa][string .a}a?>a"]', + 4 + ); + + MT('variable_interpolation_complex_syntax_3_3', + '[meta <?php] [comment /* Recursive monsters 2 */]', + '[keyword echo] ' + m3[4] + ';', + '[keyword echo] ' + m3[0] + ';', + '[keyword echo] ' + m3[3] + ';', + '[keyword echo] ' + m3[1] + ';', + '[keyword echo] ' + m3[2] + ';', + '[keyword echo] [string "end"];', + '[meta ?>]'); + + MT("variable_interpolation_heredoc", + "[meta <?php]", + "[string <<<here]", + "[string doc ][variable-2 $]{[variable yay]}[string more]", + "[string here]; [comment // normal]"); +})(); diff --git a/modules/editor/codemirror/mode/powershell/powershell.min.js b/modules/editor/codemirror/mode/powershell/powershell.min.js @@ -0,0 +1,398 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + 'use strict'; + if (typeof exports == 'object' && typeof module == 'object') // CommonJS + mod(require('../../lib/codemirror')); + else if (typeof define == 'function' && define.amd) // AMD + define(['../../lib/codemirror'], mod); + else // Plain browser env + mod(window.CodeMirror); +})(function(CodeMirror) { +'use strict'; + +CodeMirror.defineMode('powershell', function() { + function buildRegexp(patterns, options) { + options = options || {}; + var prefix = options.prefix !== undefined ? options.prefix : '^'; + var suffix = options.suffix !== undefined ? options.suffix : '\\b'; + + for (var i = 0; i < patterns.length; i++) { + if (patterns[i] instanceof RegExp) { + patterns[i] = patterns[i].source; + } + else { + patterns[i] = patterns[i].replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + } + + return new RegExp(prefix + '(' + patterns.join('|') + ')' + suffix, 'i'); + } + + var notCharacterOrDash = '(?=[^A-Za-z\\d\\-_]|$)'; + var varNames = /[\w\-:]/ + var keywords = buildRegexp([ + /begin|break|catch|continue|data|default|do|dynamicparam/, + /else|elseif|end|exit|filter|finally|for|foreach|from|function|if|in/, + /param|process|return|switch|throw|trap|try|until|where|while/ + ], { suffix: notCharacterOrDash }); + + var punctuation = /[\[\]{},;`\.]|@[({]/; + var wordOperators = buildRegexp([ + 'f', + /b?not/, + /[ic]?split/, 'join', + /is(not)?/, 'as', + /[ic]?(eq|ne|[gl][te])/, + /[ic]?(not)?(like|match|contains)/, + /[ic]?replace/, + /b?(and|or|xor)/ + ], { prefix: '-' }); + var symbolOperators = /[+\-*\/%]=|\+\+|--|\.\.|[+\-*&^%:=!|\/]|<(?!#)|(?!#)>/; + var operators = buildRegexp([wordOperators, symbolOperators], { suffix: '' }); + + var numbers = /^((0x[\da-f]+)|((\d+\.\d+|\d\.|\.\d+|\d+)(e[\+\-]?\d+)?))[ld]?([kmgtp]b)?/i; + + var identifiers = /^[A-Za-z\_][A-Za-z\-\_\d]*\b/; + + var symbolBuiltins = /[A-Z]:|%|\?/i; + var namedBuiltins = buildRegexp([ + /Add-(Computer|Content|History|Member|PSSnapin|Type)/, + /Checkpoint-Computer/, + /Clear-(Content|EventLog|History|Host|Item(Property)?|Variable)/, + /Compare-Object/, + /Complete-Transaction/, + /Connect-PSSession/, + /ConvertFrom-(Csv|Json|SecureString|StringData)/, + /Convert-Path/, + /ConvertTo-(Csv|Html|Json|SecureString|Xml)/, + /Copy-Item(Property)?/, + /Debug-Process/, + /Disable-(ComputerRestore|PSBreakpoint|PSRemoting|PSSessionConfiguration)/, + /Disconnect-PSSession/, + /Enable-(ComputerRestore|PSBreakpoint|PSRemoting|PSSessionConfiguration)/, + /(Enter|Exit)-PSSession/, + /Export-(Alias|Clixml|Console|Counter|Csv|FormatData|ModuleMember|PSSession)/, + /ForEach-Object/, + /Format-(Custom|List|Table|Wide)/, + new RegExp('Get-(Acl|Alias|AuthenticodeSignature|ChildItem|Command|ComputerRestorePoint|Content|ControlPanelItem|Counter|Credential' + + '|Culture|Date|Event|EventLog|EventSubscriber|ExecutionPolicy|FormatData|Help|History|Host|HotFix|Item|ItemProperty|Job' + + '|Location|Member|Module|PfxCertificate|Process|PSBreakpoint|PSCallStack|PSDrive|PSProvider|PSSession|PSSessionConfiguration' + + '|PSSnapin|Random|Service|TraceSource|Transaction|TypeData|UICulture|Unique|Variable|Verb|WinEvent|WmiObject)'), + /Group-Object/, + /Import-(Alias|Clixml|Counter|Csv|LocalizedData|Module|PSSession)/, + /ImportSystemModules/, + /Invoke-(Command|Expression|History|Item|RestMethod|WebRequest|WmiMethod)/, + /Join-Path/, + /Limit-EventLog/, + /Measure-(Command|Object)/, + /Move-Item(Property)?/, + new RegExp('New-(Alias|Event|EventLog|Item(Property)?|Module|ModuleManifest|Object|PSDrive|PSSession|PSSessionConfigurationFile' + + '|PSSessionOption|PSTransportOption|Service|TimeSpan|Variable|WebServiceProxy|WinEvent)'), + /Out-(Default|File|GridView|Host|Null|Printer|String)/, + /Pause/, + /(Pop|Push)-Location/, + /Read-Host/, + /Receive-(Job|PSSession)/, + /Register-(EngineEvent|ObjectEvent|PSSessionConfiguration|WmiEvent)/, + /Remove-(Computer|Event|EventLog|Item(Property)?|Job|Module|PSBreakpoint|PSDrive|PSSession|PSSnapin|TypeData|Variable|WmiObject)/, + /Rename-(Computer|Item(Property)?)/, + /Reset-ComputerMachinePassword/, + /Resolve-Path/, + /Restart-(Computer|Service)/, + /Restore-Computer/, + /Resume-(Job|Service)/, + /Save-Help/, + /Select-(Object|String|Xml)/, + /Send-MailMessage/, + new RegExp('Set-(Acl|Alias|AuthenticodeSignature|Content|Date|ExecutionPolicy|Item(Property)?|Location|PSBreakpoint|PSDebug' + + '|PSSessionConfiguration|Service|StrictMode|TraceSource|Variable|WmiInstance)'), + /Show-(Command|ControlPanelItem|EventLog)/, + /Sort-Object/, + /Split-Path/, + /Start-(Job|Process|Service|Sleep|Transaction|Transcript)/, + /Stop-(Computer|Job|Process|Service|Transcript)/, + /Suspend-(Job|Service)/, + /TabExpansion2/, + /Tee-Object/, + /Test-(ComputerSecureChannel|Connection|ModuleManifest|Path|PSSessionConfigurationFile)/, + /Trace-Command/, + /Unblock-File/, + /Undo-Transaction/, + /Unregister-(Event|PSSessionConfiguration)/, + /Update-(FormatData|Help|List|TypeData)/, + /Use-Transaction/, + /Wait-(Event|Job|Process)/, + /Where-Object/, + /Write-(Debug|Error|EventLog|Host|Output|Progress|Verbose|Warning)/, + /cd|help|mkdir|more|oss|prompt/, + /ac|asnp|cat|cd|chdir|clc|clear|clhy|cli|clp|cls|clv|cnsn|compare|copy|cp|cpi|cpp|cvpa|dbp|del|diff|dir|dnsn|ebp/, + /echo|epal|epcsv|epsn|erase|etsn|exsn|fc|fl|foreach|ft|fw|gal|gbp|gc|gci|gcm|gcs|gdr|ghy|gi|gjb|gl|gm|gmo|gp|gps/, + /group|gsn|gsnp|gsv|gu|gv|gwmi|h|history|icm|iex|ihy|ii|ipal|ipcsv|ipmo|ipsn|irm|ise|iwmi|iwr|kill|lp|ls|man|md/, + /measure|mi|mount|move|mp|mv|nal|ndr|ni|nmo|npssc|nsn|nv|ogv|oh|popd|ps|pushd|pwd|r|rbp|rcjb|rcsn|rd|rdr|ren|ri/, + /rjb|rm|rmdir|rmo|rni|rnp|rp|rsn|rsnp|rujb|rv|rvpa|rwmi|sajb|sal|saps|sasv|sbp|sc|select|set|shcm|si|sl|sleep|sls/, + /sort|sp|spjb|spps|spsv|start|sujb|sv|swmi|tee|trcm|type|where|wjb|write/ + ], { prefix: '', suffix: '' }); + var variableBuiltins = buildRegexp([ + /[$?^_]|Args|ConfirmPreference|ConsoleFileName|DebugPreference|Error|ErrorActionPreference|ErrorView|ExecutionContext/, + /FormatEnumerationLimit|Home|Host|Input|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount/, + /MaximumHistoryCount|MaximumVariableCount|MyInvocation|NestedPromptLevel|OutputEncoding|Pid|Profile|ProgressPreference/, + /PSBoundParameters|PSCommandPath|PSCulture|PSDefaultParameterValues|PSEmailServer|PSHome|PSScriptRoot|PSSessionApplicationName/, + /PSSessionConfigurationName|PSSessionOption|PSUICulture|PSVersionTable|Pwd|ShellId|StackTrace|VerbosePreference/, + /WarningPreference|WhatIfPreference/, + + /Event|EventArgs|EventSubscriber|Sender/, + /Matches|Ofs|ForEach|LastExitCode|PSCmdlet|PSItem|PSSenderInfo|This/, + /true|false|null/ + ], { prefix: '\\$', suffix: '' }); + + var builtins = buildRegexp([symbolBuiltins, namedBuiltins, variableBuiltins], { suffix: notCharacterOrDash }); + + var grammar = { + keyword: keywords, + number: numbers, + operator: operators, + builtin: builtins, + punctuation: punctuation, + identifier: identifiers + }; + + // tokenizers + function tokenBase(stream, state) { + // Handle Comments + //var ch = stream.peek(); + + var parent = state.returnStack[state.returnStack.length - 1]; + if (parent && parent.shouldReturnFrom(state)) { + state.tokenize = parent.tokenize; + state.returnStack.pop(); + return state.tokenize(stream, state); + } + + if (stream.eatSpace()) { + return null; + } + + if (stream.eat('(')) { + state.bracketNesting += 1; + return 'punctuation'; + } + + if (stream.eat(')')) { + state.bracketNesting -= 1; + return 'punctuation'; + } + + for (var key in grammar) { + if (stream.match(grammar[key])) { + return key; + } + } + + var ch = stream.next(); + + // single-quote string + if (ch === "'") { + return tokenSingleQuoteString(stream, state); + } + + if (ch === '$') { + return tokenVariable(stream, state); + } + + // double-quote string + if (ch === '"') { + return tokenDoubleQuoteString(stream, state); + } + + if (ch === '<' && stream.eat('#')) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + + if (ch === '#') { + stream.skipToEnd(); + return 'comment'; + } + + if (ch === '@') { + var quoteMatch = stream.eat(/["']/); + if (quoteMatch && stream.eol()) { + state.tokenize = tokenMultiString; + state.startQuote = quoteMatch[0]; + return tokenMultiString(stream, state); + } else if (stream.eol()) { + return 'error'; + } else if (stream.peek().match(/[({]/)) { + return 'punctuation'; + } else if (stream.peek().match(varNames)) { + // splatted variable + return tokenVariable(stream, state); + } + } + return 'error'; + } + + function tokenSingleQuoteString(stream, state) { + var ch; + while ((ch = stream.peek()) != null) { + stream.next(); + + if (ch === "'" && !stream.eat("'")) { + state.tokenize = tokenBase; + return 'string'; + } + } + + return 'error'; + } + + function tokenDoubleQuoteString(stream, state) { + var ch; + while ((ch = stream.peek()) != null) { + if (ch === '$') { + state.tokenize = tokenStringInterpolation; + return 'string'; + } + + stream.next(); + if (ch === '`') { + stream.next(); + continue; + } + + if (ch === '"' && !stream.eat('"')) { + state.tokenize = tokenBase; + return 'string'; + } + } + + return 'error'; + } + + function tokenStringInterpolation(stream, state) { + return tokenInterpolation(stream, state, tokenDoubleQuoteString); + } + + function tokenMultiStringReturn(stream, state) { + state.tokenize = tokenMultiString; + state.startQuote = '"' + return tokenMultiString(stream, state); + } + + function tokenHereStringInterpolation(stream, state) { + return tokenInterpolation(stream, state, tokenMultiStringReturn); + } + + function tokenInterpolation(stream, state, parentTokenize) { + if (stream.match('$(')) { + var savedBracketNesting = state.bracketNesting; + state.returnStack.push({ + /*jshint loopfunc:true */ + shouldReturnFrom: function(state) { + return state.bracketNesting === savedBracketNesting; + }, + tokenize: parentTokenize + }); + state.tokenize = tokenBase; + state.bracketNesting += 1; + return 'punctuation'; + } else { + stream.next(); + state.returnStack.push({ + shouldReturnFrom: function() { return true; }, + tokenize: parentTokenize + }); + state.tokenize = tokenVariable; + return state.tokenize(stream, state); + } + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while ((ch = stream.next()) != null) { + if (maybeEnd && ch == '>') { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch === '#'); + } + return 'comment'; + } + + function tokenVariable(stream, state) { + var ch = stream.peek(); + if (stream.eat('{')) { + state.tokenize = tokenVariableWithBraces; + return tokenVariableWithBraces(stream, state); + } else if (ch != undefined && ch.match(varNames)) { + stream.eatWhile(varNames); + state.tokenize = tokenBase; + return 'variable-2'; + } else { + state.tokenize = tokenBase; + return 'error'; + } + } + + function tokenVariableWithBraces(stream, state) { + var ch; + while ((ch = stream.next()) != null) { + if (ch === '}') { + state.tokenize = tokenBase; + break; + } + } + return 'variable-2'; + } + + function tokenMultiString(stream, state) { + var quote = state.startQuote; + if (stream.sol() && stream.match(new RegExp(quote + '@'))) { + state.tokenize = tokenBase; + } + else if (quote === '"') { + while (!stream.eol()) { + var ch = stream.peek(); + if (ch === '$') { + state.tokenize = tokenHereStringInterpolation; + return 'string'; + } + + stream.next(); + if (ch === '`') { + stream.next(); + } + } + } + else { + stream.skipToEnd(); + } + + return 'string'; + } + + var external = { + startState: function() { + return { + returnStack: [], + bracketNesting: 0, + tokenize: tokenBase + }; + }, + + token: function(stream, state) { + return state.tokenize(stream, state); + }, + + blockCommentStart: '<#', + blockCommentEnd: '#>', + lineComment: '#', + fold: 'brace' + }; + return external; +}); + +CodeMirror.defineMIME('application/x-powershell', 'powershell'); +}); diff --git a/modules/editor/codemirror/mode/rpm/rpm.min.js b/modules/editor/codemirror/mode/rpm/rpm.min.js @@ -0,0 +1,109 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("rpm-changes", function() { + var headerSeperator = /^-+$/; + var headerLine = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ?\d{1,2} \d{2}:\d{2}(:\d{2})? [A-Z]{3,4} \d{4} - /; + var simpleEmail = /^[\w+.-]+@[\w.-]+/; + + return { + token: function(stream) { + if (stream.sol()) { + if (stream.match(headerSeperator)) { return 'tag'; } + if (stream.match(headerLine)) { return 'tag'; } + } + if (stream.match(simpleEmail)) { return 'string'; } + stream.next(); + return null; + } + }; +}); + +CodeMirror.defineMIME("text/x-rpm-changes", "rpm-changes"); + +// Quick and dirty spec file highlighting + +CodeMirror.defineMode("rpm-spec", function() { + var arch = /^(i386|i586|i686|x86_64|ppc64le|ppc64|ppc|ia64|s390x|s390|sparc64|sparcv9|sparc|noarch|alphaev6|alpha|hppa|mipsel)/; + + var preamble = /^[a-zA-Z0-9()]+:/; + var section = /^%(debug_package|package|description|prep|build|install|files|clean|changelog|preinstall|preun|postinstall|postun|pretrans|posttrans|pre|post|triggerin|triggerun|verifyscript|check|triggerpostun|triggerprein|trigger)/; + var control_flow_complex = /^%(ifnarch|ifarch|if)/; // rpm control flow macros + var control_flow_simple = /^%(else|endif)/; // rpm control flow macros + var operators = /^(\!|\?|\<\=|\<|\>\=|\>|\=\=|\&\&|\|\|)/; // operators in control flow macros + + return { + startState: function () { + return { + controlFlow: false, + macroParameters: false, + section: false + }; + }, + token: function (stream, state) { + var ch = stream.peek(); + if (ch == "#") { stream.skipToEnd(); return "comment"; } + + if (stream.sol()) { + if (stream.match(preamble)) { return "header"; } + if (stream.match(section)) { return "atom"; } + } + + if (stream.match(/^\$\w+/)) { return "def"; } // Variables like '$RPM_BUILD_ROOT' + if (stream.match(/^\$\{\w+\}/)) { return "def"; } // Variables like '${RPM_BUILD_ROOT}' + + if (stream.match(control_flow_simple)) { return "keyword"; } + if (stream.match(control_flow_complex)) { + state.controlFlow = true; + return "keyword"; + } + if (state.controlFlow) { + if (stream.match(operators)) { return "operator"; } + if (stream.match(/^(\d+)/)) { return "number"; } + if (stream.eol()) { state.controlFlow = false; } + } + + if (stream.match(arch)) { + if (stream.eol()) { state.controlFlow = false; } + return "number"; + } + + // Macros like '%make_install' or '%attr(0775,root,root)' + if (stream.match(/^%[\w]+/)) { + if (stream.match(/^\(/)) { state.macroParameters = true; } + return "keyword"; + } + if (state.macroParameters) { + if (stream.match(/^\d+/)) { return "number";} + if (stream.match(/^\)/)) { + state.macroParameters = false; + return "keyword"; + } + } + + // Macros like '%{defined fedora}' + if (stream.match(/^%\{\??[\w \-\:\!]+\}/)) { + if (stream.eol()) { state.controlFlow = false; } + return "def"; + } + + //TODO: Include bash script sub-parser (CodeMirror supports that) + stream.next(); + return null; + } + }; +}); + +CodeMirror.defineMIME("text/x-rpm-spec", "rpm-spec"); + +}); diff --git a/modules/editor/codemirror/mode/ruby/ruby.min.js b/modules/editor/codemirror/mode/ruby/ruby.min.js @@ -0,0 +1,296 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("ruby", function(config) { + function wordObj(words) { + var o = {}; + for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true; + return o; + } + var keywords = wordObj([ + "alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else", + "elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or", + "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless", + "until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc", + "caller", "lambda", "proc", "public", "protected", "private", "require", "load", + "require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__" + ]); + var indentWords = wordObj(["def", "class", "case", "for", "while", "until", "module", "then", + "catch", "loop", "proc", "begin"]); + var dedentWords = wordObj(["end", "until"]); + var matching = {"[": "]", "{": "}", "(": ")"}; + var curPunc; + + function chain(newtok, stream, state) { + state.tokenize.push(newtok); + return newtok(stream, state); + } + + function tokenBase(stream, state) { + if (stream.sol() && stream.match("=begin") && stream.eol()) { + state.tokenize.push(readBlockComment); + return "comment"; + } + if (stream.eatSpace()) return null; + var ch = stream.next(), m; + if (ch == "`" || ch == "'" || ch == '"') { + return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state); + } else if (ch == "/") { + if (regexpAhead(stream)) + return chain(readQuoted(ch, "string-2", true), stream, state); + else + return "operator"; + } else if (ch == "%") { + var style = "string", embed = true; + if (stream.eat("s")) style = "atom"; + else if (stream.eat(/[WQ]/)) style = "string"; + else if (stream.eat(/[r]/)) style = "string-2"; + else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; } + var delim = stream.eat(/[^\w\s=]/); + if (!delim) return "operator"; + if (matching.propertyIsEnumerable(delim)) delim = matching[delim]; + return chain(readQuoted(delim, style, embed, true), stream, state); + } else if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } else if (ch == "<" && (m = stream.match(/^<-?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) { + return chain(readHereDoc(m[1]), stream, state); + } else if (ch == "0") { + if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/); + else if (stream.eat("b")) stream.eatWhile(/[01]/); + else stream.eatWhile(/[0-7]/); + return "number"; + } else if (/\d/.test(ch)) { + stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/); + return "number"; + } else if (ch == "?") { + while (stream.match(/^\\[CM]-/)) {} + if (stream.eat("\\")) stream.eatWhile(/\w/); + else stream.next(); + return "string"; + } else if (ch == ":") { + if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state); + if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state); + + // :> :>> :< :<< are valid symbols + if (stream.eat(/[\<\>]/)) { + stream.eat(/[\<\>]/); + return "atom"; + } + + // :+ :- :/ :* :| :& :! are valid symbols + if (stream.eat(/[\+\-\*\/\&\|\:\!]/)) { + return "atom"; + } + + // Symbols can't start by a digit + if (stream.eat(/[a-zA-Z$@_\xa1-\uffff]/)) { + stream.eatWhile(/[\w$\xa1-\uffff]/); + // Only one ? ! = is allowed and only as the last character + stream.eat(/[\?\!\=]/); + return "atom"; + } + return "operator"; + } else if (ch == "@" && stream.match(/^@?[a-zA-Z_\xa1-\uffff]/)) { + stream.eat("@"); + stream.eatWhile(/[\w\xa1-\uffff]/); + return "variable-2"; + } else if (ch == "$") { + if (stream.eat(/[a-zA-Z_]/)) { + stream.eatWhile(/[\w]/); + } else if (stream.eat(/\d/)) { + stream.eat(/\d/); + } else { + stream.next(); // Must be a special global like $: or $! + } + return "variable-3"; + } else if (/[a-zA-Z_\xa1-\uffff]/.test(ch)) { + stream.eatWhile(/[\w\xa1-\uffff]/); + stream.eat(/[\?\!]/); + if (stream.eat(":")) return "atom"; + return "ident"; + } else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) { + curPunc = "|"; + return null; + } else if (/[\(\)\[\]{}\\;]/.test(ch)) { + curPunc = ch; + return null; + } else if (ch == "-" && stream.eat(">")) { + return "arrow"; + } else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) { + var more = stream.eatWhile(/[=+\-\/*:\.^%<>~|]/); + if (ch == "." && !more) curPunc = "."; + return "operator"; + } else { + return null; + } + } + + function regexpAhead(stream) { + var start = stream.pos, depth = 0, next, found = false, escaped = false + while ((next = stream.next()) != null) { + if (!escaped) { + if ("[{(".indexOf(next) > -1) { + depth++ + } else if ("]})".indexOf(next) > -1) { + depth-- + if (depth < 0) break + } else if (next == "/" && depth == 0) { + found = true + break + } + escaped = next == "\\" + } else { + escaped = false + } + } + stream.backUp(stream.pos - start) + return found + } + + function tokenBaseUntilBrace(depth) { + if (!depth) depth = 1; + return function(stream, state) { + if (stream.peek() == "}") { + if (depth == 1) { + state.tokenize.pop(); + return state.tokenize[state.tokenize.length-1](stream, state); + } else { + state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth - 1); + } + } else if (stream.peek() == "{") { + state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth + 1); + } + return tokenBase(stream, state); + }; + } + function tokenBaseOnce() { + var alreadyCalled = false; + return function(stream, state) { + if (alreadyCalled) { + state.tokenize.pop(); + return state.tokenize[state.tokenize.length-1](stream, state); + } + alreadyCalled = true; + return tokenBase(stream, state); + }; + } + function readQuoted(quote, style, embed, unescaped) { + return function(stream, state) { + var escaped = false, ch; + + if (state.context.type === 'read-quoted-paused') { + state.context = state.context.prev; + stream.eat("}"); + } + + while ((ch = stream.next()) != null) { + if (ch == quote && (unescaped || !escaped)) { + state.tokenize.pop(); + break; + } + if (embed && ch == "#" && !escaped) { + if (stream.eat("{")) { + if (quote == "}") { + state.context = {prev: state.context, type: 'read-quoted-paused'}; + } + state.tokenize.push(tokenBaseUntilBrace()); + break; + } else if (/[@\$]/.test(stream.peek())) { + state.tokenize.push(tokenBaseOnce()); + break; + } + } + escaped = !escaped && ch == "\\"; + } + return style; + }; + } + function readHereDoc(phrase) { + return function(stream, state) { + if (stream.match(phrase)) state.tokenize.pop(); + else stream.skipToEnd(); + return "string"; + }; + } + function readBlockComment(stream, state) { + if (stream.sol() && stream.match("=end") && stream.eol()) + state.tokenize.pop(); + stream.skipToEnd(); + return "comment"; + } + + return { + startState: function() { + return {tokenize: [tokenBase], + indented: 0, + context: {type: "top", indented: -config.indentUnit}, + continuedLine: false, + lastTok: null, + varList: false}; + }, + + token: function(stream, state) { + curPunc = null; + if (stream.sol()) state.indented = stream.indentation(); + var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype; + var thisTok = curPunc; + if (style == "ident") { + var word = stream.current(); + style = state.lastTok == "." ? "property" + : keywords.propertyIsEnumerable(stream.current()) ? "keyword" + : /^[A-Z]/.test(word) ? "tag" + : (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def" + : "variable"; + if (style == "keyword") { + thisTok = word; + if (indentWords.propertyIsEnumerable(word)) kwtype = "indent"; + else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent"; + else if ((word == "if" || word == "unless") && stream.column() == stream.indentation()) + kwtype = "indent"; + else if (word == "do" && state.context.indented < state.indented) + kwtype = "indent"; + } + } + if (curPunc || (style && style != "comment")) state.lastTok = thisTok; + if (curPunc == "|") state.varList = !state.varList; + + if (kwtype == "indent" || /[\(\[\{]/.test(curPunc)) + state.context = {prev: state.context, type: curPunc || style, indented: state.indented}; + else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev) + state.context = state.context.prev; + + if (stream.eol()) + state.continuedLine = (curPunc == "\\" || style == "operator"); + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize[state.tokenize.length-1] != tokenBase) return 0; + var firstChar = textAfter && textAfter.charAt(0); + var ct = state.context; + var closing = ct.type == matching[firstChar] || + ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter); + return ct.indented + (closing ? 0 : config.indentUnit) + + (state.continuedLine ? config.indentUnit : 0); + }, + + electricInput: /^\s*(?:end|rescue|elsif|else|\})$/, + lineComment: "#", + fold: "indent" + }; +}); + +CodeMirror.defineMIME("text/x-ruby", "ruby"); + +}); diff --git a/modules/editor/codemirror/mode/rust/test.min.js b/modules/editor/codemirror/mode/rust/test.min.js @@ -0,0 +1,39 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function() { + var mode = CodeMirror.getMode({indentUnit: 4}, "rust"); + function MT(name) {test.mode(name, mode, Array.prototype.slice.call(arguments, 1));} + + MT('integer_test', + '[number 123i32]', + '[number 123u32]', + '[number 123_u32]', + '[number 0xff_u8]', + '[number 0o70_i16]', + '[number 0b1111_1111_1001_0000_i32]', + '[number 0usize]'); + + MT('float_test', + '[number 123.0f64]', + '[number 0.1f64]', + '[number 0.1f32]', + '[number 12E+99_f64]'); + + MT('string-literals-test', + '[string "foo"]', + '[string r"foo"]', + '[string "\\"foo\\""]', + '[string r#""foo""#]', + '[string "foo #\\"# bar"]', + + '[string b"foo"]', + '[string br"foo"]', + '[string b"\\"foo\\""]', + '[string br#""foo""#]', + '[string br##"foo #" bar"##]', + + "[string-2 'h']", + "[string-2 b'h']"); + +})(); diff --git a/modules/editor/codemirror/mode/scheme/scheme.min.js b/modules/editor/codemirror/mode/scheme/scheme.min.js @@ -0,0 +1,249 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +/** + * Author: Koh Zi Han, based on implementation by Koh Zi Chun + */ + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("scheme", function () { + var BUILTIN = "builtin", COMMENT = "comment", STRING = "string", + ATOM = "atom", NUMBER = "number", BRACKET = "bracket"; + var INDENT_WORD_SKIP = 2; + + function makeKeywords(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var keywords = makeKeywords("λ case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci<? char-ci=? char-ci>=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char<? char=? char>=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt #f floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci<? string-ci=? string-ci>=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string<? string=? string>=? string>? string? substring symbol->string symbol? #t tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"); + var indentKeys = makeKeywords("define let letrec let* lambda"); + + function stateStack(indent, type, prev) { // represents a state stack object + this.indent = indent; + this.type = type; + this.prev = prev; + } + + function pushStack(state, indent, type) { + state.indentStack = new stateStack(indent, type, state.indentStack); + } + + function popStack(state) { + state.indentStack = state.indentStack.prev; + } + + var binaryMatcher = new RegExp(/^(?:[-+]i|[-+][01]+#*(?:\/[01]+#*)?i|[-+]?[01]+#*(?:\/[01]+#*)?@[-+]?[01]+#*(?:\/[01]+#*)?|[-+]?[01]+#*(?:\/[01]+#*)?[-+](?:[01]+#*(?:\/[01]+#*)?)?i|[-+]?[01]+#*(?:\/[01]+#*)?)(?=[()\s;"]|$)/i); + var octalMatcher = new RegExp(/^(?:[-+]i|[-+][0-7]+#*(?:\/[0-7]+#*)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?@[-+]?[0-7]+#*(?:\/[0-7]+#*)?|[-+]?[0-7]+#*(?:\/[0-7]+#*)?[-+](?:[0-7]+#*(?:\/[0-7]+#*)?)?i|[-+]?[0-7]+#*(?:\/[0-7]+#*)?)(?=[()\s;"]|$)/i); + var hexMatcher = new RegExp(/^(?:[-+]i|[-+][\da-f]+#*(?:\/[\da-f]+#*)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?@[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?[-+](?:[\da-f]+#*(?:\/[\da-f]+#*)?)?i|[-+]?[\da-f]+#*(?:\/[\da-f]+#*)?)(?=[()\s;"]|$)/i); + var decimalMatcher = new RegExp(/^(?:[-+]i|[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)i|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)@[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)|[-+]?(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)[-+](?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*)?i|(?:(?:(?:\d+#+\.?#*|\d+\.\d*#*|\.\d+#*|\d+)(?:[esfdl][-+]?\d+)?)|\d+#*\/\d+#*))(?=[()\s;"]|$)/i); + + function isBinaryNumber (stream) { + return stream.match(binaryMatcher); + } + + function isOctalNumber (stream) { + return stream.match(octalMatcher); + } + + function isDecimalNumber (stream, backup) { + if (backup === true) { + stream.backUp(1); + } + return stream.match(decimalMatcher); + } + + function isHexNumber (stream) { + return stream.match(hexMatcher); + } + + return { + startState: function () { + return { + indentStack: null, + indentation: 0, + mode: false, + sExprComment: false + }; + }, + + token: function (stream, state) { + if (state.indentStack == null && stream.sol()) { + // update indentation, but only if indentStack is empty + state.indentation = stream.indentation(); + } + + // skip spaces + if (stream.eatSpace()) { + return null; + } + var returnType = null; + + switch(state.mode){ + case "string": // multi-line string parsing mode + var next, escaped = false; + while ((next = stream.next()) != null) { + if (next == "\"" && !escaped) { + + state.mode = false; + break; + } + escaped = !escaped && next == "\\"; + } + returnType = STRING; // continue on in scheme-string mode + break; + case "comment": // comment parsing mode + var next, maybeEnd = false; + while ((next = stream.next()) != null) { + if (next == "#" && maybeEnd) { + + state.mode = false; + break; + } + maybeEnd = (next == "|"); + } + returnType = COMMENT; + break; + case "s-expr-comment": // s-expr commenting mode + state.mode = false; + if(stream.peek() == "(" || stream.peek() == "["){ + // actually start scheme s-expr commenting mode + state.sExprComment = 0; + }else{ + // if not we just comment the entire of the next token + stream.eatWhile(/[^/s]/); // eat non spaces + returnType = COMMENT; + break; + } + default: // default parsing mode + var ch = stream.next(); + + if (ch == "\"") { + state.mode = "string"; + returnType = STRING; + + } else if (ch == "'") { + returnType = ATOM; + } else if (ch == '#') { + if (stream.eat("|")) { // Multi-line comment + state.mode = "comment"; // toggle to comment mode + returnType = COMMENT; + } else if (stream.eat(/[tf]/i)) { // #t/#f (atom) + returnType = ATOM; + } else if (stream.eat(';')) { // S-Expr comment + state.mode = "s-expr-comment"; + returnType = COMMENT; + } else { + var numTest = null, hasExactness = false, hasRadix = true; + if (stream.eat(/[ei]/i)) { + hasExactness = true; + } else { + stream.backUp(1); // must be radix specifier + } + if (stream.match(/^#b/i)) { + numTest = isBinaryNumber; + } else if (stream.match(/^#o/i)) { + numTest = isOctalNumber; + } else if (stream.match(/^#x/i)) { + numTest = isHexNumber; + } else if (stream.match(/^#d/i)) { + numTest = isDecimalNumber; + } else if (stream.match(/^[-+0-9.]/, false)) { + hasRadix = false; + numTest = isDecimalNumber; + // re-consume the intial # if all matches failed + } else if (!hasExactness) { + stream.eat('#'); + } + if (numTest != null) { + if (hasRadix && !hasExactness) { + // consume optional exactness after radix + stream.match(/^#[ei]/i); + } + if (numTest(stream)) + returnType = NUMBER; + } + } + } else if (/^[-+0-9.]/.test(ch) && isDecimalNumber(stream, true)) { // match non-prefixed number, must be decimal + returnType = NUMBER; + } else if (ch == ";") { // comment + stream.skipToEnd(); // rest of the line is a comment + returnType = COMMENT; + } else if (ch == "(" || ch == "[") { + var keyWord = ''; var indentTemp = stream.column(), letter; + /** + Either + (indent-word .. + (non-indent-word .. + (;something else, bracket, etc. + */ + + while ((letter = stream.eat(/[^\s\(\[\;\)\]]/)) != null) { + keyWord += letter; + } + + if (keyWord.length > 0 && indentKeys.propertyIsEnumerable(keyWord)) { // indent-word + + pushStack(state, indentTemp + INDENT_WORD_SKIP, ch); + } else { // non-indent word + // we continue eating the spaces + stream.eatSpace(); + if (stream.eol() || stream.peek() == ";") { + // nothing significant after + // we restart indentation 1 space after + pushStack(state, indentTemp + 1, ch); + } else { + pushStack(state, indentTemp + stream.current().length, ch); // else we match + } + } + stream.backUp(stream.current().length - 1); // undo all the eating + + if(typeof state.sExprComment == "number") state.sExprComment++; + + returnType = BRACKET; + } else if (ch == ")" || ch == "]") { + returnType = BRACKET; + if (state.indentStack != null && state.indentStack.type == (ch == ")" ? "(" : "[")) { + popStack(state); + + if(typeof state.sExprComment == "number"){ + if(--state.sExprComment == 0){ + returnType = COMMENT; // final closing bracket + state.sExprComment = false; // turn off s-expr commenting mode + } + } + } + } else { + stream.eatWhile(/[\w_\-!$%&*+\.\/:<=>?@\^~]/); + + if (keywords && keywords.propertyIsEnumerable(stream.current())) { + returnType = BUILTIN; + } else returnType = "variable"; + } + } + return (typeof state.sExprComment == "number") ? COMMENT : returnType; + }, + + indent: function (state) { + if (state.indentStack == null) return state.indentation; + return state.indentStack.indent; + }, + + closeBrackets: {pairs: "()[]{}\"\""}, + lineComment: ";;" + }; +}); + +CodeMirror.defineMIME("text/x-scheme", "scheme"); + +}); diff --git a/modules/editor/codemirror/mode/sieve/sieve.min.js b/modules/editor/codemirror/mode/sieve/sieve.min.js @@ -0,0 +1,193 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("sieve", function(config) { + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + var keywords = words("if elsif else stop require"); + var atoms = words("true false not"); + var indentUnit = config.indentUnit; + + function tokenBase(stream, state) { + + var ch = stream.next(); + if (ch == "/" && stream.eat("*")) { + state.tokenize = tokenCComment; + return tokenCComment(stream, state); + } + + if (ch === '#') { + stream.skipToEnd(); + return "comment"; + } + + if (ch == "\"") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + + if (ch == "(") { + state._indent.push("("); + // add virtual angel wings so that editor behaves... + // ...more sane incase of broken brackets + state._indent.push("{"); + return null; + } + + if (ch === "{") { + state._indent.push("{"); + return null; + } + + if (ch == ")") { + state._indent.pop(); + state._indent.pop(); + } + + if (ch === "}") { + state._indent.pop(); + return null; + } + + if (ch == ",") + return null; + + if (ch == ";") + return null; + + + if (/[{}\(\),;]/.test(ch)) + return null; + + // 1*DIGIT "K" / "M" / "G" + if (/\d/.test(ch)) { + stream.eatWhile(/[\d]/); + stream.eat(/[KkMmGg]/); + return "number"; + } + + // ":" (ALPHA / "_") *(ALPHA / DIGIT / "_") + if (ch == ":") { + stream.eatWhile(/[a-zA-Z_]/); + stream.eatWhile(/[a-zA-Z0-9_]/); + + return "operator"; + } + + stream.eatWhile(/\w/); + var cur = stream.current(); + + // "text:" *(SP / HTAB) (hash-comment / CRLF) + // *(multiline-literal / multiline-dotstart) + // "." CRLF + if ((cur == "text") && stream.eat(":")) + { + state.tokenize = tokenMultiLineString; + return "string"; + } + + if (keywords.propertyIsEnumerable(cur)) + return "keyword"; + + if (atoms.propertyIsEnumerable(cur)) + return "atom"; + + return null; + } + + function tokenMultiLineString(stream, state) + { + state._multiLineString = true; + // the first line is special it may contain a comment + if (!stream.sol()) { + stream.eatSpace(); + + if (stream.peek() == "#") { + stream.skipToEnd(); + return "comment"; + } + + stream.skipToEnd(); + return "string"; + } + + if ((stream.next() == ".") && (stream.eol())) + { + state._multiLineString = false; + state.tokenize = tokenBase; + } + + return "string"; + } + + function tokenCComment(stream, state) { + var maybeEnd = false, ch; + while ((ch = stream.next()) != null) { + if (maybeEnd && ch == "/") { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) + break; + escaped = !escaped && ch == "\\"; + } + if (!escaped) state.tokenize = tokenBase; + return "string"; + }; + } + + return { + startState: function(base) { + return {tokenize: tokenBase, + baseIndent: base || 0, + _indent: []}; + }, + + token: function(stream, state) { + if (stream.eatSpace()) + return null; + + return (state.tokenize || tokenBase)(stream, state); + }, + + indent: function(state, _textAfter) { + var length = state._indent.length; + if (_textAfter && (_textAfter[0] == "}")) + length--; + + if (length <0) + length = 0; + + return length * indentUnit; + }, + + electricChars: "}" + }; +}); + +CodeMirror.defineMIME("application/sieve", "sieve"); + +}); diff --git a/modules/editor/codemirror/mode/smalltalk/smalltalk.min.js b/modules/editor/codemirror/mode/smalltalk/smalltalk.min.js @@ -0,0 +1,168 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode('smalltalk', function(config) { + + var specialChars = /[+\-\/\\*~<>=@%|&?!.,:;^]/; + var keywords = /true|false|nil|self|super|thisContext/; + + var Context = function(tokenizer, parent) { + this.next = tokenizer; + this.parent = parent; + }; + + var Token = function(name, context, eos) { + this.name = name; + this.context = context; + this.eos = eos; + }; + + var State = function() { + this.context = new Context(next, null); + this.expectVariable = true; + this.indentation = 0; + this.userIndentationDelta = 0; + }; + + State.prototype.userIndent = function(indentation) { + this.userIndentationDelta = indentation > 0 ? (indentation / config.indentUnit - this.indentation) : 0; + }; + + var next = function(stream, context, state) { + var token = new Token(null, context, false); + var aChar = stream.next(); + + if (aChar === '"') { + token = nextComment(stream, new Context(nextComment, context)); + + } else if (aChar === '\'') { + token = nextString(stream, new Context(nextString, context)); + + } else if (aChar === '#') { + if (stream.peek() === '\'') { + stream.next(); + token = nextSymbol(stream, new Context(nextSymbol, context)); + } else { + if (stream.eatWhile(/[^\s.{}\[\]()]/)) + token.name = 'string-2'; + else + token.name = 'meta'; + } + + } else if (aChar === '$') { + if (stream.next() === '<') { + stream.eatWhile(/[^\s>]/); + stream.next(); + } + token.name = 'string-2'; + + } else if (aChar === '|' && state.expectVariable) { + token.context = new Context(nextTemporaries, context); + + } else if (/[\[\]{}()]/.test(aChar)) { + token.name = 'bracket'; + token.eos = /[\[{(]/.test(aChar); + + if (aChar === '[') { + state.indentation++; + } else if (aChar === ']') { + state.indentation = Math.max(0, state.indentation - 1); + } + + } else if (specialChars.test(aChar)) { + stream.eatWhile(specialChars); + token.name = 'operator'; + token.eos = aChar !== ';'; // ; cascaded message expression + + } else if (/\d/.test(aChar)) { + stream.eatWhile(/[\w\d]/); + token.name = 'number'; + + } else if (/[\w_]/.test(aChar)) { + stream.eatWhile(/[\w\d_]/); + token.name = state.expectVariable ? (keywords.test(stream.current()) ? 'keyword' : 'variable') : null; + + } else { + token.eos = state.expectVariable; + } + + return token; + }; + + var nextComment = function(stream, context) { + stream.eatWhile(/[^"]/); + return new Token('comment', stream.eat('"') ? context.parent : context, true); + }; + + var nextString = function(stream, context) { + stream.eatWhile(/[^']/); + return new Token('string', stream.eat('\'') ? context.parent : context, false); + }; + + var nextSymbol = function(stream, context) { + stream.eatWhile(/[^']/); + return new Token('string-2', stream.eat('\'') ? context.parent : context, false); + }; + + var nextTemporaries = function(stream, context) { + var token = new Token(null, context, false); + var aChar = stream.next(); + + if (aChar === '|') { + token.context = context.parent; + token.eos = true; + + } else { + stream.eatWhile(/[^|]/); + token.name = 'variable'; + } + + return token; + }; + + return { + startState: function() { + return new State; + }, + + token: function(stream, state) { + state.userIndent(stream.indentation()); + + if (stream.eatSpace()) { + return null; + } + + var token = state.context.next(stream, state.context, state); + state.context = token.context; + state.expectVariable = token.eos; + + return token.name; + }, + + blankLine: function(state) { + state.userIndent(0); + }, + + indent: function(state, textAfter) { + var i = state.context.next === next && textAfter && textAfter.charAt(0) === ']' ? -1 : state.userIndentationDelta; + return (state.indentation + i) * config.indentUnit; + }, + + electricChars: ']' + }; + +}); + +CodeMirror.defineMIME('text/x-stsrc', {name: 'smalltalk'}); + +}); diff --git a/modules/editor/codemirror/mode/sparql/sparql.min.js b/modules/editor/codemirror/mode/sparql/sparql.min.js @@ -0,0 +1,180 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("sparql", function(config) { + var indentUnit = config.indentUnit; + var curPunc; + + function wordRegexp(words) { + return new RegExp("^(?:" + words.join("|") + ")$", "i"); + } + var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri", + "iri", "uri", "bnode", "count", "sum", "min", "max", "avg", "sample", + "group_concat", "rand", "abs", "ceil", "floor", "round", "concat", "substr", "strlen", + "replace", "ucase", "lcase", "encode_for_uri", "contains", "strstarts", "strends", + "strbefore", "strafter", "year", "month", "day", "hours", "minutes", "seconds", + "timezone", "tz", "now", "uuid", "struuid", "md5", "sha1", "sha256", "sha384", + "sha512", "coalesce", "if", "strlang", "strdt", "isnumeric", "regex", "exists", + "isblank", "isliteral", "a", "bind"]); + var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe", + "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional", + "graph", "by", "asc", "desc", "as", "having", "undef", "values", "group", + "minus", "in", "not", "service", "silent", "using", "insert", "delete", "union", + "true", "false", "with", + "data", "copy", "to", "move", "add", "create", "drop", "clear", "load"]); + var operatorChars = /[*+\-<>=&|\^\/!\?]/; + + function tokenBase(stream, state) { + var ch = stream.next(); + curPunc = null; + if (ch == "$" || ch == "?") { + if(ch == "?" && stream.match(/\s/, false)){ + return "operator"; + } + stream.match(/^[\w\d]*/); + return "variable-2"; + } + else if (ch == "<" && !stream.match(/^[\s\u00a0=]/, false)) { + stream.match(/^[^\s\u00a0>]*>?/); + return "atom"; + } + else if (ch == "\"" || ch == "'") { + state.tokenize = tokenLiteral(ch); + return state.tokenize(stream, state); + } + else if (/[{}\(\),\.;\[\]]/.test(ch)) { + curPunc = ch; + return "bracket"; + } + else if (ch == "#") { + stream.skipToEnd(); + return "comment"; + } + else if (operatorChars.test(ch)) { + stream.eatWhile(operatorChars); + return "operator"; + } + else if (ch == ":") { + stream.eatWhile(/[\w\d\._\-]/); + return "atom"; + } + else if (ch == "@") { + stream.eatWhile(/[a-z\d\-]/i); + return "meta"; + } + else { + stream.eatWhile(/[_\w\d]/); + if (stream.eat(":")) { + stream.eatWhile(/[\w\d_\-]/); + return "atom"; + } + var word = stream.current(); + if (ops.test(word)) + return "builtin"; + else if (keywords.test(word)) + return "keyword"; + else + return "variable"; + } + } + + function tokenLiteral(quote) { + return function(stream, state) { + var escaped = false, ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) { + state.tokenize = tokenBase; + break; + } + escaped = !escaped && ch == "\\"; + } + return "string"; + }; + } + + function pushContext(state, type, col) { + state.context = {prev: state.context, indent: state.indent, col: col, type: type}; + } + function popContext(state) { + state.indent = state.context.indent; + state.context = state.context.prev; + } + + return { + startState: function() { + return {tokenize: tokenBase, + context: null, + indent: 0, + col: 0}; + }, + + token: function(stream, state) { + if (stream.sol()) { + if (state.context && state.context.align == null) state.context.align = false; + state.indent = stream.indentation(); + } + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + + if (style != "comment" && state.context && state.context.align == null && state.context.type != "pattern") { + state.context.align = true; + } + + if (curPunc == "(") pushContext(state, ")", stream.column()); + else if (curPunc == "[") pushContext(state, "]", stream.column()); + else if (curPunc == "{") pushContext(state, "}", stream.column()); + else if (/[\]\}\)]/.test(curPunc)) { + while (state.context && state.context.type == "pattern") popContext(state); + if (state.context && curPunc == state.context.type) { + popContext(state); + if (curPunc == "}" && state.context && state.context.type == "pattern") + popContext(state); + } + } + else if (curPunc == "." && state.context && state.context.type == "pattern") popContext(state); + else if (/atom|string|variable/.test(style) && state.context) { + if (/[\}\]]/.test(state.context.type)) + pushContext(state, "pattern", stream.column()); + else if (state.context.type == "pattern" && !state.context.align) { + state.context.align = true; + state.context.col = stream.column(); + } + } + + return style; + }, + + indent: function(state, textAfter) { + var firstChar = textAfter && textAfter.charAt(0); + var context = state.context; + if (/[\]\}]/.test(firstChar)) + while (context && context.type == "pattern") context = context.prev; + + var closing = context && firstChar == context.type; + if (!context) + return 0; + else if (context.type == "pattern") + return context.col; + else if (context.align) + return context.col + (closing ? 0 : 1); + else + return context.indent + (closing ? 0 : indentUnit); + }, + + lineComment: "#" + }; +}); + +CodeMirror.defineMIME("application/sparql-query", "sparql"); + +}); diff --git a/modules/editor/codemirror/mode/stex/stex.min.js b/modules/editor/codemirror/mode/stex/stex.min.js @@ -0,0 +1,251 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +/* + * Author: Constantin Jucovschi (c.jucovschi@jacobs-university.de) + * Licence: MIT + */ + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("stex", function() { + "use strict"; + + function pushCommand(state, command) { + state.cmdState.push(command); + } + + function peekCommand(state) { + if (state.cmdState.length > 0) { + return state.cmdState[state.cmdState.length - 1]; + } else { + return null; + } + } + + function popCommand(state) { + var plug = state.cmdState.pop(); + if (plug) { + plug.closeBracket(); + } + } + + // returns the non-default plugin closest to the end of the list + function getMostPowerful(state) { + var context = state.cmdState; + for (var i = context.length - 1; i >= 0; i--) { + var plug = context[i]; + if (plug.name == "DEFAULT") { + continue; + } + return plug; + } + return { styleIdentifier: function() { return null; } }; + } + + function addPluginPattern(pluginName, cmdStyle, styles) { + return function () { + this.name = pluginName; + this.bracketNo = 0; + this.style = cmdStyle; + this.styles = styles; + this.argument = null; // \begin and \end have arguments that follow. These are stored in the plugin + + this.styleIdentifier = function() { + return this.styles[this.bracketNo - 1] || null; + }; + this.openBracket = function() { + this.bracketNo++; + return "bracket"; + }; + this.closeBracket = function() {}; + }; + } + + var plugins = {}; + + plugins["importmodule"] = addPluginPattern("importmodule", "tag", ["string", "builtin"]); + plugins["documentclass"] = addPluginPattern("documentclass", "tag", ["", "atom"]); + plugins["usepackage"] = addPluginPattern("usepackage", "tag", ["atom"]); + plugins["begin"] = addPluginPattern("begin", "tag", ["atom"]); + plugins["end"] = addPluginPattern("end", "tag", ["atom"]); + + plugins["DEFAULT"] = function () { + this.name = "DEFAULT"; + this.style = "tag"; + + this.styleIdentifier = this.openBracket = this.closeBracket = function() {}; + }; + + function setState(state, f) { + state.f = f; + } + + // called when in a normal (no environment) context + function normal(source, state) { + var plug; + // Do we look like '\command' ? If so, attempt to apply the plugin 'command' + if (source.match(/^\\[a-zA-Z@]+/)) { + var cmdName = source.current().slice(1); + plug = plugins[cmdName] || plugins["DEFAULT"]; + plug = new plug(); + pushCommand(state, plug); + setState(state, beginParams); + return plug.style; + } + + // escape characters + if (source.match(/^\\[$&%#{}_]/)) { + return "tag"; + } + + // white space control characters + if (source.match(/^\\[,;!\/\\]/)) { + return "tag"; + } + + // find if we're starting various math modes + if (source.match("\\[")) { + setState(state, function(source, state){ return inMathMode(source, state, "\\]"); }); + return "keyword"; + } + if (source.match("$$")) { + setState(state, function(source, state){ return inMathMode(source, state, "$$"); }); + return "keyword"; + } + if (source.match("$")) { + setState(state, function(source, state){ return inMathMode(source, state, "$"); }); + return "keyword"; + } + + var ch = source.next(); + if (ch == "%") { + source.skipToEnd(); + return "comment"; + } else if (ch == '}' || ch == ']') { + plug = peekCommand(state); + if (plug) { + plug.closeBracket(ch); + setState(state, beginParams); + } else { + return "error"; + } + return "bracket"; + } else if (ch == '{' || ch == '[') { + plug = plugins["DEFAULT"]; + plug = new plug(); + pushCommand(state, plug); + return "bracket"; + } else if (/\d/.test(ch)) { + source.eatWhile(/[\w.%]/); + return "atom"; + } else { + source.eatWhile(/[\w\-_]/); + plug = getMostPowerful(state); + if (plug.name == 'begin') { + plug.argument = source.current(); + } + return plug.styleIdentifier(); + } + } + + function inMathMode(source, state, endModeSeq) { + if (source.eatSpace()) { + return null; + } + if (source.match(endModeSeq)) { + setState(state, normal); + return "keyword"; + } + if (source.match(/^\\[a-zA-Z@]+/)) { + return "tag"; + } + if (source.match(/^[a-zA-Z]+/)) { + return "variable-2"; + } + // escape characters + if (source.match(/^\\[$&%#{}_]/)) { + return "tag"; + } + // white space control characters + if (source.match(/^\\[,;!\/]/)) { + return "tag"; + } + // special math-mode characters + if (source.match(/^[\^_&]/)) { + return "tag"; + } + // non-special characters + if (source.match(/^[+\-<>|=,\/@!*:;'"`~#?]/)) { + return null; + } + if (source.match(/^(\d+\.\d*|\d*\.\d+|\d+)/)) { + return "number"; + } + var ch = source.next(); + if (ch == "{" || ch == "}" || ch == "[" || ch == "]" || ch == "(" || ch == ")") { + return "bracket"; + } + + if (ch == "%") { + source.skipToEnd(); + return "comment"; + } + return "error"; + } + + function beginParams(source, state) { + var ch = source.peek(), lastPlug; + if (ch == '{' || ch == '[') { + lastPlug = peekCommand(state); + lastPlug.openBracket(ch); + source.eat(ch); + setState(state, normal); + return "bracket"; + } + if (/[ \t\r]/.test(ch)) { + source.eat(ch); + return null; + } + setState(state, normal); + popCommand(state); + + return normal(source, state); + } + + return { + startState: function() { + return { + cmdState: [], + f: normal + }; + }, + copyState: function(s) { + return { + cmdState: s.cmdState.slice(), + f: s.f + }; + }, + token: function(stream, state) { + return state.f(stream, state); + }, + blankLine: function(state) { + state.f = normal; + state.cmdState.length = 0; + }, + lineComment: "%" + }; + }); + + CodeMirror.defineMIME("text/x-stex", "stex"); + CodeMirror.defineMIME("text/x-latex", "stex"); + +}); diff --git a/modules/editor/codemirror/mode/ttcn/ttcn.min.js b/modules/editor/codemirror/mode/ttcn/ttcn.min.js @@ -0,0 +1,283 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("ttcn", function(config, parserConfig) { + var indentUnit = config.indentUnit, + keywords = parserConfig.keywords || {}, + builtin = parserConfig.builtin || {}, + timerOps = parserConfig.timerOps || {}, + portOps = parserConfig.portOps || {}, + configOps = parserConfig.configOps || {}, + verdictOps = parserConfig.verdictOps || {}, + sutOps = parserConfig.sutOps || {}, + functionOps = parserConfig.functionOps || {}, + + verdictConsts = parserConfig.verdictConsts || {}, + booleanConsts = parserConfig.booleanConsts || {}, + otherConsts = parserConfig.otherConsts || {}, + + types = parserConfig.types || {}, + visibilityModifiers = parserConfig.visibilityModifiers || {}, + templateMatch = parserConfig.templateMatch || {}, + multiLineStrings = parserConfig.multiLineStrings, + indentStatements = parserConfig.indentStatements !== false; + var isOperatorChar = /[+\-*&@=<>!\/]/; + var curPunc; + + function tokenBase(stream, state) { + var ch = stream.next(); + + if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + if (/[\[\]{}\(\),;\\:\?\.]/.test(ch)) { + curPunc = ch; + return "punctuation"; + } + if (ch == "#"){ + stream.skipToEnd(); + return "atom preprocessor"; + } + if (ch == "%"){ + stream.eatWhile(/\b/); + return "atom ttcn3Macros"; + } + if (/\d/.test(ch)) { + stream.eatWhile(/[\w\.]/); + return "number"; + } + if (ch == "/") { + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + } + if (isOperatorChar.test(ch)) { + if(ch == "@"){ + if(stream.match("try") || stream.match("catch") + || stream.match("lazy")){ + return "keyword"; + } + } + stream.eatWhile(isOperatorChar); + return "operator"; + } + stream.eatWhile(/[\w\$_\xa1-\uffff]/); + var cur = stream.current(); + + if (keywords.propertyIsEnumerable(cur)) return "keyword"; + if (builtin.propertyIsEnumerable(cur)) return "builtin"; + + if (timerOps.propertyIsEnumerable(cur)) return "def timerOps"; + if (configOps.propertyIsEnumerable(cur)) return "def configOps"; + if (verdictOps.propertyIsEnumerable(cur)) return "def verdictOps"; + if (portOps.propertyIsEnumerable(cur)) return "def portOps"; + if (sutOps.propertyIsEnumerable(cur)) return "def sutOps"; + if (functionOps.propertyIsEnumerable(cur)) return "def functionOps"; + + if (verdictConsts.propertyIsEnumerable(cur)) return "string verdictConsts"; + if (booleanConsts.propertyIsEnumerable(cur)) return "string booleanConsts"; + if (otherConsts.propertyIsEnumerable(cur)) return "string otherConsts"; + + if (types.propertyIsEnumerable(cur)) return "builtin types"; + if (visibilityModifiers.propertyIsEnumerable(cur)) + return "builtin visibilityModifiers"; + if (templateMatch.propertyIsEnumerable(cur)) return "atom templateMatch"; + + return "variable"; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped){ + var afterQuote = stream.peek(); + //look if the character after the quote is like the B in '10100010'B + if (afterQuote){ + afterQuote = afterQuote.toLowerCase(); + if(afterQuote == "b" || afterQuote == "h" || afterQuote == "o") + stream.next(); + } + end = true; break; + } + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = null; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = null; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + + function pushContext(state, col, type) { + var indent = state.indented; + if (state.context && state.context.type == "statement") + indent = state.context.indented; + return state.context = new Context(indent, col, type, null, state.context); + } + + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") + state.indented = state.context.indented; + return state.context = state.context.prev; + } + + //Interface + return { + startState: function(basecolumn) { + return { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (stream.eatSpace()) return null; + curPunc = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment") return style; + if (ctx.align == null) ctx.align = true; + + if ((curPunc == ";" || curPunc == ":" || curPunc == ",") + && ctx.type == "statement"){ + popContext(state); + } + else if (curPunc == "{") pushContext(state, stream.column(), "}"); + else if (curPunc == "[") pushContext(state, stream.column(), "]"); + else if (curPunc == "(") pushContext(state, stream.column(), ")"); + else if (curPunc == "}") { + while (ctx.type == "statement") ctx = popContext(state); + if (ctx.type == "}") ctx = popContext(state); + while (ctx.type == "statement") ctx = popContext(state); + } + else if (curPunc == ctx.type) popContext(state); + else if (indentStatements && + (((ctx.type == "}" || ctx.type == "top") && curPunc != ';') || + (ctx.type == "statement" && curPunc == "newstatement"))) + pushContext(state, stream.column(), "statement"); + + state.startOfLine = false; + + return style; + }, + + electricChars: "{}", + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//", + fold: "brace" + }; + }); + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + function def(mimes, mode) { + if (typeof mimes == "string") mimes = [mimes]; + var words = []; + function add(obj) { + if (obj) for (var prop in obj) if (obj.hasOwnProperty(prop)) + words.push(prop); + } + + add(mode.keywords); + add(mode.builtin); + add(mode.timerOps); + add(mode.portOps); + + if (words.length) { + mode.helperType = mimes[0]; + CodeMirror.registerHelper("hintWords", mimes[0], words); + } + + for (var i = 0; i < mimes.length; ++i) + CodeMirror.defineMIME(mimes[i], mode); + } + + def(["text/x-ttcn", "text/x-ttcn3", "text/x-ttcnpp"], { + name: "ttcn", + keywords: words("activate address alive all alt altstep and and4b any" + + " break case component const continue control deactivate" + + " display do else encode enumerated except exception" + + " execute extends extension external for from function" + + " goto group if import in infinity inout interleave" + + " label language length log match message mixed mod" + + " modifies module modulepar mtc noblock not not4b nowait" + + " of on optional or or4b out override param pattern port" + + " procedure record recursive rem repeat return runs select" + + " self sender set signature system template testcase to" + + " type union value valueof var variant while with xor xor4b"), + builtin: words("bit2hex bit2int bit2oct bit2str char2int char2oct encvalue" + + " decomp decvalue float2int float2str hex2bit hex2int" + + " hex2oct hex2str int2bit int2char int2float int2hex" + + " int2oct int2str int2unichar isbound ischosen ispresent" + + " isvalue lengthof log2str oct2bit oct2char oct2hex oct2int" + + " oct2str regexp replace rnd sizeof str2bit str2float" + + " str2hex str2int str2oct substr unichar2int unichar2char" + + " enum2int"), + types: words("anytype bitstring boolean char charstring default float" + + " hexstring integer objid octetstring universal verdicttype timer"), + timerOps: words("read running start stop timeout"), + portOps: words("call catch check clear getcall getreply halt raise receive" + + " reply send trigger"), + configOps: words("create connect disconnect done kill killed map unmap"), + verdictOps: words("getverdict setverdict"), + sutOps: words("action"), + functionOps: words("apply derefers refers"), + + verdictConsts: words("error fail inconc none pass"), + booleanConsts: words("true false"), + otherConsts: words("null NULL omit"), + + visibilityModifiers: words("private public friend"), + templateMatch: words("complement ifpresent subset superset permutation"), + multiLineStrings: true + }); +}); diff --git a/modules/editor/codemirror/mode/twig/twig.min.js b/modules/editor/codemirror/mode/twig/twig.min.js @@ -0,0 +1,141 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../../addon/mode/multiplex")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../../addon/mode/multiplex"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { + "use strict"; + + CodeMirror.defineMode("twig:inner", function() { + var keywords = ["and", "as", "autoescape", "endautoescape", "block", "do", "endblock", "else", "elseif", "extends", "for", "endfor", "embed", "endembed", "filter", "endfilter", "flush", "from", "if", "endif", "in", "is", "include", "import", "not", "or", "set", "spaceless", "endspaceless", "with", "endwith", "trans", "endtrans", "blocktrans", "endblocktrans", "macro", "endmacro", "use", "verbatim", "endverbatim"], + operator = /^[+\-*&%=<>!?|~^]/, + sign = /^[:\[\(\{]/, + atom = ["true", "false", "null", "empty", "defined", "divisibleby", "divisible by", "even", "odd", "iterable", "sameas", "same as"], + number = /^(\d[+\-\*\/])?\d+(\.\d+)?/; + + keywords = new RegExp("((" + keywords.join(")|(") + "))\\b"); + atom = new RegExp("((" + atom.join(")|(") + "))\\b"); + + function tokenBase (stream, state) { + var ch = stream.peek(); + + //Comment + if (state.incomment) { + if (!stream.skipTo("#}")) { + stream.skipToEnd(); + } else { + stream.eatWhile(/\#|}/); + state.incomment = false; + } + return "comment"; + //Tag + } else if (state.intag) { + //After operator + if (state.operator) { + state.operator = false; + if (stream.match(atom)) { + return "atom"; + } + if (stream.match(number)) { + return "number"; + } + } + //After sign + if (state.sign) { + state.sign = false; + if (stream.match(atom)) { + return "atom"; + } + if (stream.match(number)) { + return "number"; + } + } + + if (state.instring) { + if (ch == state.instring) { + state.instring = false; + } + stream.next(); + return "string"; + } else if (ch == "'" || ch == '"') { + state.instring = ch; + stream.next(); + return "string"; + } else if (stream.match(state.intag + "}") || stream.eat("-") && stream.match(state.intag + "}")) { + state.intag = false; + return "tag"; + } else if (stream.match(operator)) { + state.operator = true; + return "operator"; + } else if (stream.match(sign)) { + state.sign = true; + } else { + if (stream.eat(" ") || stream.sol()) { + if (stream.match(keywords)) { + return "keyword"; + } + if (stream.match(atom)) { + return "atom"; + } + if (stream.match(number)) { + return "number"; + } + if (stream.sol()) { + stream.next(); + } + } else { + stream.next(); + } + + } + return "variable"; + } else if (stream.eat("{")) { + if (stream.eat("#")) { + state.incomment = true; + if (!stream.skipTo("#}")) { + stream.skipToEnd(); + } else { + stream.eatWhile(/\#|}/); + state.incomment = false; + } + return "comment"; + //Open tag + } else if (ch = stream.eat(/\{|%/)) { + //Cache close tag + state.intag = ch; + if (ch == "{") { + state.intag = "}"; + } + stream.eat("-"); + return "tag"; + } + } + stream.next(); + }; + + return { + startState: function () { + return {}; + }, + token: function (stream, state) { + return tokenBase(stream, state); + } + }; + }); + + CodeMirror.defineMode("twig", function(config, parserConfig) { + var twigInner = CodeMirror.getMode(config, "twig:inner"); + if (!parserConfig || !parserConfig.base) return twigInner; + return CodeMirror.multiplexingMode( + CodeMirror.getMode(config, parserConfig.base), { + open: /\{[{#%]/, close: /[}#%]\}/, mode: twigInner, parseDelimiters: true + } + ); + }); + CodeMirror.defineMIME("text/x-twig", "twig"); +}); diff --git a/modules/editor/codemirror/mode/verilog/verilog.min.js b/modules/editor/codemirror/mode/verilog/verilog.min.js @@ -0,0 +1,675 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("verilog", function(config, parserConfig) { + + var indentUnit = config.indentUnit, + statementIndentUnit = parserConfig.statementIndentUnit || indentUnit, + dontAlignCalls = parserConfig.dontAlignCalls, + noIndentKeywords = parserConfig.noIndentKeywords || [], + multiLineStrings = parserConfig.multiLineStrings, + hooks = parserConfig.hooks || {}; + + function words(str) { + var obj = {}, words = str.split(" "); + for (var i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; + } + + /** + * Keywords from IEEE 1800-2012 + */ + var keywords = words( + "accept_on alias always always_comb always_ff always_latch and assert assign assume automatic before begin bind " + + "bins binsof bit break buf bufif0 bufif1 byte case casex casez cell chandle checker class clocking cmos config " + + "const constraint context continue cover covergroup coverpoint cross deassign default defparam design disable " + + "dist do edge else end endcase endchecker endclass endclocking endconfig endfunction endgenerate endgroup " + + "endinterface endmodule endpackage endprimitive endprogram endproperty endspecify endsequence endtable endtask " + + "enum event eventually expect export extends extern final first_match for force foreach forever fork forkjoin " + + "function generate genvar global highz0 highz1 if iff ifnone ignore_bins illegal_bins implements implies import " + + "incdir include initial inout input inside instance int integer interconnect interface intersect join join_any " + + "join_none large let liblist library local localparam logic longint macromodule matches medium modport module " + + "nand negedge nettype new nexttime nmos nor noshowcancelled not notif0 notif1 null or output package packed " + + "parameter pmos posedge primitive priority program property protected pull0 pull1 pulldown pullup " + + "pulsestyle_ondetect pulsestyle_onevent pure rand randc randcase randsequence rcmos real realtime ref reg " + + "reject_on release repeat restrict return rnmos rpmos rtran rtranif0 rtranif1 s_always s_eventually s_nexttime " + + "s_until s_until_with scalared sequence shortint shortreal showcancelled signed small soft solve specify " + + "specparam static string strong strong0 strong1 struct super supply0 supply1 sync_accept_on sync_reject_on " + + "table tagged task this throughout time timeprecision timeunit tran tranif0 tranif1 tri tri0 tri1 triand trior " + + "trireg type typedef union unique unique0 unsigned until until_with untyped use uwire var vectored virtual void " + + "wait wait_order wand weak weak0 weak1 while wildcard wire with within wor xnor xor"); + + /** Operators from IEEE 1800-2012 + unary_operator ::= + + | - | ! | ~ | & | ~& | | | ~| | ^ | ~^ | ^~ + binary_operator ::= + + | - | * | / | % | == | != | === | !== | ==? | !=? | && | || | ** + | < | <= | > | >= | & | | | ^ | ^~ | ~^ | >> | << | >>> | <<< + | -> | <-> + inc_or_dec_operator ::= ++ | -- + unary_module_path_operator ::= + ! | ~ | & | ~& | | | ~| | ^ | ~^ | ^~ + binary_module_path_operator ::= + == | != | && | || | & | | | ^ | ^~ | ~^ + */ + var isOperatorChar = /[\+\-\*\/!~&|^%=?:]/; + var isBracketChar = /[\[\]{}()]/; + + var unsignedNumber = /\d[0-9_]*/; + var decimalLiteral = /\d*\s*'s?d\s*\d[0-9_]*/i; + var binaryLiteral = /\d*\s*'s?b\s*[xz01][xz01_]*/i; + var octLiteral = /\d*\s*'s?o\s*[xz0-7][xz0-7_]*/i; + var hexLiteral = /\d*\s*'s?h\s*[0-9a-fxz?][0-9a-fxz?_]*/i; + var realLiteral = /(\d[\d_]*(\.\d[\d_]*)?E-?[\d_]+)|(\d[\d_]*\.\d[\d_]*)/i; + + var closingBracketOrWord = /^((\w+)|[)}\]])/; + var closingBracket = /[)}\]]/; + + var curPunc; + var curKeyword; + + // Block openings which are closed by a matching keyword in the form of ("end" + keyword) + // E.g. "task" => "endtask" + var blockKeywords = words( + "case checker class clocking config function generate interface module package " + + "primitive program property specify sequence table task" + ); + + // Opening/closing pairs + var openClose = {}; + for (var keyword in blockKeywords) { + openClose[keyword] = "end" + keyword; + } + openClose["begin"] = "end"; + openClose["casex"] = "endcase"; + openClose["casez"] = "endcase"; + openClose["do" ] = "while"; + openClose["fork" ] = "join;join_any;join_none"; + openClose["covergroup"] = "endgroup"; + + for (var i in noIndentKeywords) { + var keyword = noIndentKeywords[i]; + if (openClose[keyword]) { + openClose[keyword] = undefined; + } + } + + // Keywords which open statements that are ended with a semi-colon + var statementKeywords = words("always always_comb always_ff always_latch assert assign assume else export for foreach forever if import initial repeat while"); + + function tokenBase(stream, state) { + var ch = stream.peek(), style; + if (hooks[ch] && (style = hooks[ch](stream, state)) != false) return style; + if (hooks.tokenBase && (style = hooks.tokenBase(stream, state)) != false) + return style; + + if (/[,;:\.]/.test(ch)) { + curPunc = stream.next(); + return null; + } + if (isBracketChar.test(ch)) { + curPunc = stream.next(); + return "bracket"; + } + // Macros (tick-defines) + if (ch == '`') { + stream.next(); + if (stream.eatWhile(/[\w\$_]/)) { + return "def"; + } else { + return null; + } + } + // System calls + if (ch == '$') { + stream.next(); + if (stream.eatWhile(/[\w\$_]/)) { + return "meta"; + } else { + return null; + } + } + // Time literals + if (ch == '#') { + stream.next(); + stream.eatWhile(/[\d_.]/); + return "def"; + } + // Strings + if (ch == '"') { + stream.next(); + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } + // Comments + if (ch == "/") { + stream.next(); + if (stream.eat("*")) { + state.tokenize = tokenComment; + return tokenComment(stream, state); + } + if (stream.eat("/")) { + stream.skipToEnd(); + return "comment"; + } + stream.backUp(1); + } + + // Numeric literals + if (stream.match(realLiteral) || + stream.match(decimalLiteral) || + stream.match(binaryLiteral) || + stream.match(octLiteral) || + stream.match(hexLiteral) || + stream.match(unsignedNumber) || + stream.match(realLiteral)) { + return "number"; + } + + // Operators + if (stream.eatWhile(isOperatorChar)) { + return "meta"; + } + + // Keywords / plain variables + if (stream.eatWhile(/[\w\$_]/)) { + var cur = stream.current(); + if (keywords[cur]) { + if (openClose[cur]) { + curPunc = "newblock"; + } + if (statementKeywords[cur]) { + curPunc = "newstatement"; + } + curKeyword = cur; + return "keyword"; + } + return "variable"; + } + + stream.next(); + return null; + } + + function tokenString(quote) { + return function(stream, state) { + var escaped = false, next, end = false; + while ((next = stream.next()) != null) { + if (next == quote && !escaped) {end = true; break;} + escaped = !escaped && next == "\\"; + } + if (end || !(escaped || multiLineStrings)) + state.tokenize = tokenBase; + return "string"; + }; + } + + function tokenComment(stream, state) { + var maybeEnd = false, ch; + while (ch = stream.next()) { + if (ch == "/" && maybeEnd) { + state.tokenize = tokenBase; + break; + } + maybeEnd = (ch == "*"); + } + return "comment"; + } + + function Context(indented, column, type, align, prev) { + this.indented = indented; + this.column = column; + this.type = type; + this.align = align; + this.prev = prev; + } + function pushContext(state, col, type) { + var indent = state.indented; + var c = new Context(indent, col, type, null, state.context); + return state.context = c; + } + function popContext(state) { + var t = state.context.type; + if (t == ")" || t == "]" || t == "}") { + state.indented = state.context.indented; + } + return state.context = state.context.prev; + } + + function isClosing(text, contextClosing) { + if (text == contextClosing) { + return true; + } else { + // contextClosing may be multiple keywords separated by ; + var closingKeywords = contextClosing.split(";"); + for (var i in closingKeywords) { + if (text == closingKeywords[i]) { + return true; + } + } + return false; + } + } + + function buildElectricInputRegEx() { + // Reindentation should occur on any bracket char: {}()[] + // or on a match of any of the block closing keywords, at + // the end of a line + var allClosings = []; + for (var i in openClose) { + if (openClose[i]) { + var closings = openClose[i].split(";"); + for (var j in closings) { + allClosings.push(closings[j]); + } + } + } + var re = new RegExp("[{}()\\[\\]]|(" + allClosings.join("|") + ")$"); + return re; + } + + // Interface + return { + + // Regex to force current line to reindent + electricInput: buildElectricInputRegEx(), + + startState: function(basecolumn) { + var state = { + tokenize: null, + context: new Context((basecolumn || 0) - indentUnit, 0, "top", false), + indented: 0, + startOfLine: true + }; + if (hooks.startState) hooks.startState(state); + return state; + }, + + token: function(stream, state) { + var ctx = state.context; + if (stream.sol()) { + if (ctx.align == null) ctx.align = false; + state.indented = stream.indentation(); + state.startOfLine = true; + } + if (hooks.token) { + // Call hook, with an optional return value of a style to override verilog styling. + var style = hooks.token(stream, state); + if (style !== undefined) { + return style; + } + } + if (stream.eatSpace()) return null; + curPunc = null; + curKeyword = null; + var style = (state.tokenize || tokenBase)(stream, state); + if (style == "comment" || style == "meta" || style == "variable") return style; + if (ctx.align == null) ctx.align = true; + + if (curPunc == ctx.type) { + popContext(state); + } else if ((curPunc == ";" && ctx.type == "statement") || + (ctx.type && isClosing(curKeyword, ctx.type))) { + ctx = popContext(state); + while (ctx && ctx.type == "statement") ctx = popContext(state); + } else if (curPunc == "{") { + pushContext(state, stream.column(), "}"); + } else if (curPunc == "[") { + pushContext(state, stream.column(), "]"); + } else if (curPunc == "(") { + pushContext(state, stream.column(), ")"); + } else if (ctx && ctx.type == "endcase" && curPunc == ":") { + pushContext(state, stream.column(), "statement"); + } else if (curPunc == "newstatement") { + pushContext(state, stream.column(), "statement"); + } else if (curPunc == "newblock") { + if (curKeyword == "function" && ctx && (ctx.type == "statement" || ctx.type == "endgroup")) { + // The 'function' keyword can appear in some other contexts where it actually does not + // indicate a function (import/export DPI and covergroup definitions). + // Do nothing in this case + } else if (curKeyword == "task" && ctx && ctx.type == "statement") { + // Same thing for task + } else { + var close = openClose[curKeyword]; + pushContext(state, stream.column(), close); + } + } + + state.startOfLine = false; + return style; + }, + + indent: function(state, textAfter) { + if (state.tokenize != tokenBase && state.tokenize != null) return CodeMirror.Pass; + if (hooks.indent) { + var fromHook = hooks.indent(state); + if (fromHook >= 0) return fromHook; + } + var ctx = state.context, firstChar = textAfter && textAfter.charAt(0); + if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev; + var closing = false; + var possibleClosing = textAfter.match(closingBracketOrWord); + if (possibleClosing) + closing = isClosing(possibleClosing[0], ctx.type); + if (ctx.type == "statement") return ctx.indented + (firstChar == "{" ? 0 : statementIndentUnit); + else if (closingBracket.test(ctx.type) && ctx.align && !dontAlignCalls) return ctx.column + (closing ? 0 : 1); + else if (ctx.type == ")" && !closing) return ctx.indented + statementIndentUnit; + else return ctx.indented + (closing ? 0 : indentUnit); + }, + + blockCommentStart: "/*", + blockCommentEnd: "*/", + lineComment: "//" + }; +}); + + CodeMirror.defineMIME("text/x-verilog", { + name: "verilog" + }); + + CodeMirror.defineMIME("text/x-systemverilog", { + name: "verilog" + }); + + + + // TL-Verilog mode. + // See tl-x.org for language spec. + // See the mode in action at makerchip.com. + // Contact: steve.hoover@redwoodeda.com + + // TLV Identifier prefixes. + // Note that sign is not treated separately, so "+/-" versions of numeric identifiers + // are included. + var tlvIdentifierStyle = { + "|": "link", + ">": "property", // Should condition this off for > TLV 1c. + "$": "variable", + "$$": "variable", + "?$": "qualifier", + "?*": "qualifier", + "-": "hr", + "/": "property", + "/-": "property", + "@": "variable-3", + "@-": "variable-3", + "@++": "variable-3", + "@+=": "variable-3", + "@+=-": "variable-3", + "@--": "variable-3", + "@-=": "variable-3", + "%+": "tag", + "%-": "tag", + "%": "tag", + ">>": "tag", + "<<": "tag", + "<>": "tag", + "#": "tag", // Need to choose a style for this. + "^": "attribute", + "^^": "attribute", + "^!": "attribute", + "*": "variable-2", + "**": "variable-2", + "\\": "keyword", + "\"": "comment" + }; + + // Lines starting with these characters define scope (result in indentation). + var tlvScopePrefixChars = { + "/": "beh-hier", + ">": "beh-hier", + "-": "phys-hier", + "|": "pipe", + "?": "when", + "@": "stage", + "\\": "keyword" + }; + var tlvIndentUnit = 3; + var tlvTrackStatements = false; + var tlvIdentMatch = /^([~!@#\$%\^&\*-\+=\?\/\\\|'"<>]+)([\d\w_]*)/; // Matches an identifiere. + // Note that ':' is excluded, because of it's use in [:]. + var tlvFirstLevelIndentMatch = /^[! ] /; + var tlvLineIndentationMatch = /^[! ] */; + var tlvCommentMatch = /^\/[\/\*]/; + + + // Returns a style specific to the scope at the given indentation column. + // Type is one of: "indent", "scope-ident", "before-scope-ident". + function tlvScopeStyle(state, indentation, type) { + // Begin scope. + var depth = indentation / tlvIndentUnit; // TODO: Pass this in instead. + return "tlv-" + state.tlvIndentationStyle[depth] + "-" + type; + } + + // Return true if the next thing in the stream is an identifier with a mnemonic. + function tlvIdentNext(stream) { + var match; + return (match = stream.match(tlvIdentMatch, false)) && match[2].length > 0; + } + + CodeMirror.defineMIME("text/x-tlv", { + name: "verilog", + + hooks: { + + electricInput: false, + + + // Return undefined for verilog tokenizing, or style for TLV token (null not used). + // Standard CM styles are used for most formatting, but some TL-Verilog-specific highlighting + // can be enabled with the definition of cm-tlv-* styles, including highlighting for: + // - M4 tokens + // - TLV scope indentation + // - Statement delimitation (enabled by tlvTrackStatements) + token: function(stream, state) { + var style = undefined; + var match; // Return value of pattern matches. + + // Set highlighting mode based on code region (TLV or SV). + if (stream.sol() && ! state.tlvInBlockComment) { + // Process region. + if (stream.peek() == '\\') { + style = "def"; + stream.skipToEnd(); + if (stream.string.match(/\\SV/)) { + state.tlvCodeActive = false; + } else if (stream.string.match(/\\TLV/)){ + state.tlvCodeActive = true; + } + } + // Correct indentation in the face of a line prefix char. + if (state.tlvCodeActive && stream.pos == 0 && + (state.indented == 0) && (match = stream.match(tlvLineIndentationMatch, false))) { + state.indented = match[0].length; + } + + // Compute indentation state: + // o Auto indentation on next line + // o Indentation scope styles + var indented = state.indented; + var depth = indented / tlvIndentUnit; + if (depth <= state.tlvIndentationStyle.length) { + // not deeper than current scope + + var blankline = stream.string.length == indented; + var chPos = depth * tlvIndentUnit; + if (chPos < stream.string.length) { + var bodyString = stream.string.slice(chPos); + var ch = bodyString[0]; + if (tlvScopePrefixChars[ch] && ((match = bodyString.match(tlvIdentMatch)) && + tlvIdentifierStyle[match[1]])) { + // This line begins scope. + // Next line gets indented one level. + indented += tlvIndentUnit; + // Style the next level of indentation (except non-region keyword identifiers, + // which are statements themselves) + if (!(ch == "\\" && chPos > 0)) { + state.tlvIndentationStyle[depth] = tlvScopePrefixChars[ch]; + if (tlvTrackStatements) {state.statementComment = false;} + depth++; + } + } + } + // Clear out deeper indentation levels unless line is blank. + if (!blankline) { + while (state.tlvIndentationStyle.length > depth) { + state.tlvIndentationStyle.pop(); + } + } + } + // Set next level of indentation. + state.tlvNextIndent = indented; + } + + if (state.tlvCodeActive) { + // Highlight as TLV. + + var beginStatement = false; + if (tlvTrackStatements) { + // This starts a statement if the position is at the scope level + // and we're not within a statement leading comment. + beginStatement = + (stream.peek() != " ") && // not a space + (style === undefined) && // not a region identifier + !state.tlvInBlockComment && // not in block comment + //!stream.match(tlvCommentMatch, false) && // not comment start + (stream.column() == state.tlvIndentationStyle.length * tlvIndentUnit); // at scope level + if (beginStatement) { + if (state.statementComment) { + // statement already started by comment + beginStatement = false; + } + state.statementComment = + stream.match(tlvCommentMatch, false); // comment start + } + } + + var match; + if (style !== undefined) { + // Region line. + style += " " + tlvScopeStyle(state, 0, "scope-ident") + } else if (((stream.pos / tlvIndentUnit) < state.tlvIndentationStyle.length) && + (match = stream.match(stream.sol() ? tlvFirstLevelIndentMatch : /^ /))) { + // Indentation + style = // make this style distinct from the previous one to prevent + // codemirror from combining spans + "tlv-indent-" + (((stream.pos % 2) == 0) ? "even" : "odd") + + // and style it + " " + tlvScopeStyle(state, stream.pos - tlvIndentUnit, "indent"); + // Style the line prefix character. + if (match[0].charAt(0) == "!") { + style += " tlv-alert-line-prefix"; + } + // Place a class before a scope identifier. + if (tlvIdentNext(stream)) { + style += " " + tlvScopeStyle(state, stream.pos, "before-scope-ident"); + } + } else if (state.tlvInBlockComment) { + // In a block comment. + if (stream.match(/^.*?\*\//)) { + // Exit block comment. + state.tlvInBlockComment = false; + if (tlvTrackStatements && !stream.eol()) { + // Anything after comment is assumed to be real statement content. + state.statementComment = false; + } + } else { + stream.skipToEnd(); + } + style = "comment"; + } else if ((match = stream.match(tlvCommentMatch)) && !state.tlvInBlockComment) { + // Start comment. + if (match[0] == "//") { + // Line comment. + stream.skipToEnd(); + } else { + // Block comment. + state.tlvInBlockComment = true; + } + style = "comment"; + } else if (match = stream.match(tlvIdentMatch)) { + // looks like an identifier (or identifier prefix) + var prefix = match[1]; + var mnemonic = match[2]; + if (// is identifier prefix + tlvIdentifierStyle.hasOwnProperty(prefix) && + // has mnemonic or we're at the end of the line (maybe it hasn't been typed yet) + (mnemonic.length > 0 || stream.eol())) { + style = tlvIdentifierStyle[prefix]; + if (stream.column() == state.indented) { + // Begin scope. + style += " " + tlvScopeStyle(state, stream.column(), "scope-ident") + } + } else { + // Just swallow one character and try again. + // This enables subsequent identifier match with preceding symbol character, which + // is legal within a statement. (Eg, !$reset). It also enables detection of + // comment start with preceding symbols. + stream.backUp(stream.current().length - 1); + style = "tlv-default"; + } + } else if (stream.match(/^\t+/)) { + // Highlight tabs, which are illegal. + style = "tlv-tab"; + } else if (stream.match(/^[\[\]{}\(\);\:]+/)) { + // [:], (), {}, ;. + style = "meta"; + } else if (match = stream.match(/^[mM]4([\+_])?[\w\d_]*/)) { + // m4 pre proc + style = (match[1] == "+") ? "tlv-m4-plus" : "tlv-m4"; + } else if (stream.match(/^ +/)){ + // Skip over spaces. + if (stream.eol()) { + // Trailing spaces. + style = "error"; + } else { + // Non-trailing spaces. + style = "tlv-default"; + } + } else if (stream.match(/^[\w\d_]+/)) { + // alpha-numeric token. + style = "number"; + } else { + // Eat the next char w/ no formatting. + stream.next(); + style = "tlv-default"; + } + if (beginStatement) { + style += " tlv-statement"; + } + } else { + if (stream.match(/^[mM]4([\w\d_]*)/)) { + // m4 pre proc + style = "tlv-m4"; + } + } + return style; + }, + + indent: function(state) { + return (state.tlvCodeActive == true) ? state.tlvNextIndent : -1; + }, + + startState: function(state) { + state.tlvIndentationStyle = []; // Styles to use for each level of indentation. + state.tlvCodeActive = true; // True when we're in a TLV region (and at beginning of file). + state.tlvNextIndent = -1; // The number of spaces to autoindent the next line if tlvCodeActive. + state.tlvInBlockComment = false; // True inside /**/ comment. + if (tlvTrackStatements) { + state.statementComment = false; // True inside a statement's header comment. + } + } + + } + }); +}); diff --git a/modules/editor/codemirror/mode/xquery/xquery.min.js b/modules/editor/codemirror/mode/xquery/xquery.min.js @@ -0,0 +1,448 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function(mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function(CodeMirror) { +"use strict"; + +CodeMirror.defineMode("xquery", function() { + + // The keywords object is set to the result of this self executing + // function. Each keyword is a property of the keywords object whose + // value is {type: atype, style: astyle} + var keywords = function(){ + // convenience functions used to build keywords object + function kw(type) {return {type: type, style: "keyword"};} + var operator = kw("operator") + , atom = {type: "atom", style: "atom"} + , punctuation = {type: "punctuation", style: null} + , qualifier = {type: "axis_specifier", style: "qualifier"}; + + // kwObj is what is return from this function at the end + var kwObj = { + ',': punctuation + }; + + // a list of 'basic' keywords. For each add a property to kwObj with the value of + // {type: basic[i], style: "keyword"} e.g. 'after' --> {type: "after", style: "keyword"} + var basic = ['after', 'all', 'allowing', 'ancestor', 'ancestor-or-self', 'any', 'array', 'as', + 'ascending', 'at', 'attribute', 'base-uri', 'before', 'boundary-space', 'by', 'case', 'cast', + 'castable', 'catch', 'child', 'collation', 'comment', 'construction', 'contains', 'content', + 'context', 'copy', 'copy-namespaces', 'count', 'decimal-format', 'declare', 'default', 'delete', + 'descendant', 'descendant-or-self', 'descending', 'diacritics', 'different', 'distance', + 'document', 'document-node', 'element', 'else', 'empty', 'empty-sequence', 'encoding', 'end', + 'entire', 'every', 'exactly', 'except', 'external', 'first', 'following', 'following-sibling', + 'for', 'from', 'ftand', 'ftnot', 'ft-option', 'ftor', 'function', 'fuzzy', 'greatest', 'group', + 'if', 'import', 'in', 'inherit', 'insensitive', 'insert', 'instance', 'intersect', 'into', + 'invoke', 'is', 'item', 'language', 'last', 'lax', 'least', 'let', 'levels', 'lowercase', 'map', + 'modify', 'module', 'most', 'namespace', 'next', 'no', 'node', 'nodes', 'no-inherit', + 'no-preserve', 'not', 'occurs', 'of', 'only', 'option', 'order', 'ordered', 'ordering', + 'paragraph', 'paragraphs', 'parent', 'phrase', 'preceding', 'preceding-sibling', 'preserve', + 'previous', 'processing-instruction', 'relationship', 'rename', 'replace', 'return', + 'revalidation', 'same', 'satisfies', 'schema', 'schema-attribute', 'schema-element', 'score', + 'self', 'sensitive', 'sentence', 'sentences', 'sequence', 'skip', 'sliding', 'some', 'stable', + 'start', 'stemming', 'stop', 'strict', 'strip', 'switch', 'text', 'then', 'thesaurus', 'times', + 'to', 'transform', 'treat', 'try', 'tumbling', 'type', 'typeswitch', 'union', 'unordered', + 'update', 'updating', 'uppercase', 'using', 'validate', 'value', 'variable', 'version', + 'weight', 'when', 'where', 'wildcards', 'window', 'with', 'without', 'word', 'words', 'xquery']; + for(var i=0, l=basic.length; i < l; i++) { kwObj[basic[i]] = kw(basic[i]);}; + + // a list of types. For each add a property to kwObj with the value of + // {type: "atom", style: "atom"} + var types = ['xs:anyAtomicType', 'xs:anySimpleType', 'xs:anyType', 'xs:anyURI', + 'xs:base64Binary', 'xs:boolean', 'xs:byte', 'xs:date', 'xs:dateTime', 'xs:dateTimeStamp', + 'xs:dayTimeDuration', 'xs:decimal', 'xs:double', 'xs:duration', 'xs:ENTITIES', 'xs:ENTITY', + 'xs:float', 'xs:gDay', 'xs:gMonth', 'xs:gMonthDay', 'xs:gYear', 'xs:gYearMonth', 'xs:hexBinary', + 'xs:ID', 'xs:IDREF', 'xs:IDREFS', 'xs:int', 'xs:integer', 'xs:item', 'xs:java', 'xs:language', + 'xs:long', 'xs:Name', 'xs:NCName', 'xs:negativeInteger', 'xs:NMTOKEN', 'xs:NMTOKENS', + 'xs:nonNegativeInteger', 'xs:nonPositiveInteger', 'xs:normalizedString', 'xs:NOTATION', + 'xs:numeric', 'xs:positiveInteger', 'xs:precisionDecimal', 'xs:QName', 'xs:short', 'xs:string', + 'xs:time', 'xs:token', 'xs:unsignedByte', 'xs:unsignedInt', 'xs:unsignedLong', + 'xs:unsignedShort', 'xs:untyped', 'xs:untypedAtomic', 'xs:yearMonthDuration']; + for(var i=0, l=types.length; i < l; i++) { kwObj[types[i]] = atom;}; + + // each operator will add a property to kwObj with value of {type: "operator", style: "keyword"} + var operators = ['eq', 'ne', 'lt', 'le', 'gt', 'ge', ':=', '=', '>', '>=', '<', '<=', '.', '|', '?', 'and', 'or', 'div', 'idiv', 'mod', '*', '/', '+', '-']; + for(var i=0, l=operators.length; i < l; i++) { kwObj[operators[i]] = operator;}; + + // each axis_specifiers will add a property to kwObj with value of {type: "axis_specifier", style: "qualifier"} + var axis_specifiers = ["self::", "attribute::", "child::", "descendant::", "descendant-or-self::", "parent::", + "ancestor::", "ancestor-or-self::", "following::", "preceding::", "following-sibling::", "preceding-sibling::"]; + for(var i=0, l=axis_specifiers.length; i < l; i++) { kwObj[axis_specifiers[i]] = qualifier; }; + + return kwObj; + }(); + + function chain(stream, state, f) { + state.tokenize = f; + return f(stream, state); + } + + // the primary mode tokenizer + function tokenBase(stream, state) { + var ch = stream.next(), + mightBeFunction = false, + isEQName = isEQNameAhead(stream); + + // an XML tag (if not in some sub, chained tokenizer) + if (ch == "<") { + if(stream.match("!--", true)) + return chain(stream, state, tokenXMLComment); + + if(stream.match("![CDATA", false)) { + state.tokenize = tokenCDATA; + return "tag"; + } + + if(stream.match("?", false)) { + return chain(stream, state, tokenPreProcessing); + } + + var isclose = stream.eat("/"); + stream.eatSpace(); + var tagName = "", c; + while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c; + + return chain(stream, state, tokenTag(tagName, isclose)); + } + // start code block + else if(ch == "{") { + pushStateStack(state, { type: "codeblock"}); + return null; + } + // end code block + else if(ch == "}") { + popStateStack(state); + return null; + } + // if we're in an XML block + else if(isInXmlBlock(state)) { + if(ch == ">") + return "tag"; + else if(ch == "/" && stream.eat(">")) { + popStateStack(state); + return "tag"; + } + else + return "variable"; + } + // if a number + else if (/\d/.test(ch)) { + stream.match(/^\d*(?:\.\d*)?(?:E[+\-]?\d+)?/); + return "atom"; + } + // comment start + else if (ch === "(" && stream.eat(":")) { + pushStateStack(state, { type: "comment"}); + return chain(stream, state, tokenComment); + } + // quoted string + else if (!isEQName && (ch === '"' || ch === "'")) + return chain(stream, state, tokenString(ch)); + // variable + else if(ch === "$") { + return chain(stream, state, tokenVariable); + } + // assignment + else if(ch ===":" && stream.eat("=")) { + return "keyword"; + } + // open paren + else if(ch === "(") { + pushStateStack(state, { type: "paren"}); + return null; + } + // close paren + else if(ch === ")") { + popStateStack(state); + return null; + } + // open paren + else if(ch === "[") { + pushStateStack(state, { type: "bracket"}); + return null; + } + // close paren + else if(ch === "]") { + popStateStack(state); + return null; + } + else { + var known = keywords.propertyIsEnumerable(ch) && keywords[ch]; + + // if there's a EQName ahead, consume the rest of the string portion, it's likely a function + if(isEQName && ch === '\"') while(stream.next() !== '"'){} + if(isEQName && ch === '\'') while(stream.next() !== '\''){} + + // gobble up a word if the character is not known + if(!known) stream.eatWhile(/[\w\$_-]/); + + // gobble a colon in the case that is a lib func type call fn:doc + var foundColon = stream.eat(":"); + + // if there's not a second colon, gobble another word. Otherwise, it's probably an axis specifier + // which should get matched as a keyword + if(!stream.eat(":") && foundColon) { + stream.eatWhile(/[\w\$_-]/); + } + // if the next non whitespace character is an open paren, this is probably a function (if not a keyword of other sort) + if(stream.match(/^[ \t]*\(/, false)) { + mightBeFunction = true; + } + // is the word a keyword? + var word = stream.current(); + known = keywords.propertyIsEnumerable(word) && keywords[word]; + + // if we think it's a function call but not yet known, + // set style to variable for now for lack of something better + if(mightBeFunction && !known) known = {type: "function_call", style: "variable def"}; + + // if the previous word was element, attribute, axis specifier, this word should be the name of that + if(isInXmlConstructor(state)) { + popStateStack(state); + return "variable"; + } + // as previously checked, if the word is element,attribute, axis specifier, call it an "xmlconstructor" and + // push the stack so we know to look for it on the next word + if(word == "element" || word == "attribute" || known.type == "axis_specifier") pushStateStack(state, {type: "xmlconstructor"}); + + // if the word is known, return the details of that else just call this a generic 'word' + return known ? known.style : "variable"; + } + } + + // handle comments, including nested + function tokenComment(stream, state) { + var maybeEnd = false, maybeNested = false, nestedCount = 0, ch; + while (ch = stream.next()) { + if (ch == ")" && maybeEnd) { + if(nestedCount > 0) + nestedCount--; + else { + popStateStack(state); + break; + } + } + else if(ch == ":" && maybeNested) { + nestedCount++; + } + maybeEnd = (ch == ":"); + maybeNested = (ch == "("); + } + + return "comment"; + } + + // tokenizer for string literals + // optionally pass a tokenizer function to set state.tokenize back to when finished + function tokenString(quote, f) { + return function(stream, state) { + var ch; + + if(isInString(state) && stream.current() == quote) { + popStateStack(state); + if(f) state.tokenize = f; + return "string"; + } + + pushStateStack(state, { type: "string", name: quote, tokenize: tokenString(quote, f) }); + + // if we're in a string and in an XML block, allow an embedded code block + if(stream.match("{", false) && isInXmlAttributeBlock(state)) { + state.tokenize = tokenBase; + return "string"; + } + + + while (ch = stream.next()) { + if (ch == quote) { + popStateStack(state); + if(f) state.tokenize = f; + break; + } + else { + // if we're in a string and in an XML block, allow an embedded code block in an attribute + if(stream.match("{", false) && isInXmlAttributeBlock(state)) { + state.tokenize = tokenBase; + return "string"; + } + + } + } + + return "string"; + }; + } + + // tokenizer for variables + function tokenVariable(stream, state) { + var isVariableChar = /[\w\$_-]/; + + // a variable may start with a quoted EQName so if the next character is quote, consume to the next quote + if(stream.eat("\"")) { + while(stream.next() !== '\"'){}; + stream.eat(":"); + } else { + stream.eatWhile(isVariableChar); + if(!stream.match(":=", false)) stream.eat(":"); + } + stream.eatWhile(isVariableChar); + state.tokenize = tokenBase; + return "variable"; + } + + // tokenizer for XML tags + function tokenTag(name, isclose) { + return function(stream, state) { + stream.eatSpace(); + if(isclose && stream.eat(">")) { + popStateStack(state); + state.tokenize = tokenBase; + return "tag"; + } + // self closing tag without attributes? + if(!stream.eat("/")) + pushStateStack(state, { type: "tag", name: name, tokenize: tokenBase}); + if(!stream.eat(">")) { + state.tokenize = tokenAttribute; + return "tag"; + } + else { + state.tokenize = tokenBase; + } + return "tag"; + }; + } + + // tokenizer for XML attributes + function tokenAttribute(stream, state) { + var ch = stream.next(); + + if(ch == "/" && stream.eat(">")) { + if(isInXmlAttributeBlock(state)) popStateStack(state); + if(isInXmlBlock(state)) popStateStack(state); + return "tag"; + } + if(ch == ">") { + if(isInXmlAttributeBlock(state)) popStateStack(state); + return "tag"; + } + if(ch == "=") + return null; + // quoted string + if (ch == '"' || ch == "'") + return chain(stream, state, tokenString(ch, tokenAttribute)); + + if(!isInXmlAttributeBlock(state)) + pushStateStack(state, { type: "attribute", tokenize: tokenAttribute}); + + stream.eat(/[a-zA-Z_:]/); + stream.eatWhile(/[-a-zA-Z0-9_:.]/); + stream.eatSpace(); + + // the case where the attribute has not value and the tag was closed + if(stream.match(">", false) || stream.match("/", false)) { + popStateStack(state); + state.tokenize = tokenBase; + } + + return "attribute"; + } + + // handle comments, including nested + function tokenXMLComment(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "-" && stream.match("->", true)) { + state.tokenize = tokenBase; + return "comment"; + } + } + } + + + // handle CDATA + function tokenCDATA(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "]" && stream.match("]", true)) { + state.tokenize = tokenBase; + return "comment"; + } + } + } + + // handle preprocessing instructions + function tokenPreProcessing(stream, state) { + var ch; + while (ch = stream.next()) { + if (ch == "?" && stream.match(">", true)) { + state.tokenize = tokenBase; + return "comment meta"; + } + } + } + + + // functions to test the current context of the state + function isInXmlBlock(state) { return isIn(state, "tag"); } + function isInXmlAttributeBlock(state) { return isIn(state, "attribute"); } + function isInXmlConstructor(state) { return isIn(state, "xmlconstructor"); } + function isInString(state) { return isIn(state, "string"); } + + function isEQNameAhead(stream) { + // assume we've already eaten a quote (") + if(stream.current() === '"') + return stream.match(/^[^\"]+\"\:/, false); + else if(stream.current() === '\'') + return stream.match(/^[^\"]+\'\:/, false); + else + return false; + } + + function isIn(state, type) { + return (state.stack.length && state.stack[state.stack.length - 1].type == type); + } + + function pushStateStack(state, newState) { + state.stack.push(newState); + } + + function popStateStack(state) { + state.stack.pop(); + var reinstateTokenize = state.stack.length && state.stack[state.stack.length-1].tokenize; + state.tokenize = reinstateTokenize || tokenBase; + } + + // the interface for the mode API + return { + startState: function() { + return { + tokenize: tokenBase, + cc: [], + stack: [] + }; + }, + + token: function(stream, state) { + if (stream.eatSpace()) return null; + var style = state.tokenize(stream, state); + return style; + }, + + blockCommentStart: "(:", + blockCommentEnd: ":)" + + }; + +}); + +CodeMirror.defineMIME("application/xquery", "xquery"); + +}); diff --git a/modules/editor/codemirror/mode/yaml-frontmatter/yaml-frontmatter.min.js b/modules/editor/codemirror/mode/yaml-frontmatter/yaml-frontmatter.min.js @@ -0,0 +1,68 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +(function (mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror"), require("../yaml/yaml")) + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror", "../yaml/yaml"], mod) + else // Plain browser env + mod(CodeMirror) +})(function (CodeMirror) { + + var START = 0, FRONTMATTER = 1, BODY = 2 + + // a mixed mode for Markdown text with an optional YAML front matter + CodeMirror.defineMode("yaml-frontmatter", function (config, parserConfig) { + var yamlMode = CodeMirror.getMode(config, "yaml") + var innerMode = CodeMirror.getMode(config, parserConfig && parserConfig.base || "gfm") + + function curMode(state) { + return state.state == BODY ? innerMode : yamlMode + } + + return { + startState: function () { + return { + state: START, + inner: CodeMirror.startState(yamlMode) + } + }, + copyState: function (state) { + return { + state: state.state, + inner: CodeMirror.copyState(curMode(state), state.inner) + } + }, + token: function (stream, state) { + if (state.state == START) { + if (stream.match(/---/, false)) { + state.state = FRONTMATTER + return yamlMode.token(stream, state.inner) + } else { + state.state = BODY + state.inner = CodeMirror.startState(innerMode) + return innerMode.token(stream, state.inner) + } + } else if (state.state == FRONTMATTER) { + var end = stream.sol() && stream.match(/---/, false) + var style = yamlMode.token(stream, state.inner) + if (end) { + state.state = BODY + state.inner = CodeMirror.startState(innerMode) + } + return style + } else { + return innerMode.token(stream, state.inner) + } + }, + innerMode: function (state) { + return {mode: curMode(state), state: state.inner} + }, + blankLine: function (state) { + var mode = curMode(state) + if (mode.blankLine) return mode.blankLine(state.inner) + } + } + }) +}); diff --git a/modules/editor/codemirror/src/display/gutters.min.js b/modules/editor/codemirror/src/display/gutters.min.js @@ -0,0 +1,34 @@ +import { elt, removeChildren } from "../util/dom.js" +import { indexOf } from "../util/misc.js" + +import { updateGutterSpace } from "./update_display.js" + +// Rebuild the gutter elements, ensure the margin to the left of the +// code matches their width. +export function updateGutters(cm) { + let gutters = cm.display.gutters, specs = cm.options.gutters + removeChildren(gutters) + let i = 0 + for (; i < specs.length; ++i) { + let gutterClass = specs[i] + let 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" + updateGutterSpace(cm) +} + +// Make sure the gutters options contains the element +// "CodeMirror-linenumbers" when the lineNumbers option is true. +export function setGuttersForLineNumbers(options) { + let 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) + } +} diff --git a/modules/editor/codemirror/src/display/highlight_worker.js b/modules/editor/codemirror/src/display/highlight_worker.js @@ -0,0 +1,55 @@ +import { getContextBefore, highlightLine, processLine } from "../line/highlight.js" +import { copyState } from "../modes.js" +import { bind } from "../util/misc.js" + +import { runInOp } from "./operations.js" +import { regLineChange } from "./view_tracking.js" + +// HIGHLIGHT WORKER + +export function startWorker(cm, time) { + if (cm.doc.highlightFrontier < cm.display.viewTo) + cm.state.highlight.set(time, bind(highlightWorker, cm)) +} + +function highlightWorker(cm) { + let doc = cm.doc + if (doc.highlightFrontier >= cm.display.viewTo) return + let end = +new Date + cm.options.workTime + let context = getContextBefore(cm, doc.highlightFrontier) + let changedLines = [] + + doc.iter(context.line, Math.min(doc.first + doc.size, cm.display.viewTo + 500), line => { + if (context.line >= cm.display.viewFrom) { // Visible + let oldStyles = line.styles + let resetState = line.text.length > cm.options.maxHighlightLength ? copyState(doc.mode, context.state) : null + let highlighted = highlightLine(cm, line, context, true) + if (resetState) context.state = resetState + line.styles = highlighted.styles + let oldCls = line.styleClasses, newCls = highlighted.classes + if (newCls) line.styleClasses = newCls + else if (oldCls) line.styleClasses = null + let ischange = !oldStyles || oldStyles.length != line.styles.length || + oldCls != newCls && (!oldCls || !newCls || oldCls.bgClass != newCls.bgClass || oldCls.textClass != newCls.textClass) + for (let i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i] + if (ischange) changedLines.push(context.line) + line.stateAfter = context.save() + context.nextLine() + } else { + if (line.text.length <= cm.options.maxHighlightLength) + processLine(cm, line.text, context) + line.stateAfter = context.line % 5 == 0 ? context.save() : null + context.nextLine() + } + if (+new Date > end) { + startWorker(cm, cm.options.workDelay) + return true + } + }) + doc.highlightFrontier = context.line + doc.modeFrontier = Math.max(doc.modeFrontier, context.line) + if (changedLines.length) runInOp(cm, () => { + for (let i = 0; i < changedLines.length; i++) + regLineChange(cm, changedLines[i], "text") + }) +} diff --git a/modules/editor/codemirror/src/display/scroll_events.min.js b/modules/editor/codemirror/src/display/scroll_events.min.js @@ -0,0 +1,115 @@ +import { chrome, gecko, ie, mac, presto, safari, webkit } from "../util/browser.js" +import { e_preventDefault } from "../util/event.js" + +import { updateDisplaySimple } from "./update_display.js" +import { setScrollLeft, updateScrollTop } from "./scrolling.js" + +// 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. + +let 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 wheelEventDelta(e) { + let dx = e.wheelDeltaX, dy = e.wheelDeltaY + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail + else if (dy == null) dy = e.wheelDelta + return {x: dx, y: dy} +} +export function wheelEventPixels(e) { + let delta = wheelEventDelta(e) + delta.x *= wheelPixelsPerUnit + delta.y *= wheelPixelsPerUnit + return delta +} + +export function onScrollWheel(cm, e) { + let delta = wheelEventDelta(e), dx = delta.x, dy = delta.y + + let display = cm.display, scroll = display.scroller + // Quit if there's nothing to scroll here + let canScrollX = scroll.scrollWidth > scroll.clientWidth + let canScrollY = scroll.scrollHeight > scroll.clientHeight + if (!(dx && canScrollX || dy && canScrollY)) 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) { + outer: for (let cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) { + for (let i = 0; i < view.length; i++) { + if (view[i].node == cur) { + cm.display.currentWheelTarget = cur + break outer + } + } + } + } + + // 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 && !presto && wheelPixelsPerUnit != null) { + if (dy && canScrollY) + updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)) + setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit)) + // Only prevent default scrolling if vertical scrolling is + // actually possible. Otherwise, it causes vertical scroll + // jitter on OSX trackpads when deltaX is small and deltaY + // is large (issue #3579) + if (!dy || (dy && canScrollY)) + e_preventDefault(e) + display.wheelStartX = null // Abort measurement, if in progress + return + } + + // 'Project' the visible viewport to cover the area that is being + // scrolled into view (if we know enough to estimate it). + if (dy && wheelPixelsPerUnit != null) { + let pixels = dy * wheelPixelsPerUnit + let 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) + updateDisplaySimple(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(() => { + if (display.wheelStartX == null) return + let movedX = scroll.scrollLeft - display.wheelStartX + let movedY = scroll.scrollTop - display.wheelStartY + let 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 + } + } +} diff --git a/modules/editor/codemirror/src/display/selection.min.js b/modules/editor/codemirror/src/display/selection.min.js @@ -0,0 +1,158 @@ +import { Pos } from "../line/pos.js" +import { visualLine } from "../line/spans.js" +import { getLine } from "../line/utils_line.js" +import { charCoords, cursorCoords, displayWidth, paddingH, wrappedLineExtentChar } from "../measurement/position_measurement.js" +import { getOrder, iterateBidiSections } from "../util/bidi.js" +import { elt } from "../util/dom.js" + +export function updateSelection(cm) { + cm.display.input.showSelection(cm.display.input.prepareSelection()) +} + +export function prepareSelection(cm, primary = true) { + let doc = cm.doc, result = {} + let curFragment = result.cursors = document.createDocumentFragment() + let selFragment = result.selection = document.createDocumentFragment() + + for (let i = 0; i < doc.sel.ranges.length; i++) { + if (!primary && i == doc.sel.primIndex) continue + let range = doc.sel.ranges[i] + if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) continue + let collapsed = range.empty() + if (collapsed || cm.options.showCursorWhenSelecting) + drawSelectionCursor(cm, range.head, curFragment) + if (!collapsed) + drawSelectionRange(cm, range, selFragment) + } + return result +} + +// Draws a cursor for the given range +export function drawSelectionCursor(cm, head, output) { + let pos = cursorCoords(cm, head, "div", null, null, !cm.options.singleCursorHeightPerLine) + + let cursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor")) + cursor.style.left = pos.left + "px" + cursor.style.top = pos.top + "px" + cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px" + + if (pos.other) { + // Secondary cursor, shown when on a 'jump' in bi-directional text + let otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor")) + otherCursor.style.display = "" + otherCursor.style.left = pos.other.left + "px" + otherCursor.style.top = pos.other.top + "px" + otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px" + } +} + +function cmpCoords(a, b) { return a.top - b.top || a.left - b.left } + +// Draws the given range as a highlighted selection +function drawSelectionRange(cm, range, output) { + let display = cm.display, doc = cm.doc + let fragment = document.createDocumentFragment() + let padding = paddingH(cm.display), leftSide = padding.left + let rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right + let docLTR = doc.direction == "ltr" + + function add(left, top, width, bottom) { + if (top < 0) top = 0 + top = Math.round(top) + bottom = Math.round(bottom) + fragment.appendChild(elt("div", null, "CodeMirror-selected", `position: absolute; left: ${left}px; + top: ${top}px; width: ${width == null ? rightSide - left : width}px; + height: ${bottom - top}px`)) + } + + function drawForLine(line, fromArg, toArg) { + let lineObj = getLine(doc, line) + let lineLen = lineObj.text.length + let start, end + function coords(ch, bias) { + return charCoords(cm, Pos(line, ch), "div", lineObj, bias) + } + + function wrapX(pos, dir, side) { + let extent = wrappedLineExtentChar(cm, lineObj, null, pos) + let prop = (dir == "ltr") == (side == "after") ? "left" : "right" + let ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1) + return coords(ch, prop)[prop] + } + + let order = getOrder(lineObj, doc.direction) + iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, (from, to, dir, i) => { + let ltr = dir == "ltr" + let fromPos = coords(from, ltr ? "left" : "right") + let toPos = coords(to - 1, ltr ? "right" : "left") + + let openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen + let first = i == 0, last = !order || i == order.length - 1 + if (toPos.top - fromPos.top <= 3) { // Single line + let openLeft = (docLTR ? openStart : openEnd) && first + let openRight = (docLTR ? openEnd : openStart) && last + let left = openLeft ? leftSide : (ltr ? fromPos : toPos).left + let right = openRight ? rightSide : (ltr ? toPos : fromPos).right + add(left, fromPos.top, right - left, fromPos.bottom) + } else { // Multiple lines + let topLeft, topRight, botLeft, botRight + if (ltr) { + topLeft = docLTR && openStart && first ? leftSide : fromPos.left + topRight = docLTR ? rightSide : wrapX(from, dir, "before") + botLeft = docLTR ? leftSide : wrapX(to, dir, "after") + botRight = docLTR && openEnd && last ? rightSide : toPos.right + } else { + topLeft = !docLTR ? leftSide : wrapX(from, dir, "before") + topRight = !docLTR && openStart && first ? rightSide : fromPos.right + botLeft = !docLTR && openEnd && last ? leftSide : toPos.left + botRight = !docLTR ? rightSide : wrapX(to, dir, "after") + } + add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom) + if (fromPos.bottom < toPos.top) add(leftSide, fromPos.bottom, null, toPos.top) + add(botLeft, toPos.top, botRight - botLeft, toPos.bottom) + } + + if (!start || cmpCoords(fromPos, start) < 0) start = fromPos + if (cmpCoords(toPos, start) < 0) start = toPos + if (!end || cmpCoords(fromPos, end) < 0) end = fromPos + if (cmpCoords(toPos, end) < 0) end = toPos + }) + return {start: start, end: end} + } + + let sFrom = range.from(), sTo = range.to() + if (sFrom.line == sTo.line) { + drawForLine(sFrom.line, sFrom.ch, sTo.ch) + } else { + let fromLine = getLine(doc, sFrom.line), toLine = getLine(doc, sTo.line) + let singleVLine = visualLine(fromLine) == visualLine(toLine) + let leftEnd = drawForLine(sFrom.line, sFrom.ch, singleVLine ? fromLine.text.length + 1 : null).end + let rightStart = drawForLine(sTo.line, singleVLine ? 0 : null, sTo.ch).start + if (singleVLine) { + if (leftEnd.top < rightStart.top - 2) { + add(leftEnd.right, leftEnd.top, null, leftEnd.bottom) + add(leftSide, rightStart.top, rightStart.left, rightStart.bottom) + } else { + add(leftEnd.right, leftEnd.top, rightStart.left - leftEnd.right, leftEnd.bottom) + } + } + if (leftEnd.bottom < rightStart.top) + add(leftSide, leftEnd.bottom, null, rightStart.top) + } + + output.appendChild(fragment) +} + +// Cursor-blinking +export function restartBlink(cm) { + if (!cm.state.focused) return + let display = cm.display + clearInterval(display.blinker) + let on = true + display.cursorDiv.style.visibility = "" + if (cm.options.cursorBlinkRate > 0) + display.blinker = setInterval(() => display.cursorDiv.style.visibility = (on = !on) ? "" : "hidden", + cm.options.cursorBlinkRate) + else if (cm.options.cursorBlinkRate < 0) + display.cursorDiv.style.visibility = "hidden" +} diff --git a/modules/editor/codemirror/src/display/update_display.js b/modules/editor/codemirror/src/display/update_display.js @@ -0,0 +1,260 @@ +import { sawCollapsedSpans } from "../line/saw_special_spans.js" +import { heightAtLine, visualLineEndNo, visualLineNo } from "../line/spans.js" +import { getLine, lineNumberFor } from "../line/utils_line.js" +import { displayHeight, displayWidth, getDimensions, paddingVert, scrollGap } from "../measurement/position_measurement.js" +import { mac, webkit } from "../util/browser.js" +import { activeElt, removeChildren, contains } from "../util/dom.js" +import { hasHandler, signal } from "../util/event.js" +import { indexOf } from "../util/misc.js" + +import { buildLineElement, updateLineForChanges } from "./update_line.js" +import { startWorker } from "./highlight_worker.js" +import { maybeUpdateLineNumberWidth } from "./line_numbers.js" +import { measureForScrollbars, updateScrollbars } from "./scrollbars.js" +import { updateSelection } from "./selection.js" +import { updateHeightsInViewport, visibleLines } from "./update_lines.js" +import { adjustView, countDirtyView, resetView } from "./view_tracking.js" + +// DISPLAY DRAWING + +export class DisplayUpdate { + constructor(cm, viewport, force) { + let display = cm.display + + this.viewport = viewport + // Store some values that we'll need later (but don't want to force a relayout for) + this.visible = visibleLines(display, cm.doc, viewport) + this.editorIsHidden = !display.wrapper.offsetWidth + this.wrapperHeight = display.wrapper.clientHeight + this.wrapperWidth = display.wrapper.clientWidth + this.oldDisplayWidth = displayWidth(cm) + this.force = force + this.dims = getDimensions(cm) + this.events = [] + } + + signal(emitter, type) { + if (hasHandler(emitter, type)) + this.events.push(arguments) + } + finish() { + for (let i = 0; i < this.events.length; i++) + signal.apply(null, this.events[i]) + } +} + +export function maybeClipScrollbars(cm) { + let display = cm.display + if (!display.scrollbarsClipped && display.scroller.offsetWidth) { + display.nativeBarWidth = display.scroller.offsetWidth - display.scroller.clientWidth + display.heightForcer.style.height = scrollGap(cm) + "px" + display.sizer.style.marginBottom = -display.nativeBarWidth + "px" + display.sizer.style.borderRightWidth = scrollGap(cm) + "px" + display.scrollbarsClipped = true + } +} + +function selectionSnapshot(cm) { + if (cm.hasFocus()) return null + let active = activeElt() + if (!active || !contains(cm.display.lineDiv, active)) return null + let result = {activeElt: active} + if (window.getSelection) { + let sel = window.getSelection() + if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) { + result.anchorNode = sel.anchorNode + result.anchorOffset = sel.anchorOffset + result.focusNode = sel.focusNode + result.focusOffset = sel.focusOffset + } + } + return result +} + +function restoreSelection(snapshot) { + if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) return + snapshot.activeElt.focus() + if (snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) { + let sel = window.getSelection(), range = document.createRange() + range.setEnd(snapshot.anchorNode, snapshot.anchorOffset) + range.collapse(false) + sel.removeAllRanges() + sel.addRange(range) + sel.extend(snapshot.focusNode, snapshot.focusOffset) + } +} + +// Does the actual updating of the line display. Bails out +// (returning false) when there is nothing to be done and forced is +// false. +export function updateDisplayIfNeeded(cm, update) { + let display = cm.display, doc = cm.doc + + if (update.editorIsHidden) { + resetView(cm) + return false + } + + // Bail out if the visible area is already rendered and nothing changed. + if (!update.force && + update.visible.from >= display.viewFrom && update.visible.to <= display.viewTo && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo) && + display.renderedView == display.view && countDirtyView(cm) == 0) + return false + + if (maybeUpdateLineNumberWidth(cm)) { + resetView(cm) + update.dims = getDimensions(cm) + } + + // Compute a suitable new viewport (from & to) + let end = doc.first + doc.size + let from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first) + let to = Math.min(end, update.visible.to + cm.options.viewportMargin) + if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom) + if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo) + if (sawCollapsedSpans) { + from = visualLineNo(cm.doc, from) + to = visualLineEndNo(cm.doc, to) + } + + let different = from != display.viewFrom || to != display.viewTo || + display.lastWrapHeight != update.wrapperHeight || display.lastWrapWidth != update.wrapperWidth + adjustView(cm, from, to) + + display.viewOffset = heightAtLine(getLine(cm.doc, display.viewFrom)) + // Position the mover div to align with the current scroll position + cm.display.mover.style.top = display.viewOffset + "px" + + let toUpdate = countDirtyView(cm) + if (!different && toUpdate == 0 && !update.force && display.renderedView == display.view && + (display.updateLineNumbers == null || display.updateLineNumbers >= display.viewTo)) + return false + + // For big changes, we hide the enclosing element during the + // update, since that speeds up the operations on most browsers. + let selSnapshot = selectionSnapshot(cm) + if (toUpdate > 4) display.lineDiv.style.display = "none" + patchDisplay(cm, display.updateLineNumbers, update.dims) + if (toUpdate > 4) display.lineDiv.style.display = "" + display.renderedView = display.view + // There might have been a widget with a focused element that got + // hidden or updated, if so re-focus it. + restoreSelection(selSnapshot) + + // Prevent selection and cursors from interfering with the scroll + // width and height. + removeChildren(display.cursorDiv) + removeChildren(display.selectionDiv) + display.gutters.style.height = display.sizer.style.minHeight = 0 + + if (different) { + display.lastWrapHeight = update.wrapperHeight + display.lastWrapWidth = update.wrapperWidth + startWorker(cm, 400) + } + + display.updateLineNumbers = null + + return true +} + +export function postUpdateDisplay(cm, update) { + let viewport = update.viewport + + for (let first = true;; first = false) { + if (!first || !cm.options.lineWrapping || update.oldDisplayWidth == displayWidth(cm)) { + // Clip forced viewport to actual scrollable area. + if (viewport && viewport.top != null) + viewport = {top: Math.min(cm.doc.height + paddingVert(cm.display) - displayHeight(cm), viewport.top)} + // Updated line heights might result in the drawn area not + // actually covering the viewport. Keep looping until it does. + update.visible = visibleLines(cm.display, cm.doc, viewport) + if (update.visible.from >= cm.display.viewFrom && update.visible.to <= cm.display.viewTo) + break + } + if (!updateDisplayIfNeeded(cm, update)) break + updateHeightsInViewport(cm) + let barMeasure = measureForScrollbars(cm) + updateSelection(cm) + updateScrollbars(cm, barMeasure) + setDocumentHeight(cm, barMeasure) + update.force = false + } + + update.signal(cm, "update", cm) + if (cm.display.viewFrom != cm.display.reportedViewFrom || cm.display.viewTo != cm.display.reportedViewTo) { + update.signal(cm, "viewportChange", cm, cm.display.viewFrom, cm.display.viewTo) + cm.display.reportedViewFrom = cm.display.viewFrom; cm.display.reportedViewTo = cm.display.viewTo + } +} + +export function updateDisplaySimple(cm, viewport) { + let update = new DisplayUpdate(cm, viewport) + if (updateDisplayIfNeeded(cm, update)) { + updateHeightsInViewport(cm) + postUpdateDisplay(cm, update) + let barMeasure = measureForScrollbars(cm) + updateSelection(cm) + updateScrollbars(cm, barMeasure) + setDocumentHeight(cm, barMeasure) + update.finish() + } +} + +// Sync the actual display DOM structure with display.view, removing +// nodes for lines that are no longer in view, and creating the ones +// that are not there yet, and updating the ones that are out of +// date. +function patchDisplay(cm, updateNumbersFrom, dims) { + let display = cm.display, lineNumbers = cm.options.lineNumbers + let container = display.lineDiv, cur = container.firstChild + + function rm(node) { + let next = node.nextSibling + // Works around a throw-scroll bug in OS X Webkit + if (webkit && mac && cm.display.currentWheelTarget == node) + node.style.display = "none" + else + node.parentNode.removeChild(node) + return next + } + + let view = display.view, lineN = display.viewFrom + // Loop over the elements in the view, syncing cur (the DOM nodes + // in display.lineDiv) with the view as we go. + for (let i = 0; i < view.length; i++) { + let lineView = view[i] + if (lineView.hidden) { + } else if (!lineView.node || lineView.node.parentNode != container) { // Not drawn yet + let node = buildLineElement(cm, lineView, lineN, dims) + container.insertBefore(node, cur) + } else { // Already drawn + while (cur != lineView.node) cur = rm(cur) + let updateNumber = lineNumbers && updateNumbersFrom != null && + updateNumbersFrom <= lineN && lineView.lineNumber + if (lineView.changes) { + if (indexOf(lineView.changes, "gutter") > -1) updateNumber = false + updateLineForChanges(cm, lineView, lineN, dims) + } + if (updateNumber) { + removeChildren(lineView.lineNumber) + lineView.lineNumber.appendChild(document.createTextNode(lineNumberFor(cm.options, lineN))) + } + cur = lineView.node.nextSibling + } + lineN += lineView.size + } + while (cur) cur = rm(cur) +} + +export function updateGutterSpace(cm) { + let width = cm.display.gutters.offsetWidth + cm.display.sizer.style.marginLeft = width + "px" +} + +export function setDocumentHeight(cm, measure) { + cm.display.sizer.style.minHeight = measure.docHeight + "px" + cm.display.heightForcer.style.top = measure.docHeight + "px" + cm.display.gutters.style.height = (measure.docHeight + cm.display.barHeight + scrollGap(cm)) + "px" +} diff --git a/modules/editor/codemirror/src/edit/drop_events.js b/modules/editor/codemirror/src/edit/drop_events.js @@ -0,0 +1,119 @@ +import { drawSelectionCursor } from "../display/selection.js" +import { operation } from "../display/operations.js" +import { clipPos } from "../line/pos.js" +import { posFromMouse } from "../measurement/position_measurement.js" +import { eventInWidget } from "../measurement/widgets.js" +import { makeChange, replaceRange } from "../model/changes.js" +import { changeEnd } from "../model/change_measurement.js" +import { simpleSelection } from "../model/selection.js" +import { setSelectionNoUndo, setSelectionReplaceHistory } from "../model/selection_updates.js" +import { ie, presto, safari } from "../util/browser.js" +import { elt, removeChildrenAndAdd } from "../util/dom.js" +import { e_preventDefault, e_stop, signalDOMEvent } from "../util/event.js" +import { indexOf } from "../util/misc.js" + +// Kludge to work around strange IE behavior where it'll sometimes +// re-fire a series of drag-related events right after the drop (#1551) +let lastDrop = 0 + +export function onDrop(e) { + let cm = this + clearDragCursor(cm) + if (signalDOMEvent(cm, e) || eventInWidget(cm.display, e)) + return + e_preventDefault(e) + if (ie) lastDrop = +new Date + let pos = posFromMouse(cm, e, true), files = e.dataTransfer.files + if (!pos || cm.isReadOnly()) return + // Might be a file drop, in which case we simply extract the text + // and insert it. + if (files && files.length && window.FileReader && window.File) { + let n = files.length, text = Array(n), read = 0 + let loadFile = (file, i) => { + if (cm.options.allowDropFileTypes && + indexOf(cm.options.allowDropFileTypes, file.type) == -1) + return + + let reader = new FileReader + reader.onload = operation(cm, () => { + let content = reader.result + if (/[\x00-\x08\x0e-\x1f]{2}/.test(content)) content = "" + text[i] = content + if (++read == n) { + pos = clipPos(cm.doc, pos) + let change = {from: pos, to: pos, + text: cm.doc.splitLines(text.join(cm.doc.lineSeparator())), + origin: "paste"} + makeChange(cm.doc, change) + setSelectionReplaceHistory(cm.doc, simpleSelection(pos, changeEnd(change))) + } + }) + reader.readAsText(file) + } + for (let i = 0; i < n; ++i) loadFile(files[i], i) + } else { // Normal drop + // Don't do a replace if the drop happened inside of the selected text. + if (cm.state.draggingText && cm.doc.sel.contains(pos) > -1) { + cm.state.draggingText(e) + // Ensure the editor is re-focused + setTimeout(() => cm.display.input.focus(), 20) + return + } + try { + let text = e.dataTransfer.getData("Text") + if (text) { + let selected + if (cm.state.draggingText && !cm.state.draggingText.copy) + selected = cm.listSelections() + setSelectionNoUndo(cm.doc, simpleSelection(pos, pos)) + if (selected) for (let i = 0; i < selected.length; ++i) + replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag") + cm.replaceSelection(text, "around", "paste") + cm.display.input.focus() + } + } + catch(e){} + } +} + +export 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 + + e.dataTransfer.setData("Text", cm.getSelection()) + e.dataTransfer.effectAllowed = "copyMove" + + // 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) { + let img = elt("img", null, null, "position: fixed; left: 0; top: 0;") + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" + if (presto) { + 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 (presto) img.parentNode.removeChild(img) + } +} + +export function onDragOver(cm, e) { + let pos = posFromMouse(cm, e) + if (!pos) return + let frag = document.createDocumentFragment() + drawSelectionCursor(cm, pos, frag) + if (!cm.display.dragCursor) { + cm.display.dragCursor = elt("div", null, "CodeMirror-cursors CodeMirror-dragcursors") + cm.display.lineSpace.insertBefore(cm.display.dragCursor, cm.display.cursorDiv) + } + removeChildrenAndAdd(cm.display.dragCursor, frag) +} + +export function clearDragCursor(cm) { + if (cm.display.dragCursor) { + cm.display.lineSpace.removeChild(cm.display.dragCursor) + cm.display.dragCursor = null + } +} diff --git a/modules/editor/codemirror/src/edit/fromTextArea.min.js b/modules/editor/codemirror/src/edit/fromTextArea.min.js @@ -0,0 +1,61 @@ +import { CodeMirror } from "./CodeMirror.js" +import { activeElt } from "../util/dom.js" +import { off, on } from "../util/event.js" +import { copyObj } from "../util/misc.js" + +export function fromTextArea(textarea, options) { + options = options ? copyObj(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) { + let hasFocus = activeElt() + options.autofocus = hasFocus == textarea || + textarea.getAttribute("autofocus") != null && hasFocus == document.body + } + + function save() {textarea.value = cm.getValue()} + + let realSubmit + if (textarea.form) { + on(textarea.form, "submit", save) + // Deplorable hack to make the submit method do the right thing. + if (!options.leaveSubmitMethodAlone) { + let form = textarea.form + realSubmit = form.submit + try { + let wrappedSubmit = form.submit = () => { + save() + form.submit = realSubmit + form.submit() + form.submit = wrappedSubmit + } + } catch(e) {} + } + } + + options.finishInit = cm => { + cm.save = save + cm.getTextArea = () => textarea + cm.toTextArea = () => { + cm.toTextArea = isNaN // Prevent this from being ran twice + save() + textarea.parentNode.removeChild(cm.getWrapperElement()) + textarea.style.display = "" + if (textarea.form) { + off(textarea.form, "submit", save) + if (typeof textarea.form.submit == "function") + textarea.form.submit = realSubmit + } + } + } + + textarea.style.display = "none" + let cm = CodeMirror(node => textarea.parentNode.insertBefore(node, textarea.nextSibling), + options) + return cm +} diff --git a/modules/editor/codemirror/src/edit/global_events.js b/modules/editor/codemirror/src/edit/global_events.js @@ -0,0 +1,44 @@ +import { onBlur } from "../display/focus.js" +import { on } from "../util/event.js" + +// These must be handled carefully, because naively registering a +// handler for each editor will cause the editors to never be +// garbage collected. + +function forEachCodeMirror(f) { + if (!document.getElementsByClassName) return + let byClass = document.getElementsByClassName("CodeMirror") + for (let i = 0; i < byClass.length; i++) { + let cm = byClass[i].CodeMirror + if (cm) f(cm) + } +} + +let globalsRegistered = false +export function ensureGlobalHandlers() { + if (globalsRegistered) return + registerGlobalHandlers() + globalsRegistered = true +} +function registerGlobalHandlers() { + // When the window resizes, we need to refresh active editors. + let resizeTimer + on(window, "resize", () => { + if (resizeTimer == null) resizeTimer = setTimeout(() => { + resizeTimer = null + forEachCodeMirror(onResize) + }, 100) + }) + // When the window loses focus, we want to show the editor as blurred + on(window, "blur", () => forEachCodeMirror(onBlur)) +} +// Called when the window resizes +function onResize(cm) { + let d = cm.display + if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth) + return + // Might be a text scaling operation, clear size caches. + d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null + d.scrollbarsClipped = false + cm.setSize() +} diff --git a/modules/editor/codemirror/src/edit/methods.min.js b/modules/editor/codemirror/src/edit/methods.min.js @@ -0,0 +1,539 @@ +import { deleteNearSelection } from "./deleteNearSelection.js" +import { commands } from "./commands.js" +import { attachDoc } from "../model/document_data.js" +import { activeElt, addClass, rmClass } from "../util/dom.js" +import { eventMixin, signal } from "../util/event.js" +import { getLineStyles, getContextBefore, takeToken } from "../line/highlight.js" +import { indentLine } from "../input/indent.js" +import { triggerElectric } from "../input/input.js" +import { onKeyDown, onKeyPress, onKeyUp } from "./key_events.js" +import { onMouseDown } from "./mouse_events.js" +import { getKeyMap } from "../input/keymap.js" +import { endOfLine, moveLogically, moveVisually } from "../input/movement.js" +import { endOperation, methodOp, operation, runInOp, startOperation } from "../display/operations.js" +import { clipLine, clipPos, equalCursorPos, Pos } from "../line/pos.js" +import { charCoords, charWidth, clearCaches, clearLineMeasurementCache, coordsChar, cursorCoords, displayHeight, displayWidth, estimateLineHeights, fromCoordSystem, intoCoordSystem, scrollGap, textHeight } from "../measurement/position_measurement.js" +import { Range } from "../model/selection.js" +import { replaceOneSelection, skipAtomic } from "../model/selection_updates.js" +import { addToScrollTop, ensureCursorVisible, scrollIntoView, scrollToCoords, scrollToCoordsRange, scrollToRange } from "../display/scrolling.js" +import { heightAtLine } from "../line/spans.js" +import { updateGutterSpace } from "../display/update_display.js" +import { indexOf, insertSorted, isWordChar, sel_dontScroll, sel_move } from "../util/misc.js" +import { signalLater } from "../util/operation_group.js" +import { getLine, isLine, lineAtHeight } from "../line/utils_line.js" +import { regChange, regLineChange } from "../display/view_tracking.js" + +// The publicly visible API. Note that methodOp(f) means +// 'wrap f in an operation, performed on its `this` parameter'. + +// This is not the complete set of editor methods. Most of the +// methods defined on the Doc type are also injected into +// CodeMirror.prototype, for backwards compatibility and +// convenience. + +export default function(CodeMirror) { + let optionHandlers = CodeMirror.optionHandlers + + let helpers = CodeMirror.helpers = {} + + CodeMirror.prototype = { + constructor: CodeMirror, + focus: function(){window.focus(); this.display.input.focus()}, + + setOption: function(option, value) { + let 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) + signal(this, "optionChange", this, option) + }, + + getOption: function(option) {return this.options[option]}, + getDoc: function() {return this.doc}, + + addKeyMap: function(map, bottom) { + this.state.keyMaps[bottom ? "push" : "unshift"](getKeyMap(map)) + }, + removeKeyMap: function(map) { + let maps = this.state.keyMaps + for (let i = 0; i < maps.length; ++i) + if (maps[i] == map || maps[i].name == map) { + maps.splice(i, 1) + return true + } + }, + + addOverlay: methodOp(function(spec, options) { + let mode = spec.token ? spec : CodeMirror.getMode(this.options, spec) + if (mode.startState) throw new Error("Overlays may not be stateful.") + insertSorted(this.state.overlays, + {mode: mode, modeSpec: spec, opaque: options && options.opaque, + priority: (options && options.priority) || 0}, + overlay => overlay.priority) + this.state.modeGen++ + regChange(this) + }), + removeOverlay: methodOp(function(spec) { + let overlays = this.state.overlays + for (let i = 0; i < overlays.length; ++i) { + let cur = overlays[i].modeSpec + if (cur == spec || typeof spec == "string" && cur.name == spec) { + overlays.splice(i, 1) + this.state.modeGen++ + regChange(this) + return + } + } + }), + + indentLine: methodOp(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: methodOp(function(how) { + let ranges = this.doc.sel.ranges, end = -1 + for (let i = 0; i < ranges.length; i++) { + let range = ranges[i] + if (!range.empty()) { + let from = range.from(), to = range.to() + let start = Math.max(end, from.line) + end = Math.min(this.lastLine(), to.line - (to.ch ? 0 : 1)) + 1 + for (let j = start; j < end; ++j) + indentLine(this, j, how) + let newRanges = this.doc.sel.ranges + if (from.ch == 0 && ranges.length == newRanges.length && newRanges[i].from().ch > 0) + replaceOneSelection(this.doc, i, new Range(from, newRanges[i].to()), sel_dontScroll) + } else if (range.head.line > end) { + indentLine(this, range.head.line, how, true) + end = range.head.line + if (i == this.doc.sel.primIndex) ensureCursorVisible(this) + } + } + }), + + // 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) { + return takeToken(this, pos, precise) + }, + + getLineTokens: function(line, precise) { + return takeToken(this, Pos(line), precise, true) + }, + + getTokenTypeAt: function(pos) { + pos = clipPos(this.doc, pos) + let styles = getLineStyles(this, getLine(this.doc, pos.line)) + let before = 0, after = (styles.length - 1) / 2, ch = pos.ch + let type + if (ch == 0) type = styles[2] + else for (;;) { + let 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 { type = styles[mid * 2 + 2]; break } + } + let cut = type ? type.indexOf("overlay ") : -1 + return cut < 0 ? type : cut == 0 ? null : type.slice(0, cut - 1) + }, + + getModeAt: function(pos) { + let mode = this.doc.mode + if (!mode.innerMode) return mode + return CodeMirror.innerMode(mode, this.getTokenAt(pos).state).mode + }, + + getHelper: function(pos, type) { + return this.getHelpers(pos, type)[0] + }, + + getHelpers: function(pos, type) { + let found = [] + if (!helpers.hasOwnProperty(type)) return found + let help = helpers[type], mode = this.getModeAt(pos) + if (typeof mode[type] == "string") { + if (help[mode[type]]) found.push(help[mode[type]]) + } else if (mode[type]) { + for (let i = 0; i < mode[type].length; i++) { + let val = help[mode[type][i]] + if (val) found.push(val) + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]) + } else if (help[mode.name]) { + found.push(help[mode.name]) + } + for (let i = 0; i < help._global.length; i++) { + let cur = help._global[i] + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + found.push(cur.val) + } + return found + }, + + getStateAfter: function(line, precise) { + let doc = this.doc + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line) + return getContextBefore(this, line + 1, precise).state + }, + + cursorCoords: function(start, mode) { + let pos, range = this.doc.sel.primary() + if (start == null) pos = range.head + else if (typeof start == "object") pos = clipPos(this.doc, start) + else pos = start ? range.from() : range.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, includeWidgets) { + let end = false, lineObj + if (typeof line == "number") { + let 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 } + lineObj = getLine(this.doc, line) + } else { + lineObj = line + } + return intoCoordSystem(this, lineObj, {top: 0, left: 0}, mode || "page", includeWidgets || end).top + + (end ? this.doc.height - heightAtLine(lineObj) : 0) + }, + + defaultTextHeight: function() { return textHeight(this.display) }, + defaultCharWidth: function() { return charWidth(this.display) }, + + getViewport: function() { return {from: this.display.viewFrom, to: this.display.viewTo}}, + + addWidget: function(pos, node, scroll, vert, horiz) { + let display = this.display + pos = cursorCoords(this, clipPos(this.doc, pos)) + let top = pos.bottom, left = pos.left + node.style.position = "absolute" + node.setAttribute("cm-ignore-events", "true") + this.display.input.setUneditable(node) + display.sizer.appendChild(node) + if (vert == "over") { + top = pos.top + } else if (vert == "above" || vert == "near") { + let 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, right: left + node.offsetWidth, bottom: top + node.offsetHeight}) + }, + + triggerOnKeyDown: methodOp(onKeyDown), + triggerOnKeyPress: methodOp(onKeyPress), + triggerOnKeyUp: onKeyUp, + triggerOnMouseDown: methodOp(onMouseDown), + + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + return commands[cmd].call(null, this) + }, + + triggerElectric: methodOp(function(text) { triggerElectric(this, text) }), + + findPosH: function(from, amount, unit, visually) { + let dir = 1 + if (amount < 0) { dir = -1; amount = -amount } + let cur = clipPos(this.doc, from) + for (let i = 0; i < amount; ++i) { + cur = findPosH(this.doc, cur, dir, unit, visually) + if (cur.hitSide) break + } + return cur + }, + + moveH: methodOp(function(dir, unit) { + this.extendSelectionsBy(range => { + if (this.display.shift || this.doc.extend || range.empty()) + return findPosH(this.doc, range.head, dir, unit, this.options.rtlMoveVisually) + else + return dir < 0 ? range.from() : range.to() + }, sel_move) + }), + + deleteH: methodOp(function(dir, unit) { + let sel = this.doc.sel, doc = this.doc + if (sel.somethingSelected()) + doc.replaceSelection("", null, "+delete") + else + deleteNearSelection(this, range => { + let other = findPosH(doc, range.head, dir, unit, false) + return dir < 0 ? {from: other, to: range.head} : {from: range.head, to: other} + }) + }), + + findPosV: function(from, amount, unit, goalColumn) { + let dir = 1, x = goalColumn + if (amount < 0) { dir = -1; amount = -amount } + let cur = clipPos(this.doc, from) + for (let i = 0; i < amount; ++i) { + let 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: methodOp(function(dir, unit) { + let doc = this.doc, goals = [] + let collapse = !this.display.shift && !doc.extend && doc.sel.somethingSelected() + doc.extendSelectionsBy(range => { + if (collapse) + return dir < 0 ? range.from() : range.to() + let headPos = cursorCoords(this, range.head, "div") + if (range.goalColumn != null) headPos.left = range.goalColumn + goals.push(headPos.left) + let pos = findPosV(this, headPos, dir, unit) + if (unit == "page" && range == doc.sel.primary()) + addToScrollTop(this, charCoords(this, pos, "div").top - headPos.top) + return pos + }, sel_move) + if (goals.length) for (let i = 0; i < doc.sel.ranges.length; i++) + doc.sel.ranges[i].goalColumn = goals[i] + }), + + // Find the word at the given position (as returned by coordsChar). + findWordAt: function(pos) { + let doc = this.doc, line = getLine(doc, pos.line).text + let start = pos.ch, end = pos.ch + if (line) { + let helper = this.getHelper(pos, "wordChars") + if ((pos.sticky == "before" || end == line.length) && start) --start; else ++end + let startChar = line.charAt(start) + let check = isWordChar(startChar, helper) + ? ch => isWordChar(ch, helper) + : /\s/.test(startChar) ? ch => /\s/.test(ch) + : ch => (!/\s/.test(ch) && !isWordChar(ch)) + while (start > 0 && check(line.charAt(start - 1))) --start + while (end < line.length && check(line.charAt(end))) ++end + } + return new Range(Pos(pos.line, start), Pos(pos.line, end)) + }, + + toggleOverwrite: function(value) { + if (value != null && value == this.state.overwrite) return + if (this.state.overwrite = !this.state.overwrite) + addClass(this.display.cursorDiv, "CodeMirror-overwrite") + else + rmClass(this.display.cursorDiv, "CodeMirror-overwrite") + + signal(this, "overwriteToggle", this, this.state.overwrite) + }, + hasFocus: function() { return this.display.input.getField() == activeElt() }, + isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) }, + + scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y) }), + getScrollInfo: function() { + let scroller = this.display.scroller + return {left: scroller.scrollLeft, top: scroller.scrollTop, + height: scroller.scrollHeight - scrollGap(this) - this.display.barHeight, + width: scroller.scrollWidth - scrollGap(this) - this.display.barWidth, + clientHeight: displayHeight(this), clientWidth: displayWidth(this)} + }, + + scrollIntoView: methodOp(function(range, margin) { + if (range == null) { + range = {from: this.doc.sel.primary().head, to: null} + if (margin == null) margin = this.options.cursorScrollMargin + } 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 + range.margin = margin || 0 + + if (range.from.line != null) { + scrollToRange(this, range) + } else { + scrollToCoordsRange(this, range.from, range.to, range.margin) + } + }), + + setSize: methodOp(function(width, height) { + let interpret = val => typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val + if (width != null) this.display.wrapper.style.width = interpret(width) + if (height != null) this.display.wrapper.style.height = interpret(height) + if (this.options.lineWrapping) clearLineMeasurementCache(this) + let lineNo = this.display.viewFrom + this.doc.iter(lineNo, this.display.viewTo, line => { + if (line.widgets) for (let i = 0; i < line.widgets.length; i++) + if (line.widgets[i].noHScroll) { regLineChange(this, lineNo, "widget"); break } + ++lineNo + }) + this.curOp.forceUpdate = true + signal(this, "refresh", this) + }), + + operation: function(f){return runInOp(this, f)}, + startOperation: function(){return startOperation(this)}, + endOperation: function(){return endOperation(this)}, + + refresh: methodOp(function() { + let oldHeight = this.display.cachedTextHeight + regChange(this) + this.curOp.forceUpdate = true + clearCaches(this) + scrollToCoords(this, this.doc.scrollLeft, this.doc.scrollTop) + updateGutterSpace(this) + if (oldHeight == null || Math.abs(oldHeight - textHeight(this.display)) > .5) + estimateLineHeights(this) + signal(this, "refresh", this) + }), + + swapDoc: methodOp(function(doc) { + let old = this.doc + old.cm = null + attachDoc(this, doc) + clearCaches(this) + this.display.input.reset() + scrollToCoords(this, doc.scrollLeft, doc.scrollTop) + this.curOp.forceScroll = true + signalLater(this, "swapDoc", this, old) + return old + }), + + getInputField: function(){return this.display.input.getField()}, + getWrapperElement: function(){return this.display.wrapper}, + getScrollerElement: function(){return this.display.scroller}, + getGutterElement: function(){return this.display.gutters} + } + eventMixin(CodeMirror) + + CodeMirror.registerHelper = function(type, name, value) { + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []} + helpers[type][name] = value + } + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value) + helpers[type]._global.push({pred: predicate, val: value}) + } +} + +// Used for horizontal relative motion. Dir is -1 or 1 (left or +// right), unit can be "char", "column" (like char, but doesn't +// cross line boundaries), "word" (across next word), or "group" (to +// the start of next group of word or non-word-non-whitespace +// chars). The visually param controls whether, in right-to-left +// text, direction 1 means to move towards the next index in the +// string, or towards the character to the right of the current +// position. The resulting position will have a hitSide=true +// property if it reached the end of the document. +function findPosH(doc, pos, dir, unit, visually) { + let oldPos = pos + let origDir = dir + let lineObj = getLine(doc, pos.line) + function findNextLine() { + let l = pos.line + dir + if (l < doc.first || l >= doc.first + doc.size) return false + pos = new Pos(l, pos.ch, pos.sticky) + return lineObj = getLine(doc, l) + } + function moveOnce(boundToLine) { + let next + if (visually) { + next = moveVisually(doc.cm, lineObj, pos, dir) + } else { + next = moveLogically(lineObj, pos, dir) + } + if (next == null) { + if (!boundToLine && findNextLine()) + pos = endOfLine(visually, doc.cm, lineObj, pos.line, dir) + else + return false + } else { + pos = next + } + return true + } + + if (unit == "char") { + moveOnce() + } else if (unit == "column") { + moveOnce(true) + } else if (unit == "word" || unit == "group") { + let sawType = null, group = unit == "group" + let helper = doc.cm && doc.cm.getHelper(pos, "wordChars") + for (let first = true;; first = false) { + if (dir < 0 && !moveOnce(!first)) break + let cur = lineObj.text.charAt(pos.ch) || "\n" + let type = isWordChar(cur, helper) ? "w" + : group && cur == "\n" ? "n" + : !group || /\s/.test(cur) ? null + : "p" + if (group && !first && !type) type = "s" + if (sawType && sawType != type) { + if (dir < 0) {dir = 1; moveOnce(); pos.sticky = "after"} + break + } + + if (type) sawType = type + if (dir > 0 && !moveOnce(!first)) break + } + } + let result = skipAtomic(doc, pos, oldPos, origDir, true) + if (equalCursorPos(oldPos, result)) result.hitSide = true + return result +} + +// For relative vertical movement. Dir may be -1 or 1. Unit can be +// "page" or "line". The resulting position will have a hitSide=true +// property if it reached the end of the document. +function findPosV(cm, pos, dir, unit) { + let doc = cm.doc, x = pos.left, y + if (unit == "page") { + let pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight) + let moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3) + y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount + + } else if (unit == "line") { + y = dir > 0 ? pos.bottom + 3 : pos.top - 3 + } + let target + for (;;) { + 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 +} diff --git a/modules/editor/codemirror/src/edit/options.js b/modules/editor/codemirror/src/edit/options.js @@ -0,0 +1,190 @@ +import { onBlur } from "../display/focus.js" +import { setGuttersForLineNumbers, updateGutters } from "../display/gutters.js" +import { alignHorizontally } from "../display/line_numbers.js" +import { loadMode, resetModeState } from "../display/mode_state.js" +import { initScrollbars, updateScrollbars } from "../display/scrollbars.js" +import { updateSelection } from "../display/selection.js" +import { regChange } from "../display/view_tracking.js" +import { getKeyMap } from "../input/keymap.js" +import { defaultSpecialCharPlaceholder } from "../line/line_data.js" +import { Pos } from "../line/pos.js" +import { findMaxLine } from "../line/spans.js" +import { clearCaches, compensateForHScroll, estimateLineHeights } from "../measurement/position_measurement.js" +import { replaceRange } from "../model/changes.js" +import { mobile, windows } from "../util/browser.js" +import { addClass, rmClass } from "../util/dom.js" +import { off, on } from "../util/event.js" + +import { themeChanged } from "./utils.js" + +export let Init = {toString: function(){return "CodeMirror.Init"}} + +export let defaults = {} +export let optionHandlers = {} + +export function defineOptions(CodeMirror) { + let optionHandlers = CodeMirror.optionHandlers + + function option(name, deflt, handle, notOnInit) { + CodeMirror.defaults[name] = deflt + if (handle) optionHandlers[name] = + notOnInit ? (cm, val, old) => {if (old != Init) handle(cm, val, old)} : handle + } + + CodeMirror.defineOption = option + + // Passed to option handlers when there is no old value. + CodeMirror.Init = 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", "", (cm, val) => cm.setValue(val), true) + option("mode", null, (cm, val) => { + cm.doc.modeOption = val + loadMode(cm) + }, true) + + option("indentUnit", 2, loadMode, true) + option("indentWithTabs", false) + option("smartIndent", true) + option("tabSize", 4, cm => { + resetModeState(cm) + clearCaches(cm) + regChange(cm) + }, true) + option("lineSeparator", null, (cm, val) => { + cm.doc.lineSep = val + if (!val) return + let newBreaks = [], lineNo = cm.doc.first + cm.doc.iter(line => { + for (let pos = 0;;) { + let found = line.text.indexOf(val, pos) + if (found == -1) break + pos = found + val.length + newBreaks.push(Pos(lineNo, found)) + } + lineNo++ + }) + for (let i = newBreaks.length - 1; i >= 0; i--) + replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)) + }) + option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200f\u2028\u2029\ufeff]/g, (cm, val, old) => { + cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g") + if (old != Init) cm.refresh() + }) + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, cm => cm.refresh(), true) + option("electricChars", true) + option("inputStyle", mobile ? "contenteditable" : "textarea", () => { + throw new Error("inputStyle can not (yet) be changed in a running editor") // FIXME + }, true) + option("spellcheck", false, (cm, val) => cm.getInputField().spellcheck = val, true) + option("rtlMoveVisually", !windows) + option("wholeLineUpdateBefore", true) + + option("theme", "default", cm => { + themeChanged(cm) + guttersChanged(cm) + }, true) + option("keyMap", "default", (cm, val, old) => { + let next = getKeyMap(val) + let prev = old != Init && getKeyMap(old) + if (prev && prev.detach) prev.detach(cm, next) + if (next.attach) next.attach(cm, prev || null) + }) + option("extraKeys", null) + option("configureMouse", null) + + option("lineWrapping", false, wrappingChanged, true) + option("gutters", [], cm => { + setGuttersForLineNumbers(cm.options) + guttersChanged(cm) + }, true) + option("fixedGutter", true, (cm, val) => { + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0" + cm.refresh() + }, true) + option("coverGutterNextToScrollbar", false, cm => updateScrollbars(cm), true) + option("scrollbarStyle", "native", cm => { + initScrollbars(cm) + updateScrollbars(cm) + cm.display.scrollbars.setScrollTop(cm.doc.scrollTop) + cm.display.scrollbars.setScrollLeft(cm.doc.scrollLeft) + }, true) + option("lineNumbers", false, cm => { + setGuttersForLineNumbers(cm.options) + guttersChanged(cm) + }, true) + option("firstLineNumber", 1, guttersChanged, true) + option("lineNumberFormatter", integer => integer, guttersChanged, true) + option("showCursorWhenSelecting", false, updateSelection, true) + + option("resetSelectionOnContextMenu", true) + option("lineWiseCopyCut", true) + option("pasteLinesPerSelection", true) + + option("readOnly", false, (cm, val) => { + if (val == "nocursor") { + onBlur(cm) + cm.display.input.blur() + } + cm.display.input.readOnlyChanged(val) + }) + option("disableInput", false, (cm, val) => {if (!val) cm.display.input.reset()}, true) + option("dragDrop", true, dragDropChanged) + option("allowDropFileTypes", null) + + option("cursorBlinkRate", 530) + option("cursorScrollMargin", 0) + option("cursorHeight", 1, updateSelection, true) + option("singleCursorHeightPerLine", true, updateSelection, true) + option("workTime", 100) + option("workDelay", 100) + option("flattenSpans", true, resetModeState, true) + option("addModeClass", false, resetModeState, true) + option("pollInterval", 100) + option("undoDepth", 200, (cm, val) => cm.doc.history.undoDepth = val) + option("historyEventDelay", 1250) + option("viewportMargin", 10, cm => cm.refresh(), true) + option("maxHighlightLength", 10000, resetModeState, true) + option("moveInputWithCursor", true, (cm, val) => { + if (!val) cm.display.input.resetPosition() + }) + + option("tabindex", null, (cm, val) => cm.display.input.getField().tabIndex = val || "") + option("autofocus", null) + option("direction", "ltr", (cm, val) => cm.doc.setDirection(val), true) +} + +function guttersChanged(cm) { + updateGutters(cm) + regChange(cm) + alignHorizontally(cm) +} + +function dragDropChanged(cm, value, old) { + let wasOn = old && old != Init + if (!value != !wasOn) { + let funcs = cm.display.dragFunctions + let toggle = value ? on : off + toggle(cm.display.scroller, "dragstart", funcs.start) + toggle(cm.display.scroller, "dragenter", funcs.enter) + toggle(cm.display.scroller, "dragover", funcs.over) + toggle(cm.display.scroller, "dragleave", funcs.leave) + toggle(cm.display.scroller, "drop", funcs.drop) + } +} + +function wrappingChanged(cm) { + if (cm.options.lineWrapping) { + addClass(cm.display.wrapper, "CodeMirror-wrap") + cm.display.sizer.style.minWidth = "" + cm.display.sizerWidth = null + } else { + rmClass(cm.display.wrapper, "CodeMirror-wrap") + findMaxLine(cm) + } + estimateLineHeights(cm) + regChange(cm) + clearCaches(cm) + setTimeout(() => updateScrollbars(cm), 100) +} diff --git a/modules/editor/codemirror/src/edit/utils.js b/modules/editor/codemirror/src/edit/utils.js @@ -0,0 +1,7 @@ +import { clearCaches } from "../measurement/position_measurement.js" + +export 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) +} diff --git a/modules/editor/codemirror/src/input/ContentEditableInput.min.js b/modules/editor/codemirror/src/input/ContentEditableInput.min.js @@ -0,0 +1,517 @@ +import { operation, runInOp } from "../display/operations.js" +import { prepareSelection } from "../display/selection.js" +import { regChange } from "../display/view_tracking.js" +import { applyTextInput, copyableRanges, disableBrowserMagic, handlePaste, hiddenTextarea, lastCopied, setLastCopied } from "./input.js" +import { cmp, maxPos, minPos, Pos } from "../line/pos.js" +import { getBetween, getLine, lineNo } from "../line/utils_line.js" +import { findViewForLine, findViewIndex, mapFromLineView, nodeAndOffsetInLineMap } from "../measurement/position_measurement.js" +import { replaceRange } from "../model/changes.js" +import { simpleSelection } from "../model/selection.js" +import { setSelection } from "../model/selection_updates.js" +import { getBidiPartAt, getOrder } from "../util/bidi.js" +import { android, chrome, gecko, ie_version } from "../util/browser.js" +import { contains, range, removeChildrenAndAdd, selectInput } from "../util/dom.js" +import { on, signalDOMEvent } from "../util/event.js" +import { Delayed, lst, sel_dontScroll } from "../util/misc.js" + +// CONTENTEDITABLE INPUT STYLE + +export default class ContentEditableInput { + constructor(cm) { + this.cm = cm + this.lastAnchorNode = this.lastAnchorOffset = this.lastFocusNode = this.lastFocusOffset = null + this.polling = new Delayed() + this.composing = null + this.gracePeriod = false + this.readDOMTimeout = null + } + + init(display) { + let input = this, cm = input.cm + let div = input.div = display.lineDiv + disableBrowserMagic(div, cm.options.spellcheck) + + on(div, "paste", e => { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return + // IE doesn't fire input events, so we schedule a read for the pasted content in this way + if (ie_version <= 11) setTimeout(operation(cm, () => this.updateFromDOM()), 20) + }) + + on(div, "compositionstart", e => { + this.composing = {data: e.data, done: false} + }) + on(div, "compositionupdate", e => { + if (!this.composing) this.composing = {data: e.data, done: false} + }) + on(div, "compositionend", e => { + if (this.composing) { + if (e.data != this.composing.data) this.readFromDOMSoon() + this.composing.done = true + } + }) + + on(div, "touchstart", () => input.forceCompositionEnd()) + + on(div, "input", () => { + if (!this.composing) this.readFromDOMSoon() + }) + + function onCopyCut(e) { + if (signalDOMEvent(cm, e)) return + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}) + if (e.type == "cut") cm.replaceSelection("", null, "cut") + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + let ranges = copyableRanges(cm) + setLastCopied({lineWise: true, text: ranges.text}) + if (e.type == "cut") { + cm.operation(() => { + cm.setSelections(ranges.ranges, 0, sel_dontScroll) + cm.replaceSelection("", null, "cut") + }) + } + } + if (e.clipboardData) { + e.clipboardData.clearData() + let content = lastCopied.text.join("\n") + // iOS exposes the clipboard API, but seems to discard content inserted into it + e.clipboardData.setData("Text", content) + if (e.clipboardData.getData("Text") == content) { + e.preventDefault() + return + } + } + // Old-fashioned briefly-focus-a-textarea hack + let kludge = hiddenTextarea(), te = kludge.firstChild + cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild) + te.value = lastCopied.text.join("\n") + let hadFocus = document.activeElement + selectInput(te) + setTimeout(() => { + cm.display.lineSpace.removeChild(kludge) + hadFocus.focus() + if (hadFocus == div) input.showPrimarySelection() + }, 50) + } + on(div, "copy", onCopyCut) + on(div, "cut", onCopyCut) + } + + prepareSelection() { + let result = prepareSelection(this.cm, false) + result.focus = this.cm.state.focused + return result + } + + showSelection(info, takeFocus) { + if (!info || !this.cm.display.view.length) return + if (info.focus || takeFocus) this.showPrimarySelection() + this.showMultipleSelections(info) + } + + showPrimarySelection() { + let sel = window.getSelection(), cm = this.cm, prim = cm.doc.sel.primary() + let from = prim.from(), to = prim.to() + + if (cm.display.viewTo == cm.display.viewFrom || from.line >= cm.display.viewTo || to.line < cm.display.viewFrom) { + sel.removeAllRanges() + return + } + + let curAnchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) + let curFocus = domToPos(cm, sel.focusNode, sel.focusOffset) + if (curAnchor && !curAnchor.bad && curFocus && !curFocus.bad && + cmp(minPos(curAnchor, curFocus), from) == 0 && + cmp(maxPos(curAnchor, curFocus), to) == 0) + return + + let view = cm.display.view + let start = (from.line >= cm.display.viewFrom && posToDOM(cm, from)) || + {node: view[0].measure.map[2], offset: 0} + let end = to.line < cm.display.viewTo && posToDOM(cm, to) + if (!end) { + let measure = view[view.length - 1].measure + let map = measure.maps ? measure.maps[measure.maps.length - 1] : measure.map + end = {node: map[map.length - 1], offset: map[map.length - 2] - map[map.length - 3]} + } + + if (!start || !end) { + sel.removeAllRanges() + return + } + + let old = sel.rangeCount && sel.getRangeAt(0), rng + try { rng = range(start.node, start.offset, end.offset, end.node) } + catch(e) {} // Our model of the DOM might be outdated, in which case the range we try to set can be impossible + if (rng) { + if (!gecko && cm.state.focused) { + sel.collapse(start.node, start.offset) + if (!rng.collapsed) { + sel.removeAllRanges() + sel.addRange(rng) + } + } else { + sel.removeAllRanges() + sel.addRange(rng) + } + if (old && sel.anchorNode == null) sel.addRange(old) + else if (gecko) this.startGracePeriod() + } + this.rememberSelection() + } + + startGracePeriod() { + clearTimeout(this.gracePeriod) + this.gracePeriod = setTimeout(() => { + this.gracePeriod = false + if (this.selectionChanged()) + this.cm.operation(() => this.cm.curOp.selectionChanged = true) + }, 20) + } + + showMultipleSelections(info) { + removeChildrenAndAdd(this.cm.display.cursorDiv, info.cursors) + removeChildrenAndAdd(this.cm.display.selectionDiv, info.selection) + } + + rememberSelection() { + let sel = window.getSelection() + this.lastAnchorNode = sel.anchorNode; this.lastAnchorOffset = sel.anchorOffset + this.lastFocusNode = sel.focusNode; this.lastFocusOffset = sel.focusOffset + } + + selectionInEditor() { + let sel = window.getSelection() + if (!sel.rangeCount) return false + let node = sel.getRangeAt(0).commonAncestorContainer + return contains(this.div, node) + } + + focus() { + if (this.cm.options.readOnly != "nocursor") { + if (!this.selectionInEditor()) + this.showSelection(this.prepareSelection(), true) + this.div.focus() + } + } + blur() { this.div.blur() } + getField() { return this.div } + + supportsTouch() { return true } + + receivedFocus() { + let input = this + if (this.selectionInEditor()) + this.pollSelection() + else + runInOp(this.cm, () => input.cm.curOp.selectionChanged = true) + + function poll() { + if (input.cm.state.focused) { + input.pollSelection() + input.polling.set(input.cm.options.pollInterval, poll) + } + } + this.polling.set(this.cm.options.pollInterval, poll) + } + + selectionChanged() { + let sel = window.getSelection() + return sel.anchorNode != this.lastAnchorNode || sel.anchorOffset != this.lastAnchorOffset || + sel.focusNode != this.lastFocusNode || sel.focusOffset != this.lastFocusOffset + } + + pollSelection() { + if (this.readDOMTimeout != null || this.gracePeriod || !this.selectionChanged()) return + let sel = window.getSelection(), cm = this.cm + // On Android Chrome (version 56, at least), backspacing into an + // uneditable block element will put the cursor in that element, + // and then, because it's not editable, hide the virtual keyboard. + // Because Android doesn't allow us to actually detect backspace + // presses in a sane way, this code checks for when that happens + // and simulates a backspace press in this case. + if (android && chrome && this.cm.options.gutters.length && isInGutter(sel.anchorNode)) { + this.cm.triggerOnKeyDown({type: "keydown", keyCode: 8, preventDefault: Math.abs}) + this.blur() + this.focus() + return + } + if (this.composing) return + this.rememberSelection() + let anchor = domToPos(cm, sel.anchorNode, sel.anchorOffset) + let head = domToPos(cm, sel.focusNode, sel.focusOffset) + if (anchor && head) runInOp(cm, () => { + setSelection(cm.doc, simpleSelection(anchor, head), sel_dontScroll) + if (anchor.bad || head.bad) cm.curOp.selectionChanged = true + }) + } + + pollContent() { + if (this.readDOMTimeout != null) { + clearTimeout(this.readDOMTimeout) + this.readDOMTimeout = null + } + + let cm = this.cm, display = cm.display, sel = cm.doc.sel.primary() + let from = sel.from(), to = sel.to() + if (from.ch == 0 && from.line > cm.firstLine()) + from = Pos(from.line - 1, getLine(cm.doc, from.line - 1).length) + if (to.ch == getLine(cm.doc, to.line).text.length && to.line < cm.lastLine()) + to = Pos(to.line + 1, 0) + if (from.line < display.viewFrom || to.line > display.viewTo - 1) return false + + let fromIndex, fromLine, fromNode + if (from.line == display.viewFrom || (fromIndex = findViewIndex(cm, from.line)) == 0) { + fromLine = lineNo(display.view[0].line) + fromNode = display.view[0].node + } else { + fromLine = lineNo(display.view[fromIndex].line) + fromNode = display.view[fromIndex - 1].node.nextSibling + } + let toIndex = findViewIndex(cm, to.line) + let toLine, toNode + if (toIndex == display.view.length - 1) { + toLine = display.viewTo - 1 + toNode = display.lineDiv.lastChild + } else { + toLine = lineNo(display.view[toIndex + 1].line) - 1 + toNode = display.view[toIndex + 1].node.previousSibling + } + + if (!fromNode) return false + let newText = cm.doc.splitLines(domTextBetween(cm, fromNode, toNode, fromLine, toLine)) + let oldText = getBetween(cm.doc, Pos(fromLine, 0), Pos(toLine, getLine(cm.doc, toLine).text.length)) + while (newText.length > 1 && oldText.length > 1) { + if (lst(newText) == lst(oldText)) { newText.pop(); oldText.pop(); toLine-- } + else if (newText[0] == oldText[0]) { newText.shift(); oldText.shift(); fromLine++ } + else break + } + + let cutFront = 0, cutEnd = 0 + let newTop = newText[0], oldTop = oldText[0], maxCutFront = Math.min(newTop.length, oldTop.length) + while (cutFront < maxCutFront && newTop.charCodeAt(cutFront) == oldTop.charCodeAt(cutFront)) + ++cutFront + let newBot = lst(newText), oldBot = lst(oldText) + let maxCutEnd = Math.min(newBot.length - (newText.length == 1 ? cutFront : 0), + oldBot.length - (oldText.length == 1 ? cutFront : 0)) + while (cutEnd < maxCutEnd && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) + ++cutEnd + // Try to move start of change to start of selection if ambiguous + if (newText.length == 1 && oldText.length == 1 && fromLine == from.line) { + while (cutFront && cutFront > from.ch && + newBot.charCodeAt(newBot.length - cutEnd - 1) == oldBot.charCodeAt(oldBot.length - cutEnd - 1)) { + cutFront-- + cutEnd++ + } + } + + newText[newText.length - 1] = newBot.slice(0, newBot.length - cutEnd).replace(/^\u200b+/, "") + newText[0] = newText[0].slice(cutFront).replace(/\u200b+$/, "") + + let chFrom = Pos(fromLine, cutFront) + let chTo = Pos(toLine, oldText.length ? lst(oldText).length - cutEnd : 0) + if (newText.length > 1 || newText[0] || cmp(chFrom, chTo)) { + replaceRange(cm.doc, newText, chFrom, chTo, "+input") + return true + } + } + + ensurePolled() { + this.forceCompositionEnd() + } + reset() { + this.forceCompositionEnd() + } + forceCompositionEnd() { + if (!this.composing) return + clearTimeout(this.readDOMTimeout) + this.composing = null + this.updateFromDOM() + this.div.blur() + this.div.focus() + } + readFromDOMSoon() { + if (this.readDOMTimeout != null) return + this.readDOMTimeout = setTimeout(() => { + this.readDOMTimeout = null + if (this.composing) { + if (this.composing.done) this.composing = null + else return + } + this.updateFromDOM() + }, 80) + } + + updateFromDOM() { + if (this.cm.isReadOnly() || !this.pollContent()) + runInOp(this.cm, () => regChange(this.cm)) + } + + setUneditable(node) { + node.contentEditable = "false" + } + + onKeyPress(e) { + if (e.charCode == 0) return + e.preventDefault() + if (!this.cm.isReadOnly()) + operation(this.cm, applyTextInput)(this.cm, String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode), 0) + } + + readOnlyChanged(val) { + this.div.contentEditable = String(val != "nocursor") + } + + onContextMenu() {} + resetPosition() {} +} + +ContentEditableInput.prototype.needsContentAttribute = true + +function posToDOM(cm, pos) { + let view = findViewForLine(cm, pos.line) + if (!view || view.hidden) return null + let line = getLine(cm.doc, pos.line) + let info = mapFromLineView(view, line, pos.line) + + let order = getOrder(line, cm.doc.direction), side = "left" + if (order) { + let partPos = getBidiPartAt(order, pos.ch) + side = partPos % 2 ? "right" : "left" + } + let result = nodeAndOffsetInLineMap(info.map, pos.ch, side) + result.offset = result.collapse == "right" ? result.end : result.start + return result +} + +function isInGutter(node) { + for (let scan = node; scan; scan = scan.parentNode) + if (/CodeMirror-gutter-wrapper/.test(scan.className)) return true + return false +} + +function badPos(pos, bad) { if (bad) pos.bad = true; return pos } + +function domTextBetween(cm, from, to, fromLine, toLine) { + let text = "", closing = false, lineSep = cm.doc.lineSeparator() + function recognizeMarker(id) { return marker => marker.id == id } + function close() { + if (closing) { + text += lineSep + closing = false + } + } + function addText(str) { + if (str) { + close() + text += str + } + } + function walk(node) { + if (node.nodeType == 1) { + let cmText = node.getAttribute("cm-text") + if (cmText != null) { + addText(cmText || node.textContent.replace(/\u200b/g, "")) + return + } + let markerID = node.getAttribute("cm-marker"), range + if (markerID) { + let found = cm.findMarks(Pos(fromLine, 0), Pos(toLine + 1, 0), recognizeMarker(+markerID)) + if (found.length && (range = found[0].find(0))) + addText(getBetween(cm.doc, range.from, range.to).join(lineSep)) + return + } + if (node.getAttribute("contenteditable") == "false") return + let isBlock = /^(pre|div|p)$/i.test(node.nodeName) + if (isBlock) close() + for (let i = 0; i < node.childNodes.length; i++) + walk(node.childNodes[i]) + if (isBlock) closing = true + } else if (node.nodeType == 3) { + addText(node.nodeValue) + } + } + for (;;) { + walk(from) + if (from == to) break + from = from.nextSibling + } + return text +} + +function domToPos(cm, node, offset) { + let lineNode + if (node == cm.display.lineDiv) { + lineNode = cm.display.lineDiv.childNodes[offset] + if (!lineNode) return badPos(cm.clipPos(Pos(cm.display.viewTo - 1)), true) + node = null; offset = 0 + } else { + for (lineNode = node;; lineNode = lineNode.parentNode) { + if (!lineNode || lineNode == cm.display.lineDiv) return null + if (lineNode.parentNode && lineNode.parentNode == cm.display.lineDiv) break + } + } + for (let i = 0; i < cm.display.view.length; i++) { + let lineView = cm.display.view[i] + if (lineView.node == lineNode) + return locateNodeInLineView(lineView, node, offset) + } +} + +function locateNodeInLineView(lineView, node, offset) { + let wrapper = lineView.text.firstChild, bad = false + if (!node || !contains(wrapper, node)) return badPos(Pos(lineNo(lineView.line), 0), true) + if (node == wrapper) { + bad = true + node = wrapper.childNodes[offset] + offset = 0 + if (!node) { + let line = lineView.rest ? lst(lineView.rest) : lineView.line + return badPos(Pos(lineNo(line), line.text.length), bad) + } + } + + let textNode = node.nodeType == 3 ? node : null, topNode = node + if (!textNode && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + textNode = node.firstChild + if (offset) offset = textNode.nodeValue.length + } + while (topNode.parentNode != wrapper) topNode = topNode.parentNode + let measure = lineView.measure, maps = measure.maps + + function find(textNode, topNode, offset) { + for (let i = -1; i < (maps ? maps.length : 0); i++) { + let map = i < 0 ? measure.map : maps[i] + for (let j = 0; j < map.length; j += 3) { + let curNode = map[j + 2] + if (curNode == textNode || curNode == topNode) { + let line = lineNo(i < 0 ? lineView.line : lineView.rest[i]) + let ch = map[j] + offset + if (offset < 0 || curNode != textNode) ch = map[j + (offset ? 1 : 0)] + return Pos(line, ch) + } + } + } + } + let found = find(textNode, topNode, offset) + if (found) return badPos(found, bad) + + // FIXME this is all really shaky. might handle the few cases it needs to handle, but likely to cause problems + for (let after = topNode.nextSibling, dist = textNode ? textNode.nodeValue.length - offset : 0; after; after = after.nextSibling) { + found = find(after, after.firstChild, 0) + if (found) + return badPos(Pos(found.line, found.ch - dist), bad) + else + dist += after.textContent.length + } + for (let before = topNode.previousSibling, dist = offset; before; before = before.previousSibling) { + found = find(before, before.firstChild, -1) + if (found) + return badPos(Pos(found.line, found.ch + dist), bad) + else + dist += before.textContent.length + } +} diff --git a/modules/editor/codemirror/src/input/TextareaInput.js b/modules/editor/codemirror/src/input/TextareaInput.js @@ -0,0 +1,350 @@ +import { operation, runInOp } from "../display/operations.js" +import { prepareSelection } from "../display/selection.js" +import { applyTextInput, copyableRanges, handlePaste, hiddenTextarea, setLastCopied } from "./input.js" +import { cursorCoords, posFromMouse } from "../measurement/position_measurement.js" +import { eventInWidget } from "../measurement/widgets.js" +import { simpleSelection } from "../model/selection.js" +import { selectAll, setSelection } from "../model/selection_updates.js" +import { captureRightClick, ie, ie_version, ios, mac, mobile, presto, webkit } from "../util/browser.js" +import { activeElt, removeChildrenAndAdd, selectInput } from "../util/dom.js" +import { e_preventDefault, e_stop, off, on, signalDOMEvent } from "../util/event.js" +import { hasSelection } from "../util/feature_detection.js" +import { Delayed, sel_dontScroll } from "../util/misc.js" + +// TEXTAREA INPUT STYLE + +export default class TextareaInput { + constructor(cm) { + this.cm = cm + // See input.poll and input.reset + this.prevInput = "" + + // Flag that indicates whether we expect input to appear real soon + // now (after some event like 'keypress' or 'input') and are + // polling intensively. + this.pollingFast = false + // Self-resetting timeout for the poller + this.polling = new Delayed() + // Used to work around IE issue with selection being forgotten when focus moves away from textarea + this.hasSelection = false + this.composing = null + } + + init(display) { + let input = this, cm = this.cm + + // Wraps and hides input textarea + let div = this.wrapper = hiddenTextarea() + // The semihidden textarea that is focused when the editor is + // focused, and receives input. + let te = this.textarea = div.firstChild + display.wrapper.insertBefore(div, display.wrapper.firstChild) + + // Needed to hide big blue blinking cursor on Mobile Safari (doesn't seem to work in iOS 8 anymore) + if (ios) te.style.width = "0px" + + on(te, "input", () => { + if (ie && ie_version >= 9 && this.hasSelection) this.hasSelection = null + input.poll() + }) + + on(te, "paste", e => { + if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return + + cm.state.pasteIncoming = true + input.fastPoll() + }) + + function prepareCopyCut(e) { + if (signalDOMEvent(cm, e)) return + if (cm.somethingSelected()) { + setLastCopied({lineWise: false, text: cm.getSelections()}) + } else if (!cm.options.lineWiseCopyCut) { + return + } else { + let ranges = copyableRanges(cm) + setLastCopied({lineWise: true, text: ranges.text}) + if (e.type == "cut") { + cm.setSelections(ranges.ranges, null, sel_dontScroll) + } else { + input.prevInput = "" + te.value = ranges.text.join("\n") + selectInput(te) + } + } + if (e.type == "cut") cm.state.cutIncoming = true + } + on(te, "cut", prepareCopyCut) + on(te, "copy", prepareCopyCut) + + on(display.scroller, "paste", e => { + if (eventInWidget(display, e) || signalDOMEvent(cm, e)) return + cm.state.pasteIncoming = true + input.focus() + }) + + // Prevent normal selection in the editor (we handle our own) + on(display.lineSpace, "selectstart", e => { + if (!eventInWidget(display, e)) e_preventDefault(e) + }) + + on(te, "compositionstart", () => { + let start = cm.getCursor("from") + if (input.composing) input.composing.range.clear() + input.composing = { + start: start, + range: cm.markText(start, cm.getCursor("to"), {className: "CodeMirror-composing"}) + } + }) + on(te, "compositionend", () => { + if (input.composing) { + input.poll() + input.composing.range.clear() + input.composing = null + } + }) + } + + prepareSelection() { + // Redraw the selection and/or cursor + let cm = this.cm, display = cm.display, doc = cm.doc + let result = prepareSelection(cm) + + // Move the hidden textarea near the cursor to prevent scrolling artifacts + if (cm.options.moveInputWithCursor) { + let headPos = cursorCoords(cm, doc.sel.primary().head, "div") + let wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect() + result.teTop = Math.max(0, Math.min(display.wrapper.clientHeight - 10, + headPos.top + lineOff.top - wrapOff.top)) + result.teLeft = Math.max(0, Math.min(display.wrapper.clientWidth - 10, + headPos.left + lineOff.left - wrapOff.left)) + } + + return result + } + + showSelection(drawn) { + let cm = this.cm, display = cm.display + removeChildrenAndAdd(display.cursorDiv, drawn.cursors) + removeChildrenAndAdd(display.selectionDiv, drawn.selection) + if (drawn.teTop != null) { + this.wrapper.style.top = drawn.teTop + "px" + this.wrapper.style.left = drawn.teLeft + "px" + } + } + + // Reset the input to correspond to the selection (or to be empty, + // when not typing and nothing is selected) + reset(typing) { + if (this.contextMenuPending || this.composing) return + let cm = this.cm + if (cm.somethingSelected()) { + this.prevInput = "" + let content = cm.getSelection() + this.textarea.value = content + if (cm.state.focused) selectInput(this.textarea) + if (ie && ie_version >= 9) this.hasSelection = content + } else if (!typing) { + this.prevInput = this.textarea.value = "" + if (ie && ie_version >= 9) this.hasSelection = null + } + } + + getField() { return this.textarea } + + supportsTouch() { return false } + + focus() { + if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) { + try { this.textarea.focus() } + catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM + } + } + + blur() { this.textarea.blur() } + + resetPosition() { + this.wrapper.style.top = this.wrapper.style.left = 0 + } + + receivedFocus() { this.slowPoll() } + + // Poll for input changes, using the normal rate of polling. This + // runs as long as the editor is focused. + slowPoll() { + if (this.pollingFast) return + this.polling.set(this.cm.options.pollInterval, () => { + this.poll() + if (this.cm.state.focused) this.slowPoll() + }) + } + + // When an event has just come in that is likely to add or change + // something in the input textarea, we poll faster, to ensure that + // the change appears on the screen quickly. + fastPoll() { + let missed = false, input = this + input.pollingFast = true + function p() { + let changed = input.poll() + if (!changed && !missed) {missed = true; input.polling.set(60, p)} + else {input.pollingFast = false; input.slowPoll()} + } + input.polling.set(20, p) + } + + // Read input from the textarea, and update the document to match. + // When something is selected, it is present in the textarea, and + // selected (unless it is huge, in which case a placeholder is + // used). When nothing is selected, the cursor sits after previously + // seen text (can be empty), which is stored in prevInput (we must + // not reset the textarea when typing, because that breaks IME). + poll() { + let cm = this.cm, input = this.textarea, prevInput = this.prevInput + // Since this is called a *lot*, try to bail out as cheaply as + // possible when it is clear that nothing happened. hasSelection + // will be the case when there is a lot of text in the textarea, + // in which case reading its value would be expensive. + if (this.contextMenuPending || !cm.state.focused || + (hasSelection(input) && !prevInput && !this.composing) || + cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq) + return false + + let text = input.value + // If nothing changed, bail. + if (text == prevInput && !cm.somethingSelected()) return false + // Work around nonsensical selection resetting in IE9/10, and + // inexplicable appearance of private area unicode characters on + // some key combos in Mac (#2689). + if (ie && ie_version >= 9 && this.hasSelection === text || + mac && /[\uf700-\uf7ff]/.test(text)) { + cm.display.input.reset() + return false + } + + if (cm.doc.sel == cm.display.selForContextMenu) { + let first = text.charCodeAt(0) + if (first == 0x200b && !prevInput) prevInput = "\u200b" + if (first == 0x21da) { this.reset(); return this.cm.execCommand("undo") } + } + // Find the part of the input that is actually new + let same = 0, l = Math.min(prevInput.length, text.length) + while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same + + runInOp(cm, () => { + applyTextInput(cm, text.slice(same), prevInput.length - same, + null, this.composing ? "*compose" : null) + + // Don't leave long text in the textarea, since it makes further polling slow + if (text.length > 1000 || text.indexOf("\n") > -1) input.value = this.prevInput = "" + else this.prevInput = text + + if (this.composing) { + this.composing.range.clear() + this.composing.range = cm.markText(this.composing.start, cm.getCursor("to"), + {className: "CodeMirror-composing"}) + } + }) + return true + } + + ensurePolled() { + if (this.pollingFast && this.poll()) this.pollingFast = false + } + + onKeyPress() { + if (ie && ie_version >= 9) this.hasSelection = null + this.fastPoll() + } + + onContextMenu(e) { + let input = this, cm = input.cm, display = cm.display, te = input.textarea + let pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop + if (!pos || presto) return // Opera is difficult. + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + let reset = cm.options.resetSelectionOnContextMenu + if (reset && cm.doc.sel.contains(pos) == -1) + operation(cm, setSelection)(cm.doc, simpleSelection(pos), sel_dontScroll) + + let oldCSS = te.style.cssText, oldWrapperCSS = input.wrapper.style.cssText + input.wrapper.style.cssText = "position: absolute" + let wrapperBox = input.wrapper.getBoundingClientRect() + te.style.cssText = `position: absolute; width: 30px; height: 30px; + top: ${e.clientY - wrapperBox.top - 5}px; left: ${e.clientX - wrapperBox.left - 5}px; + z-index: 1000; background: ${ie ? "rgba(255, 255, 255, .05)" : "transparent"}; + outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);` + let oldScrollY + if (webkit) oldScrollY = window.scrollY // Work around Chrome issue (#2712) + display.input.focus() + if (webkit) window.scrollTo(null, oldScrollY) + display.input.reset() + // Adds "Select all" to context menu in FF + if (!cm.somethingSelected()) te.value = input.prevInput = " " + input.contextMenuPending = true + display.selForContextMenu = cm.doc.sel + clearTimeout(display.detectingSelectAll) + + // Select-all will be greyed out if there's nothing to select, so + // this adds a zero-width space so that we can later check whether + // it got selected. + function prepareSelectAllHack() { + if (te.selectionStart != null) { + let selected = cm.somethingSelected() + let extval = "\u200b" + (selected ? te.value : "") + te.value = "\u21da" // Used to catch context-menu undo + te.value = extval + input.prevInput = selected ? "" : "\u200b" + te.selectionStart = 1; te.selectionEnd = extval.length + // Re-set this, in case some other handler touched the + // selection in the meantime. + display.selForContextMenu = cm.doc.sel + } + } + function rehide() { + input.contextMenuPending = false + input.wrapper.style.cssText = oldWrapperCSS + te.style.cssText = oldCSS + if (ie && ie_version < 9) display.scrollbars.setScrollTop(display.scroller.scrollTop = scrollPos) + + // Try to detect the user choosing select-all + if (te.selectionStart != null) { + if (!ie || (ie && ie_version < 9)) prepareSelectAllHack() + let i = 0, poll = () => { + if (display.selForContextMenu == cm.doc.sel && te.selectionStart == 0 && + te.selectionEnd > 0 && input.prevInput == "\u200b") { + operation(cm, selectAll)(cm) + } else if (i++ < 10) { + display.detectingSelectAll = setTimeout(poll, 500) + } else { + display.selForContextMenu = null + display.input.reset() + } + } + display.detectingSelectAll = setTimeout(poll, 200) + } + } + + if (ie && ie_version >= 9) prepareSelectAllHack() + if (captureRightClick) { + e_stop(e) + let mouseup = () => { + off(window, "mouseup", mouseup) + setTimeout(rehide, 20) + } + on(window, "mouseup", mouseup) + } else { + setTimeout(rehide, 50) + } + } + + readOnlyChanged(val) { + if (!val) this.reset() + this.textarea.disabled = val == "nocursor" + } + + setUneditable() {} +} + +TextareaInput.prototype.needsContentAttribute = false diff --git a/modules/editor/codemirror/src/input/keymap.js b/modules/editor/codemirror/src/input/keymap.js @@ -0,0 +1,145 @@ +import { flipCtrlCmd, mac, presto } from "../util/browser.js" +import { map } from "../util/misc.js" + +import { keyNames } from "./keynames.js" + +export let 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", + "Esc": "singleSelection" +} +// Note that the save and find-related commands aren't defined by +// default. User code or addons can define them. 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", "Ctrl-End": "goDocEnd", "Ctrl-Up": "goLineUp", "Ctrl-Down": "goLineDown", + "Ctrl-Left": "goGroupLeft", "Ctrl-Right": "goGroupRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delGroupBefore", "Ctrl-Delete": "delGroupAfter", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + "Ctrl-U": "undoSelection", "Shift-Ctrl-U": "redoSelection", "Alt-U": "redoSelection", + fallthrough: "basic" +} +// Very basic readline/emacs-style bindings, which are standard on Mac. +keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "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", + "Ctrl-O": "openLine" +} +keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Home": "goDocStart", "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goGroupLeft", + "Alt-Right": "goGroupRight", "Cmd-Left": "goLineLeft", "Cmd-Right": "goLineRight", "Alt-Backspace": "delGroupBefore", + "Ctrl-Alt-Backspace": "delGroupAfter", "Alt-Delete": "delGroupAfter", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", "Cmd-Backspace": "delWrappedLineLeft", "Cmd-Delete": "delWrappedLineRight", + "Cmd-U": "undoSelection", "Shift-Cmd-U": "redoSelection", "Ctrl-Up": "goDocStart", "Ctrl-Down": "goDocEnd", + fallthrough: ["basic", "emacsy"] +} +keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault + +// KEYMAP DISPATCH + +function normalizeKeyName(name) { + let parts = name.split(/-(?!$)/) + name = parts[parts.length - 1] + let alt, ctrl, shift, cmd + for (let i = 0; i < parts.length - 1; i++) { + let mod = parts[i] + if (/^(cmd|meta|m)$/i.test(mod)) cmd = true + else if (/^a(lt)?$/i.test(mod)) alt = true + else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true + else if (/^s(hift)?$/i.test(mod)) shift = true + else throw new Error("Unrecognized modifier name: " + mod) + } + if (alt) name = "Alt-" + name + if (ctrl) name = "Ctrl-" + name + if (cmd) name = "Cmd-" + name + if (shift) name = "Shift-" + name + return name +} + +// This is a kludge to keep keymaps mostly working as raw objects +// (backwards compatibility) while at the same time support features +// like normalization and multi-stroke key bindings. It compiles a +// new normalized keymap, and then updates the old object to reflect +// this. +export function normalizeKeyMap(keymap) { + let copy = {} + for (let keyname in keymap) if (keymap.hasOwnProperty(keyname)) { + let value = keymap[keyname] + if (/^(name|fallthrough|(de|at)tach)$/.test(keyname)) continue + if (value == "...") { delete keymap[keyname]; continue } + + let keys = map(keyname.split(" "), normalizeKeyName) + for (let i = 0; i < keys.length; i++) { + let val, name + if (i == keys.length - 1) { + name = keys.join(" ") + val = value + } else { + name = keys.slice(0, i + 1).join(" ") + val = "..." + } + let prev = copy[name] + if (!prev) copy[name] = val + else if (prev != val) throw new Error("Inconsistent bindings for " + name) + } + delete keymap[keyname] + } + for (let prop in copy) keymap[prop] = copy[prop] + return keymap +} + +export function lookupKey(key, map, handle, context) { + map = getKeyMap(map) + let found = map.call ? map.call(key, context) : map[key] + if (found === false) return "nothing" + if (found === "...") return "multi" + if (found != null && handle(found)) return "handled" + + if (map.fallthrough) { + if (Object.prototype.toString.call(map.fallthrough) != "[object Array]") + return lookupKey(key, map.fallthrough, handle, context) + for (let i = 0; i < map.fallthrough.length; i++) { + let result = lookupKey(key, map.fallthrough[i], handle, context) + if (result) return result + } + } +} + +// Modifier key presses don't count as 'real' key presses for the +// purpose of keymap fallthrough. +export function isModifierKey(value) { + let name = typeof value == "string" ? value : keyNames[value.keyCode] + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod" +} + +export function addModifierNames(name, event, noShift) { + let base = name + if (event.altKey && base != "Alt") name = "Alt-" + name + if ((flipCtrlCmd ? event.metaKey : event.ctrlKey) && base != "Ctrl") name = "Ctrl-" + name + if ((flipCtrlCmd ? event.ctrlKey : event.metaKey) && base != "Cmd") name = "Cmd-" + name + if (!noShift && event.shiftKey && base != "Shift") name = "Shift-" + name + return name +} + +// Look up the name of a key as indicated by an event object. +export function keyName(event, noShift) { + if (presto && event.keyCode == 34 && event["char"]) return false + let name = keyNames[event.keyCode] + if (name == null || event.altGraphKey) return false + return addModifierNames(name, event, noShift) +} + +export function getKeyMap(val) { + return typeof val == "string" ? keyMap[val] : val +} diff --git a/modules/editor/codemirror/src/line/highlight.js b/modules/editor/codemirror/src/line/highlight.js @@ -0,0 +1,284 @@ +import { countColumn } from "../util/misc.js" +import { copyState, innerMode, startState } from "../modes.js" +import StringStream from "../util/StringStream.js" + +import { getLine, lineNo } from "./utils_line.js" +import { clipPos } from "./pos.js" + +class SavedContext { + constructor(state, lookAhead) { + this.state = state + this.lookAhead = lookAhead + } +} + +class Context { + constructor(doc, state, line, lookAhead) { + this.state = state + this.doc = doc + this.line = line + this.maxLookAhead = lookAhead || 0 + this.baseTokens = null + this.baseTokenPos = 1 + } + + lookAhead(n) { + let line = this.doc.getLine(this.line + n) + if (line != null && n > this.maxLookAhead) this.maxLookAhead = n + return line + } + + baseToken(n) { + if (!this.baseTokens) return null + while (this.baseTokens[this.baseTokenPos] <= n) + this.baseTokenPos += 2 + let type = this.baseTokens[this.baseTokenPos + 1] + return {type: type && type.replace(/( |^)overlay .*/, ""), + size: this.baseTokens[this.baseTokenPos] - n} + } + + nextLine() { + this.line++ + if (this.maxLookAhead > 0) this.maxLookAhead-- + } + + static fromSaved(doc, saved, line) { + if (saved instanceof SavedContext) + return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) + else + return new Context(doc, copyState(doc.mode, saved), line) + } + + save(copy) { + let state = copy !== false ? copyState(this.doc.mode, this.state) : this.state + return this.maxLookAhead > 0 ? new SavedContext(state, this.maxLookAhead) : state + } +} + + +// Compute a style array (an array starting with a mode generation +// -- for invalidation -- followed by pairs of end positions and +// style strings), which is used to highlight the tokens on the +// line. +export function highlightLine(cm, line, context, forceToEnd) { + // A styles array always starts with a number identifying the + // mode/overlays that it is based on (for easy invalidation). + let st = [cm.state.modeGen], lineClasses = {} + // Compute the base array of styles + runMode(cm, line.text, cm.doc.mode, context, (end, style) => st.push(end, style), + lineClasses, forceToEnd) + let state = context.state + + // Run overlays, adjust style array. + for (let o = 0; o < cm.state.overlays.length; ++o) { + context.baseTokens = st + let overlay = cm.state.overlays[o], i = 1, at = 0 + context.state = true + runMode(cm, line.text, overlay.mode, context, (end, style) => { + let start = i + // Ensure there's a token end at the current position, and that i points at it + while (at < end) { + let 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, "overlay " + style) + i = start + 2 + } else { + for (; start < i; start += 2) { + let cur = st[start+1] + st[start+1] = (cur ? cur + " " : "") + "overlay " + style + } + } + }, lineClasses) + context.state = state + context.baseTokens = null + context.baseTokenPos = 1 + } + + return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null} +} + +export function getLineStyles(cm, line, updateFrontier) { + if (!line.styles || line.styles[0] != cm.state.modeGen) { + let context = getContextBefore(cm, lineNo(line)) + let resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state) + let result = highlightLine(cm, line, context) + if (resetState) context.state = resetState + line.stateAfter = context.save(!resetState) + line.styles = result.styles + if (result.classes) line.styleClasses = result.classes + else if (line.styleClasses) line.styleClasses = null + if (updateFrontier === cm.doc.highlightFrontier) + cm.doc.modeFrontier = Math.max(cm.doc.modeFrontier, ++cm.doc.highlightFrontier) + } + return line.styles +} + +export function getContextBefore(cm, n, precise) { + let doc = cm.doc, display = cm.display + if (!doc.mode.startState) return new Context(doc, true, n) + let start = findStartLine(cm, n, precise) + let saved = start > doc.first && getLine(doc, start - 1).stateAfter + let context = saved ? Context.fromSaved(doc, saved, start) : new Context(doc, startState(doc.mode), start) + + doc.iter(start, n, line => { + processLine(cm, line.text, context) + let pos = context.line + line.stateAfter = pos == n - 1 || pos % 5 == 0 || pos >= display.viewFrom && pos < display.viewTo ? context.save() : null + context.nextLine() + }) + if (precise) doc.modeFrontier = context.line + return context +} + +// Lightweight form of highlight -- proceed over this line and +// update state, but don't save a style array. Used for lines that +// aren't currently visible. +export function processLine(cm, text, context, startAt) { + let mode = cm.doc.mode + let stream = new StringStream(text, cm.options.tabSize, context) + stream.start = stream.pos = startAt || 0 + if (text == "") callBlankLine(mode, context.state) + while (!stream.eol()) { + readToken(mode, stream, context.state) + stream.start = stream.pos + } +} + +function callBlankLine(mode, state) { + if (mode.blankLine) return mode.blankLine(state) + if (!mode.innerMode) return + let inner = innerMode(mode, state) + if (inner.mode.blankLine) return inner.mode.blankLine(inner.state) +} + +export function readToken(mode, stream, state, inner) { + for (let i = 0; i < 10; i++) { + if (inner) inner[0] = innerMode(mode, state).mode + let style = mode.token(stream, state) + if (stream.pos > stream.start) return style + } + throw new Error("Mode " + mode.name + " failed to advance stream.") +} + +class Token { + constructor(stream, type, state) { + this.start = stream.start; this.end = stream.pos + this.string = stream.current() + this.type = type || null + this.state = state + } +} + +// Utility for getTokenAt and getLineTokens +export function takeToken(cm, pos, precise, asArray) { + let doc = cm.doc, mode = doc.mode, style + pos = clipPos(doc, pos) + let line = getLine(doc, pos.line), context = getContextBefore(cm, pos.line, precise) + let stream = new StringStream(line.text, cm.options.tabSize, context), tokens + if (asArray) tokens = [] + while ((asArray || stream.pos < pos.ch) && !stream.eol()) { + stream.start = stream.pos + style = readToken(mode, stream, context.state) + if (asArray) tokens.push(new Token(stream, style, copyState(doc.mode, context.state))) + } + return asArray ? tokens : new Token(stream, style, context.state) +} + +function extractLineClasses(type, output) { + if (type) for (;;) { + let lineClass = type.match(/(?:^|\s+)line-(background-)?(\S+)/) + if (!lineClass) break + type = type.slice(0, lineClass.index) + type.slice(lineClass.index + lineClass[0].length) + let prop = lineClass[1] ? "bgClass" : "textClass" + if (output[prop] == null) + output[prop] = lineClass[2] + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(output[prop])) + output[prop] += " " + lineClass[2] + } + return type +} + +// Run the given mode's parser over a line, calling f for each token. +function runMode(cm, text, mode, context, f, lineClasses, forceToEnd) { + let flattenSpans = mode.flattenSpans + if (flattenSpans == null) flattenSpans = cm.options.flattenSpans + let curStart = 0, curStyle = null + let stream = new StringStream(text, cm.options.tabSize, context), style + let inner = cm.options.addModeClass && [null] + if (text == "") extractLineClasses(callBlankLine(mode, context.state), lineClasses) + while (!stream.eol()) { + if (stream.pos > cm.options.maxHighlightLength) { + flattenSpans = false + if (forceToEnd) processLine(cm, text, context, stream.pos) + stream.pos = text.length + style = null + } else { + style = extractLineClasses(readToken(mode, stream, context.state, inner), lineClasses) + } + if (inner) { + let mName = inner[0].name + if (mName) style = "m-" + (style ? mName + " " + style : mName) + } + if (!flattenSpans || curStyle != style) { + while (curStart < stream.start) { + curStart = Math.min(stream.start, curStart + 5000) + f(curStart, curStyle) + } + curStyle = style + } + stream.start = stream.pos + } + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 + // characters, and returns inaccurate measurements in nodes + // starting around 5000 chars. + let pos = Math.min(stream.pos, curStart + 5000) + f(pos, curStyle) + curStart = pos + } +} + +// 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) { + let minindent, minline, doc = cm.doc + let lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100) + for (let search = n; search > lim; --search) { + if (search <= doc.first) return doc.first + let line = getLine(doc, search - 1), after = line.stateAfter + if (after && (!precise || search + (after instanceof SavedContext ? after.lookAhead : 0) <= doc.modeFrontier)) + return search + let indented = countColumn(line.text, null, cm.options.tabSize) + if (minline == null || minindent > indented) { + minline = search - 1 + minindent = indented + } + } + return minline +} + +export function retreatFrontier(doc, n) { + doc.modeFrontier = Math.min(doc.modeFrontier, n) + if (doc.highlightFrontier < n - 10) return + let start = doc.first + for (let line = n - 1; line > start; line--) { + let saved = getLine(doc, line).stateAfter + // change is on 3 + // state on line 1 looked ahead 2 -- so saw 3 + // test 1 + 2 < 3 should cover this + if (saved && (!(saved instanceof SavedContext) || line + saved.lookAhead < n)) { + start = line + 1 + break + } + } + doc.highlightFrontier = Math.min(doc.highlightFrontier, start) +} diff --git a/modules/editor/codemirror/src/line/line_data.min.js b/modules/editor/codemirror/src/line/line_data.min.js @@ -0,0 +1,337 @@ +import { getOrder } from "../util/bidi.js" +import { ie, ie_version, webkit } from "../util/browser.js" +import { elt, eltP, joinClasses } from "../util/dom.js" +import { eventMixin, signal } from "../util/event.js" +import { hasBadBidiRects, zeroWidthElement } from "../util/feature_detection.js" +import { lst, spaceStr } from "../util/misc.js" + +import { getLineStyles } from "./highlight.js" +import { attachMarkedSpans, compareCollapsedMarkers, detachMarkedSpans, lineIsHidden, visualLineContinued } from "./spans.js" +import { getLine, lineNo, updateLineHeight } from "./utils_line.js" + +// LINE DATA STRUCTURE + +// Line objects. These hold state related to a line, including +// highlighting info (the styles array). +export class Line { + constructor(text, markedSpans, estimateHeight) { + this.text = text + attachMarkedSpans(this, markedSpans) + this.height = estimateHeight ? estimateHeight(this) : 1 + } + + lineNo() { return lineNo(this) } +} +eventMixin(Line) + +// Change the content (text, markers) of a line. Automatically +// invalidates cached information and tries to re-estimate the +// line's height. +export 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) + let estHeight = estimateHeight ? estimateHeight(line) : 1 + if (estHeight != line.height) updateLineHeight(line, estHeight) +} + +// Detach a line from the document tree and its markers. +export function cleanUpLine(line) { + line.parent = null + detachMarkedSpans(line) +} + +// Convert a style as returned by a mode (either null, or a string +// containing one or more styles) to a CSS style. This is cached, +// and also looks for line-wide styles. +let styleToClassCache = {}, styleToClassCacheWithMode = {} +function interpretTokenStyle(style, options) { + if (!style || /^\s*$/.test(style)) return null + let cache = options.addModeClass ? styleToClassCacheWithMode : styleToClassCache + return cache[style] || + (cache[style] = style.replace(/\S+/g, "cm-$&")) +} + +// Render the DOM representation of the text of a line. Also builds +// up a 'line map', which points at the DOM nodes that represent +// specific stretches of text, and is used by the measuring code. +// The returned object contains the DOM node, this map, and +// information about line-wide styles that were set by the mode. +export function buildLineContent(cm, lineView) { + // The padding-right forces the element to have a 'border', which + // is needed on Webkit to be able to get line-level bounding + // rectangles for it (in measureChar). + let content = eltP("span", null, null, webkit ? "padding-right: .1px" : null) + let builder = {pre: eltP("pre", [content], "CodeMirror-line"), content: content, + col: 0, pos: 0, cm: cm, + trailingSpace: false, + splitSpaces: (ie || webkit) && cm.getOption("lineWrapping")} + lineView.measure = {} + + // Iterate over the logical lines that make up this visual line. + for (let i = 0; i <= (lineView.rest ? lineView.rest.length : 0); i++) { + let line = i ? lineView.rest[i - 1] : lineView.line, order + builder.pos = 0 + builder.addToken = buildToken + // Optionally wire in some hacks into the token-rendering + // algorithm, to deal with browser quirks. + if (hasBadBidiRects(cm.display.measure) && (order = getOrder(line, cm.doc.direction))) + builder.addToken = buildTokenBadBidi(builder.addToken, order) + builder.map = [] + let allowFrontierUpdate = lineView != cm.display.externalMeasured && lineNo(line) + insertLineContent(line, builder, getLineStyles(cm, line, allowFrontierUpdate)) + if (line.styleClasses) { + if (line.styleClasses.bgClass) + builder.bgClass = joinClasses(line.styleClasses.bgClass, builder.bgClass || "") + if (line.styleClasses.textClass) + builder.textClass = joinClasses(line.styleClasses.textClass, builder.textClass || "") + } + + // Ensure at least a single node is present, for measuring. + if (builder.map.length == 0) + builder.map.push(0, 0, builder.content.appendChild(zeroWidthElement(cm.display.measure))) + + // Store the map and a cache object for the current logical line + if (i == 0) { + lineView.measure.map = builder.map + lineView.measure.cache = {} + } else { + ;(lineView.measure.maps || (lineView.measure.maps = [])).push(builder.map) + ;(lineView.measure.caches || (lineView.measure.caches = [])).push({}) + } + } + + // See issue #2901 + if (webkit) { + let last = builder.content.lastChild + if (/\bcm-tab\b/.test(last.className) || (last.querySelector && last.querySelector(".cm-tab"))) + builder.content.className = "cm-tab-wrap-hack" + } + + signal(cm, "renderLine", cm, lineView.line, builder.pre) + if (builder.pre.className) + builder.textClass = joinClasses(builder.pre.className, builder.textClass || "") + + return builder +} + +export function defaultSpecialCharPlaceholder(ch) { + let token = elt("span", "\u2022", "cm-invalidchar") + token.title = "\\u" + ch.charCodeAt(0).toString(16) + token.setAttribute("aria-label", token.title) + return token +} + +// Build up the DOM representation for a single token, and add it to +// the line map. Takes care to render special characters separately. +function buildToken(builder, text, style, startStyle, endStyle, title, css) { + if (!text) return + let displayText = builder.splitSpaces ? splitSpaces(text, builder.trailingSpace) : text + let special = builder.cm.state.specialChars, mustWrap = false + let content + if (!special.test(text)) { + builder.col += text.length + content = document.createTextNode(displayText) + builder.map.push(builder.pos, builder.pos + text.length, content) + if (ie && ie_version < 9) mustWrap = true + builder.pos += text.length + } else { + content = document.createDocumentFragment() + let pos = 0 + while (true) { + special.lastIndex = pos + let m = special.exec(text) + let skipped = m ? m.index - pos : text.length - pos + if (skipped) { + let txt = document.createTextNode(displayText.slice(pos, pos + skipped)) + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])) + else content.appendChild(txt) + builder.map.push(builder.pos, builder.pos + skipped, txt) + builder.col += skipped + builder.pos += skipped + } + if (!m) break + pos += skipped + 1 + let txt + if (m[0] == "\t") { + let tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize + txt = content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")) + txt.setAttribute("role", "presentation") + txt.setAttribute("cm-text", "\t") + builder.col += tabWidth + } else if (m[0] == "\r" || m[0] == "\n") { + txt = content.appendChild(elt("span", m[0] == "\r" ? "\u240d" : "\u2424", "cm-invalidchar")) + txt.setAttribute("cm-text", m[0]) + builder.col += 1 + } else { + txt = builder.cm.options.specialCharPlaceholder(m[0]) + txt.setAttribute("cm-text", m[0]) + if (ie && ie_version < 9) content.appendChild(elt("span", [txt])) + else content.appendChild(txt) + builder.col += 1 + } + builder.map.push(builder.pos, builder.pos + 1, txt) + builder.pos++ + } + } + builder.trailingSpace = displayText.charCodeAt(text.length - 1) == 32 + if (style || startStyle || endStyle || mustWrap || css) { + let fullStyle = style || "" + if (startStyle) fullStyle += startStyle + if (endStyle) fullStyle += endStyle + let token = elt("span", [content], fullStyle, css) + if (title) token.title = title + return builder.content.appendChild(token) + } + builder.content.appendChild(content) +} + +function splitSpaces(text, trailingBefore) { + if (text.length > 1 && !/ /.test(text)) return text + let spaceBefore = trailingBefore, result = "" + for (let i = 0; i < text.length; i++) { + let ch = text.charAt(i) + if (ch == " " && spaceBefore && (i == text.length - 1 || text.charCodeAt(i + 1) == 32)) + ch = "\u00a0" + result += ch + spaceBefore = ch == " " + } + return result +} + +// Work around nonsense dimensions being reported for stretches of +// right-to-left text. +function buildTokenBadBidi(inner, order) { + return (builder, text, style, startStyle, endStyle, title, css) => { + style = style ? style + " cm-force-border" : "cm-force-border" + let start = builder.pos, end = start + text.length + for (;;) { + // Find the part that overlaps with the start of this text + let part + for (let i = 0; i < order.length; i++) { + part = order[i] + if (part.to > start && part.from <= start) break + } + if (part.to >= end) return inner(builder, text, style, startStyle, endStyle, title, css) + inner(builder, text.slice(0, part.to - start), style, startStyle, null, title, css) + startStyle = null + text = text.slice(part.to - start) + start = part.to + } + } +} + +function buildCollapsedSpan(builder, size, marker, ignoreWidget) { + let widget = !ignoreWidget && marker.widgetNode + if (widget) builder.map.push(builder.pos, builder.pos + size, widget) + if (!ignoreWidget && builder.cm.display.input.needsContentAttribute) { + if (!widget) + widget = builder.content.appendChild(document.createElement("span")) + widget.setAttribute("cm-marker", marker.id) + } + if (widget) { + builder.cm.display.input.setUneditable(widget) + builder.content.appendChild(widget) + } + builder.pos += size + builder.trailingSpace = false +} + +// Outputs a number of spans to make up a line, taking highlighting +// and marked text into account. +function insertLineContent(line, builder, styles) { + let spans = line.markedSpans, allText = line.text, at = 0 + if (!spans) { + for (let i = 1; i < styles.length; i+=2) + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder.cm.options)) + return + } + + let len = allText.length, pos = 0, i = 1, text = "", style, css + let nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, title, collapsed + for (;;) { + if (nextChange == pos) { // Update current marker set + spanStyle = spanEndStyle = spanStartStyle = title = css = "" + collapsed = null; nextChange = Infinity + let foundBookmarks = [], endStyles + for (let j = 0; j < spans.length; ++j) { + let sp = spans[j], m = sp.marker + if (m.type == "bookmark" && sp.from == pos && m.widgetNode) { + foundBookmarks.push(m) + } else if (sp.from <= pos && (sp.to == null || sp.to > pos || m.collapsed && sp.to == pos && sp.from == pos)) { + if (sp.to != null && sp.to != pos && nextChange > sp.to) { + nextChange = sp.to + spanEndStyle = "" + } + if (m.className) spanStyle += " " + m.className + if (m.css) css = (css ? css + ";" : "") + m.css + if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle + if (m.endStyle && sp.to == nextChange) (endStyles || (endStyles = [])).push(m.endStyle, sp.to) + if (m.title && !title) title = m.title + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) + collapsed = sp + } else if (sp.from > pos && nextChange > sp.from) { + nextChange = sp.from + } + } + if (endStyles) for (let j = 0; j < endStyles.length; j += 2) + if (endStyles[j + 1] == nextChange) spanEndStyle += " " + endStyles[j] + + if (!collapsed || collapsed.from == pos) for (let j = 0; j < foundBookmarks.length; ++j) + buildCollapsedSpan(builder, 0, foundBookmarks[j]) + if (collapsed && (collapsed.from || 0) == pos) { + buildCollapsedSpan(builder, (collapsed.to == null ? len + 1 : collapsed.to) - pos, + collapsed.marker, collapsed.from == null) + if (collapsed.to == null) return + if (collapsed.to == pos) collapsed = false + } + } + if (pos >= len) break + + let upto = Math.min(len, nextChange) + while (true) { + if (text) { + let end = pos + text.length + if (!collapsed) { + let tokenText = end > upto ? text.slice(0, upto - pos) : text + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle, + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "", title, css) + } + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break} + pos = end + spanStartStyle = "" + } + text = allText.slice(at, at = styles[i++]) + style = interpretTokenStyle(styles[i++], builder.cm.options) + } + } +} + + +// These objects are used to represent the visible (currently drawn) +// part of the document. A LineView may correspond to multiple +// logical lines, if those are connected by collapsed ranges. +export function LineView(doc, line, lineN) { + // The starting line + this.line = line + // Continuing lines, if any + this.rest = visualLineContinued(line) + // Number of logical lines in this visual line + this.size = this.rest ? lineNo(lst(this.rest)) - lineN + 1 : 1 + this.node = this.text = null + this.hidden = lineIsHidden(doc, line) +} + +// Create a range of LineView objects for the given lines. +export function buildViewArray(cm, from, to) { + let array = [], nextPos + for (let pos = from; pos < to; pos = nextPos) { + let view = new LineView(cm.doc, getLine(cm.doc, pos), pos) + nextPos = pos + view.size + array.push(view) + } + return array +} diff --git a/modules/editor/codemirror/src/line/pos.js b/modules/editor/codemirror/src/line/pos.js @@ -0,0 +1,40 @@ +import { getLine } from "./utils_line.js" + +// A Pos instance represents a position within the text. +export function Pos(line, ch, sticky = null) { + if (!(this instanceof Pos)) return new Pos(line, ch, sticky) + this.line = line + this.ch = ch + this.sticky = sticky +} + +// Compare two positions, return 0 if they are the same, a negative +// number when a is less, and a positive number otherwise. +export function cmp(a, b) { return a.line - b.line || a.ch - b.ch } + +export function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } + +export function copyPos(x) {return Pos(x.line, x.ch)} +export function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } +export function minPos(a, b) { return cmp(a, b) < 0 ? a : b } + +// Most of the external API clips given positions to make sure they +// actually exist within the document. +export function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} +export function clipPos(doc, pos) { + if (pos.line < doc.first) return Pos(doc.first, 0) + let 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) { + let 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 +} +export function clipPosArray(doc, array) { + let out = [] + for (let i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]) + return out +} diff --git a/modules/editor/codemirror/src/line/pos.min.js b/modules/editor/codemirror/src/line/pos.min.js @@ -0,0 +1,40 @@ +import { getLine } from "./utils_line.js" + +// A Pos instance represents a position within the text. +export function Pos(line, ch, sticky = null) { + if (!(this instanceof Pos)) return new Pos(line, ch, sticky) + this.line = line + this.ch = ch + this.sticky = sticky +} + +// Compare two positions, return 0 if they are the same, a negative +// number when a is less, and a positive number otherwise. +export function cmp(a, b) { return a.line - b.line || a.ch - b.ch } + +export function equalCursorPos(a, b) { return a.sticky == b.sticky && cmp(a, b) == 0 } + +export function copyPos(x) {return Pos(x.line, x.ch)} +export function maxPos(a, b) { return cmp(a, b) < 0 ? b : a } +export function minPos(a, b) { return cmp(a, b) < 0 ? a : b } + +// Most of the external API clips given positions to make sure they +// actually exist within the document. +export function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1))} +export function clipPos(doc, pos) { + if (pos.line < doc.first) return Pos(doc.first, 0) + let 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) { + let 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 +} +export function clipPosArray(doc, array) { + let out = [] + for (let i = 0; i < array.length; i++) out[i] = clipPos(doc, array[i]) + return out +} diff --git a/modules/editor/codemirror/src/line/spans.js b/modules/editor/codemirror/src/line/spans.js @@ -0,0 +1,372 @@ +import { indexOf, lst } from "../util/misc.js" + +import { cmp } from "./pos.js" +import { sawCollapsedSpans } from "./saw_special_spans.js" +import { getLine, isLine, lineNo } from "./utils_line.js" + +// TEXTMARKER SPANS + +export function MarkedSpan(marker, from, to) { + this.marker = marker + this.from = from; this.to = to +} + +// Search an array of spans for a span matching the given marker. +export function getMarkedSpanFor(spans, marker) { + if (spans) for (let i = 0; i < spans.length; ++i) { + let span = spans[i] + if (span.marker == marker) return span + } +} +// Remove a span from an array, returning undefined if no spans are +// left (we don't store arrays for lines without spans). +export function removeMarkedSpan(spans, span) { + let r + for (let i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]) + return r +} +// Add a span to a line. +export function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span] + span.marker.attachLine(line) +} + +// Used for the algorithm that adjusts markers for a change in the +// document. These functions cut an array of spans at a given +// character position, returning an array of remaining chunks (or +// undefined if nothing remains). +function markedSpansBefore(old, startCh, isInsert) { + let nw + if (old) for (let i = 0; i < old.length; ++i) { + let span = old[i], marker = span.marker + let startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh) + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + let endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)) + } + } + return nw +} +function markedSpansAfter(old, endCh, isInsert) { + let nw + if (old) for (let i = 0; i < old.length; ++i) { + let span = old[i], marker = span.marker + let endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh) + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + let startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)) + } + } + return nw +} + +// Given a change object, compute the new set of marker spans that +// cover the line in which the change took place. Removes spans +// entirely within the change, reconnects spans belonging to the +// same marker that appear on both sides of the change, and cuts off +// spans partially within the change. Returns an array of span +// arrays with one element for each line in (after) the change. +export function stretchSpansOverChange(doc, change) { + if (change.full) return null + let oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans + let oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans + if (!oldFirst && !oldLast) return null + + let startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0 + // Get the spans that 'stick out' on both sides + let first = markedSpansBefore(oldFirst, startCh, isInsert) + let last = markedSpansAfter(oldLast, endCh, isInsert) + + // Next, merge those two ends + let sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0) + if (first) { + // Fix up .to properties of first + for (let i = 0; i < first.length; ++i) { + let span = first[i] + if (span.to == null) { + let 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 (let i = 0; i < last.length; ++i) { + let span = last[i] + if (span.to != null) span.to += offset + if (span.from == null) { + let 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) + } + } + } + // Make sure we didn't create any zero-length spans + if (first) first = clearEmptySpans(first) + if (last && last != first) last = clearEmptySpans(last) + + let newMarkers = [first] + if (!sameLine) { + // Fill gap with whole-line-spans + let gap = change.text.length - 2, gapMarkers + if (gap > 0 && first) + for (let i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)) + for (let i = 0; i < gap; ++i) + newMarkers.push(gapMarkers) + newMarkers.push(last) + } + return newMarkers +} + +// Remove spans that are empty and don't have a clearWhenEmpty +// option of false. +function clearEmptySpans(spans) { + for (let i = 0; i < spans.length; ++i) { + let span = spans[i] + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + spans.splice(i--, 1) + } + if (!spans.length) return null + return spans +} + +// Used to 'clip' out readOnly ranges when making a change. +export function removeReadOnlyRanges(doc, from, to) { + let markers = null + doc.iter(from.line, to.line + 1, line => { + if (line.markedSpans) for (let i = 0; i < line.markedSpans.length; ++i) { + let mark = line.markedSpans[i].marker + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + (markers || (markers = [])).push(mark) + } + }) + if (!markers) return null + let parts = [{from: from, to: to}] + for (let i = 0; i < markers.length; ++i) { + let mk = markers[i], m = mk.find(0) + for (let j = 0; j < parts.length; ++j) { + let p = parts[j] + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue + let newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to) + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + newParts.push({from: p.from, to: m.from}) + if (dto > 0 || !mk.inclusiveRight && !dto) + newParts.push({from: m.to, to: p.to}) + parts.splice.apply(parts, newParts) + j += newParts.length - 3 + } + } + return parts +} + +// Connect or disconnect spans from a line. +export function detachMarkedSpans(line) { + let spans = line.markedSpans + if (!spans) return + for (let i = 0; i < spans.length; ++i) + spans[i].marker.detachLine(line) + line.markedSpans = null +} +export function attachMarkedSpans(line, spans) { + if (!spans) return + for (let i = 0; i < spans.length; ++i) + spans[i].marker.attachLine(line) + line.markedSpans = spans +} + +// Helpers used when computing which overlapping collapsed span +// counts as the larger one. +function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } +function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } + +// Returns a number indicating which of two overlapping collapsed +// spans is larger (and thus includes the other). Falls back to +// comparing ids when the spans cover exactly the same range. +export function compareCollapsedMarkers(a, b) { + let lenDiff = a.lines.length - b.lines.length + if (lenDiff != 0) return lenDiff + let aPos = a.find(), bPos = b.find() + let fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b) + if (fromCmp) return -fromCmp + let toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b) + if (toCmp) return toCmp + return b.id - a.id +} + +// Find out whether a line ends or starts in a collapsed span. If +// so, return the marker for that span. +function collapsedSpanAtSide(line, start) { + let sps = sawCollapsedSpans && line.markedSpans, found + if (sps) for (let sp, i = 0; i < sps.length; ++i) { + sp = sps[i] + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + found = sp.marker + } + return found +} +export function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } +export function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } + +// Test whether there exists a collapsed span that partially +// overlaps (covers the start or end, but not both) of a new span. +// Such overlap is not allowed. +export function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + let line = getLine(doc, lineNo) + let sps = sawCollapsedSpans && line.markedSpans + if (sps) for (let i = 0; i < sps.length; ++i) { + let sp = sps[i] + if (!sp.marker.collapsed) continue + let found = sp.marker.find(0) + let fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker) + let toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker) + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue + if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || + fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) + return true + } +} + +// A visual line is a line as drawn on the screen. Folding, for +// example, can cause multiple logical lines to appear on the same +// visual line. This finds the start of the visual line that the +// given line is part of (usually that is the line itself). +export function visualLine(line) { + let merged + while (merged = collapsedSpanAtStart(line)) + line = merged.find(-1, true).line + return line +} + +export function visualLineEnd(line) { + let merged + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line + return line +} + +// Returns an array of logical lines that continue the visual line +// started by the argument, or undefined if there are no such lines. +export function visualLineContinued(line) { + let merged, lines + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line + ;(lines || (lines = [])).push(line) + } + return lines +} + +// Get the line number of the start of the visual line that the +// given line number is part of. +export function visualLineNo(doc, lineN) { + let line = getLine(doc, lineN), vis = visualLine(line) + if (line == vis) return lineN + return lineNo(vis) +} + +// Get the line number of the start of the next visual line after +// the given line. +export function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) return lineN + let line = getLine(doc, lineN), merged + if (!lineIsHidden(doc, line)) return lineN + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line + return lineNo(line) + 1 +} + +// Compute whether a line is hidden. Lines count as hidden when they +// are part of a visual line that starts with another line, or when +// they are entirely covered by collapsed, non-widget span. +export function lineIsHidden(doc, line) { + let sps = sawCollapsedSpans && line.markedSpans + if (sps) for (let sp, i = 0; i < sps.length; ++i) { + sp = sps[i] + if (!sp.marker.collapsed) continue + if (sp.from == null) return true + if (sp.marker.widgetNode) continue + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + return true + } +} +function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + let end = span.marker.find(1, true) + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) + } + if (span.marker.inclusiveRight && span.to == line.text.length) + return true + for (let sp, i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i] + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) return true + } +} + +// Find the height above the given line. +export function heightAtLine(lineObj) { + lineObj = visualLine(lineObj) + + let h = 0, chunk = lineObj.parent + for (let i = 0; i < chunk.lines.length; ++i) { + let line = chunk.lines[i] + if (line == lineObj) break + else h += line.height + } + for (let p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (let i = 0; i < p.children.length; ++i) { + let cur = p.children[i] + if (cur == chunk) break + else h += cur.height + } + } + return h +} + +// Compute the character length of a line, taking into account +// collapsed ranges (see markText) that might hide parts, and join +// other lines onto it. +export function lineLength(line) { + if (line.height == 0) return 0 + let len = line.text.length, merged, cur = line + while (merged = collapsedSpanAtStart(cur)) { + let found = merged.find(0, true) + cur = found.from.line + len += found.from.ch - found.to.ch + } + cur = line + while (merged = collapsedSpanAtEnd(cur)) { + let found = merged.find(0, true) + len -= cur.text.length - found.from.ch + cur = found.to.line + len += cur.text.length - found.to.ch + } + return len +} + +// Find the longest line in the document. +export function findMaxLine(cm) { + let d = cm.display, doc = cm.doc + d.maxLine = getLine(doc, doc.first) + d.maxLineLength = lineLength(d.maxLine) + d.maxLineChanged = true + doc.iter(line => { + let len = lineLength(line) + if (len > d.maxLineLength) { + d.maxLineLength = len + d.maxLine = line + } + }) +} diff --git a/modules/editor/codemirror/src/line/spans.min.js b/modules/editor/codemirror/src/line/spans.min.js @@ -0,0 +1,372 @@ +import { indexOf, lst } from "../util/misc.js" + +import { cmp } from "./pos.js" +import { sawCollapsedSpans } from "./saw_special_spans.js" +import { getLine, isLine, lineNo } from "./utils_line.js" + +// TEXTMARKER SPANS + +export function MarkedSpan(marker, from, to) { + this.marker = marker + this.from = from; this.to = to +} + +// Search an array of spans for a span matching the given marker. +export function getMarkedSpanFor(spans, marker) { + if (spans) for (let i = 0; i < spans.length; ++i) { + let span = spans[i] + if (span.marker == marker) return span + } +} +// Remove a span from an array, returning undefined if no spans are +// left (we don't store arrays for lines without spans). +export function removeMarkedSpan(spans, span) { + let r + for (let i = 0; i < spans.length; ++i) + if (spans[i] != span) (r || (r = [])).push(spans[i]) + return r +} +// Add a span to a line. +export function addMarkedSpan(line, span) { + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span] + span.marker.attachLine(line) +} + +// Used for the algorithm that adjusts markers for a change in the +// document. These functions cut an array of spans at a given +// character position, returning an array of remaining chunks (or +// undefined if nothing remains). +function markedSpansBefore(old, startCh, isInsert) { + let nw + if (old) for (let i = 0; i < old.length; ++i) { + let span = old[i], marker = span.marker + let startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh) + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { + let endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, span.from, endsAfter ? null : span.to)) + } + } + return nw +} +function markedSpansAfter(old, endCh, isInsert) { + let nw + if (old) for (let i = 0; i < old.length; ++i) { + let span = old[i], marker = span.marker + let endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh) + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { + let startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh) + ;(nw || (nw = [])).push(new MarkedSpan(marker, startsBefore ? null : span.from - endCh, + span.to == null ? null : span.to - endCh)) + } + } + return nw +} + +// Given a change object, compute the new set of marker spans that +// cover the line in which the change took place. Removes spans +// entirely within the change, reconnects spans belonging to the +// same marker that appear on both sides of the change, and cuts off +// spans partially within the change. Returns an array of span +// arrays with one element for each line in (after) the change. +export function stretchSpansOverChange(doc, change) { + if (change.full) return null + let oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans + let oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans + if (!oldFirst && !oldLast) return null + + let startCh = change.from.ch, endCh = change.to.ch, isInsert = cmp(change.from, change.to) == 0 + // Get the spans that 'stick out' on both sides + let first = markedSpansBefore(oldFirst, startCh, isInsert) + let last = markedSpansAfter(oldLast, endCh, isInsert) + + // Next, merge those two ends + let sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0) + if (first) { + // Fix up .to properties of first + for (let i = 0; i < first.length; ++i) { + let span = first[i] + if (span.to == null) { + let 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 (let i = 0; i < last.length; ++i) { + let span = last[i] + if (span.to != null) span.to += offset + if (span.from == null) { + let 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) + } + } + } + // Make sure we didn't create any zero-length spans + if (first) first = clearEmptySpans(first) + if (last && last != first) last = clearEmptySpans(last) + + let newMarkers = [first] + if (!sameLine) { + // Fill gap with whole-line-spans + let gap = change.text.length - 2, gapMarkers + if (gap > 0 && first) + for (let i = 0; i < first.length; ++i) + if (first[i].to == null) + (gapMarkers || (gapMarkers = [])).push(new MarkedSpan(first[i].marker, null, null)) + for (let i = 0; i < gap; ++i) + newMarkers.push(gapMarkers) + newMarkers.push(last) + } + return newMarkers +} + +// Remove spans that are empty and don't have a clearWhenEmpty +// option of false. +function clearEmptySpans(spans) { + for (let i = 0; i < spans.length; ++i) { + let span = spans[i] + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + spans.splice(i--, 1) + } + if (!spans.length) return null + return spans +} + +// Used to 'clip' out readOnly ranges when making a change. +export function removeReadOnlyRanges(doc, from, to) { + let markers = null + doc.iter(from.line, to.line + 1, line => { + if (line.markedSpans) for (let i = 0; i < line.markedSpans.length; ++i) { + let mark = line.markedSpans[i].marker + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1)) + (markers || (markers = [])).push(mark) + } + }) + if (!markers) return null + let parts = [{from: from, to: to}] + for (let i = 0; i < markers.length; ++i) { + let mk = markers[i], m = mk.find(0) + for (let j = 0; j < parts.length; ++j) { + let p = parts[j] + if (cmp(p.to, m.from) < 0 || cmp(p.from, m.to) > 0) continue + let newParts = [j, 1], dfrom = cmp(p.from, m.from), dto = cmp(p.to, m.to) + if (dfrom < 0 || !mk.inclusiveLeft && !dfrom) + newParts.push({from: p.from, to: m.from}) + if (dto > 0 || !mk.inclusiveRight && !dto) + newParts.push({from: m.to, to: p.to}) + parts.splice.apply(parts, newParts) + j += newParts.length - 3 + } + } + return parts +} + +// Connect or disconnect spans from a line. +export function detachMarkedSpans(line) { + let spans = line.markedSpans + if (!spans) return + for (let i = 0; i < spans.length; ++i) + spans[i].marker.detachLine(line) + line.markedSpans = null +} +export function attachMarkedSpans(line, spans) { + if (!spans) return + for (let i = 0; i < spans.length; ++i) + spans[i].marker.attachLine(line) + line.markedSpans = spans +} + +// Helpers used when computing which overlapping collapsed span +// counts as the larger one. +function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0 } +function extraRight(marker) { return marker.inclusiveRight ? 1 : 0 } + +// Returns a number indicating which of two overlapping collapsed +// spans is larger (and thus includes the other). Falls back to +// comparing ids when the spans cover exactly the same range. +export function compareCollapsedMarkers(a, b) { + let lenDiff = a.lines.length - b.lines.length + if (lenDiff != 0) return lenDiff + let aPos = a.find(), bPos = b.find() + let fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b) + if (fromCmp) return -fromCmp + let toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b) + if (toCmp) return toCmp + return b.id - a.id +} + +// Find out whether a line ends or starts in a collapsed span. If +// so, return the marker for that span. +function collapsedSpanAtSide(line, start) { + let sps = sawCollapsedSpans && line.markedSpans, found + if (sps) for (let sp, i = 0; i < sps.length; ++i) { + sp = sps[i] + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) + found = sp.marker + } + return found +} +export function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true) } +export function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false) } + +// Test whether there exists a collapsed span that partially +// overlaps (covers the start or end, but not both) of a new span. +// Such overlap is not allowed. +export function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + let line = getLine(doc, lineNo) + let sps = sawCollapsedSpans && line.markedSpans + if (sps) for (let i = 0; i < sps.length; ++i) { + let sp = sps[i] + if (!sp.marker.collapsed) continue + let found = sp.marker.find(0) + let fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker) + let toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker) + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue + if (fromCmp <= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.to, from) >= 0 : cmp(found.to, from) > 0) || + fromCmp >= 0 && (sp.marker.inclusiveRight && marker.inclusiveLeft ? cmp(found.from, to) <= 0 : cmp(found.from, to) < 0)) + return true + } +} + +// A visual line is a line as drawn on the screen. Folding, for +// example, can cause multiple logical lines to appear on the same +// visual line. This finds the start of the visual line that the +// given line is part of (usually that is the line itself). +export function visualLine(line) { + let merged + while (merged = collapsedSpanAtStart(line)) + line = merged.find(-1, true).line + return line +} + +export function visualLineEnd(line) { + let merged + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line + return line +} + +// Returns an array of logical lines that continue the visual line +// started by the argument, or undefined if there are no such lines. +export function visualLineContinued(line) { + let merged, lines + while (merged = collapsedSpanAtEnd(line)) { + line = merged.find(1, true).line + ;(lines || (lines = [])).push(line) + } + return lines +} + +// Get the line number of the start of the visual line that the +// given line number is part of. +export function visualLineNo(doc, lineN) { + let line = getLine(doc, lineN), vis = visualLine(line) + if (line == vis) return lineN + return lineNo(vis) +} + +// Get the line number of the start of the next visual line after +// the given line. +export function visualLineEndNo(doc, lineN) { + if (lineN > doc.lastLine()) return lineN + let line = getLine(doc, lineN), merged + if (!lineIsHidden(doc, line)) return lineN + while (merged = collapsedSpanAtEnd(line)) + line = merged.find(1, true).line + return lineNo(line) + 1 +} + +// Compute whether a line is hidden. Lines count as hidden when they +// are part of a visual line that starts with another line, or when +// they are entirely covered by collapsed, non-widget span. +export function lineIsHidden(doc, line) { + let sps = sawCollapsedSpans && line.markedSpans + if (sps) for (let sp, i = 0; i < sps.length; ++i) { + sp = sps[i] + if (!sp.marker.collapsed) continue + if (sp.from == null) return true + if (sp.marker.widgetNode) continue + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp)) + return true + } +} +function lineIsHiddenInner(doc, line, span) { + if (span.to == null) { + let end = span.marker.find(1, true) + return lineIsHiddenInner(doc, end.line, getMarkedSpanFor(end.line.markedSpans, span.marker)) + } + if (span.marker.inclusiveRight && span.to == line.text.length) + return true + for (let sp, i = 0; i < line.markedSpans.length; ++i) { + sp = line.markedSpans[i] + if (sp.marker.collapsed && !sp.marker.widgetNode && sp.from == span.to && + (sp.to == null || sp.to != span.from) && + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && + lineIsHiddenInner(doc, line, sp)) return true + } +} + +// Find the height above the given line. +export function heightAtLine(lineObj) { + lineObj = visualLine(lineObj) + + let h = 0, chunk = lineObj.parent + for (let i = 0; i < chunk.lines.length; ++i) { + let line = chunk.lines[i] + if (line == lineObj) break + else h += line.height + } + for (let p = chunk.parent; p; chunk = p, p = chunk.parent) { + for (let i = 0; i < p.children.length; ++i) { + let cur = p.children[i] + if (cur == chunk) break + else h += cur.height + } + } + return h +} + +// Compute the character length of a line, taking into account +// collapsed ranges (see markText) that might hide parts, and join +// other lines onto it. +export function lineLength(line) { + if (line.height == 0) return 0 + let len = line.text.length, merged, cur = line + while (merged = collapsedSpanAtStart(cur)) { + let found = merged.find(0, true) + cur = found.from.line + len += found.from.ch - found.to.ch + } + cur = line + while (merged = collapsedSpanAtEnd(cur)) { + let found = merged.find(0, true) + len -= cur.text.length - found.from.ch + cur = found.to.line + len += cur.text.length - found.to.ch + } + return len +} + +// Find the longest line in the document. +export function findMaxLine(cm) { + let d = cm.display, doc = cm.doc + d.maxLine = getLine(doc, doc.first) + d.maxLineLength = lineLength(d.maxLine) + d.maxLineChanged = true + doc.iter(line => { + let len = lineLength(line) + if (len > d.maxLineLength) { + d.maxLineLength = len + d.maxLine = line + } + }) +} diff --git a/modules/editor/codemirror/src/model/Doc.js b/modules/editor/codemirror/src/model/Doc.js @@ -0,0 +1,432 @@ +import CodeMirror from "../edit/CodeMirror.js" +import { docMethodOp } from "../display/operations.js" +import { Line } from "../line/line_data.js" +import { clipPos, clipPosArray, Pos } from "../line/pos.js" +import { visualLine } from "../line/spans.js" +import { getBetween, getLine, getLines, isLine, lineNo } from "../line/utils_line.js" +import { classTest } from "../util/dom.js" +import { splitLinesAuto } from "../util/feature_detection.js" +import { createObj, map, isEmpty, sel_dontScroll } from "../util/misc.js" +import { ensureCursorVisible, scrollToCoords } from "../display/scrolling.js" + +import { changeLine, makeChange, makeChangeFromHistory, replaceRange } from "./changes.js" +import { computeReplacedSel } from "./change_measurement.js" +import { BranchChunk, LeafChunk } from "./chunk.js" +import { directionChanged, linkedDocs, updateDoc } from "./document_data.js" +import { copyHistoryArray, History } from "./history.js" +import { addLineWidget } from "./line_widget.js" +import { copySharedMarkers, detachSharedMarkers, findSharedMarkers, markText } from "./mark_text.js" +import { normalizeSelection, Range, simpleSelection } from "./selection.js" +import { extendSelection, extendSelections, setSelection, setSelectionReplaceHistory, setSimpleSelection } from "./selection_updates.js" + +let nextDocId = 0 +let Doc = function(text, mode, firstLine, lineSep, direction) { + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep, direction) + 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.cleanGeneration = 1 + this.modeFrontier = this.highlightFrontier = firstLine + let start = Pos(firstLine, 0) + this.sel = simpleSelection(start) + this.history = new History(null) + this.id = ++nextDocId + this.modeOption = mode + this.lineSep = lineSep + this.direction = (direction == "rtl") ? "rtl" : "ltr" + this.extend = false + + if (typeof text == "string") text = this.splitLines(text) + updateDoc(this, {from: start, to: start, text: text}) + setSelection(this, simpleSelection(start), sel_dontScroll) +} + +Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + 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) + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + let height = 0 + for (let i = 0; i < lines.length; ++i) height += lines[i].height + this.insertInner(at - this.first, lines, height) + }, + remove: function(at, n) { this.removeInner(at - this.first, n) }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + let lines = getLines(this, this.first, this.first + this.size) + if (lineSep === false) return lines + return lines.join(lineSep || this.lineSeparator()) + }, + setValue: docMethodOp(function(code) { + let top = Pos(this.first, 0), last = this.first + this.size - 1 + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: this.splitLines(code), origin: "setValue", full: true}, true) + if (this.cm) scrollToCoords(this.cm, 0, 0) + setSelection(this, simpleSelection(top), sel_dontScroll) + }), + 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) { + let lines = getBetween(this, clipPos(this, from), clipPos(this, to)) + if (lineSep === false) return lines + return lines.join(lineSep || this.lineSeparator()) + }, + + getLine: function(line) {let l = this.getLineHandle(line); return l && l.text}, + + 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(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) { + let range = this.sel.primary(), pos + if (start == null || start == "head") pos = range.head + else if (start == "anchor") pos = range.anchor + else if (start == "end" || start == "to" || start === false) pos = range.to() + else pos = range.from() + return pos + }, + listSelections: function() { return this.sel.ranges }, + somethingSelected: function() {return this.sel.somethingSelected()}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options) + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options) + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options) + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads), options) + }), + extendSelectionsBy: docMethodOp(function(f, options) { + let heads = map(this.sel.ranges, f) + extendSelections(this, clipPosArray(this, heads), options) + }), + setSelections: docMethodOp(function(ranges, primary, options) { + if (!ranges.length) return + let out = [] + for (let i = 0; i < ranges.length; i++) + out[i] = new Range(clipPos(this, ranges[i].anchor), + clipPos(this, ranges[i].head)) + if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex) + setSelection(this, normalizeSelection(out, primary), options) + }), + addSelection: docMethodOp(function(anchor, head, options) { + let ranges = this.sel.ranges.slice(0) + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))) + setSelection(this, normalizeSelection(ranges, ranges.length - 1), options) + }), + + getSelection: function(lineSep) { + let ranges = this.sel.ranges, lines + for (let i = 0; i < ranges.length; i++) { + let sel = getBetween(this, ranges[i].from(), ranges[i].to()) + lines = lines ? lines.concat(sel) : sel + } + if (lineSep === false) return lines + else return lines.join(lineSep || this.lineSeparator()) + }, + getSelections: function(lineSep) { + let parts = [], ranges = this.sel.ranges + for (let i = 0; i < ranges.length; i++) { + let sel = getBetween(this, ranges[i].from(), ranges[i].to()) + if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator()) + parts[i] = sel + } + return parts + }, + replaceSelection: function(code, collapse, origin) { + let dup = [] + for (let i = 0; i < this.sel.ranges.length; i++) + dup[i] = code + this.replaceSelections(dup, collapse, origin || "+input") + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + let changes = [], sel = this.sel + for (let i = 0; i < sel.ranges.length; i++) { + let range = sel.ranges[i] + changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin} + } + let newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse) + for (let i = changes.length - 1; i >= 0; i--) + makeChange(this, changes[i]) + if (newSel) setSelectionReplaceHistory(this, newSel) + else if (this.cm) ensureCursorVisible(this.cm) + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true)}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true)}), + + setExtending: function(val) {this.extend = val}, + getExtending: function() {return this.extend}, + + historySize: function() { + let hist = this.history, done = 0, undone = 0 + for (let i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done + for (let i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone + return {undo: done, redo: undone} + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration)}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true) + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastSelOp = 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) { + let hist = this.history = new History(this.history.maxGeneration) + hist.done = copyHistoryArray(histData.done.slice(0), null, true) + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true) + }, + + setGutterMarker: docMethodOp(function(line, gutterID, value) { + return changeLine(this, line, "gutter", line => { + let markers = line.gutterMarkers || (line.gutterMarkers = {}) + markers[gutterID] = value + if (!value && isEmpty(markers)) line.gutterMarkers = null + return true + }) + }), + + clearGutter: docMethodOp(function(gutterID) { + this.iter(line => { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + changeLine(this, line, "gutter", () => { + line.gutterMarkers[gutterID] = null + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null + return true + }) + } + }) + }), + + lineInfo: function(line) { + let n + if (typeof line == "number") { + if (!isLine(this, line)) return null + n = line + line = getLine(this, line) + if (!line) return null + } else { + 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} + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", line => { + let prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass" + if (!line[prop]) line[prop] = cls + else if (classTest(cls).test(line[prop])) return false + else line[prop] += " " + cls + return true + }) + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", line => { + let prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass" + let cur = line[prop] + if (!cur) return false + else if (cls == null) line[prop] = null + else { + let found = cur.match(classTest(cls)) + if (!found) return false + let 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: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options) + }), + removeLineWidget: function(widget) { widget.clear() }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") + }, + setBookmark: function(pos, options) { + let realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents} + pos = clipPos(this, pos) + return markText(this, pos, pos, realOpts, "bookmark") + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos) + let markers = [], spans = getLine(this, pos.line).markedSpans + if (spans) for (let i = 0; i < spans.length; ++i) { + let 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 + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to) + let found = [], lineNo = from.line + this.iter(from.line, to.line + 1, line => { + let spans = line.markedSpans + if (spans) for (let i = 0; i < spans.length; i++) { + let span = spans[i] + if (!(span.to != null && lineNo == from.line && from.ch >= span.to || + span.from == null && lineNo != from.line || + span.from != null && lineNo == to.line && span.from >= to.ch) && + (!filter || filter(span.marker))) + found.push(span.marker.parent || span.marker) + } + ++lineNo + }) + return found + }, + getAllMarks: function() { + let markers = [] + this.iter(line => { + let sps = line.markedSpans + if (sps) for (let i = 0; i < sps.length; ++i) + if (sps[i].from != null) markers.push(sps[i].marker) + }) + return markers + }, + + posFromIndex: function(off) { + let ch, lineNo = this.first, sepSize = this.lineSeparator().length + this.iter(line => { + let sz = line.text.length + sepSize + if (sz > off) { ch = off; return true } + off -= sz + ++lineNo + }) + return clipPos(this, Pos(lineNo, ch)) + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords) + let index = coords.ch + if (coords.line < this.first || coords.ch < 0) return 0 + let sepSize = this.lineSeparator().length + this.iter(this.first, coords.line, line => { // iter aborts when callback returns a truthy value + index += line.text.length + sepSize + }) + return index + }, + + copy: function(copyHistory) { + let doc = new Doc(getLines(this, this.first, this.first + this.size), + this.modeOption, this.first, this.lineSep, this.direction) + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft + doc.sel = this.sel + doc.extend = false + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth + doc.setHistory(this.getHistory()) + } + return doc + }, + + linkedDoc: function(options) { + if (!options) options = {} + let 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 + let copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction) + if (options.sharedHist) copy.history = this.history + ;(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}) + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}] + copySharedMarkers(copy, findSharedMarkers(this)) + return copy + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) other = other.doc + if (this.linked) for (let i = 0; i < this.linked.length; ++i) { + let link = this.linked[i] + if (link.doc != other) continue + this.linked.splice(i, 1) + other.unlinkDoc(this) + detachSharedMarkers(findSharedMarkers(this)) + break + } + // If the histories were shared, split them again + if (other.history == this.history) { + let splitIds = [other.id] + linkedDocs(other, doc => splitIds.push(doc.id), true) + other.history = new History(null) + 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}, + + splitLines: function(str) { + if (this.lineSep) return str.split(this.lineSep) + return splitLinesAuto(str) + }, + lineSeparator: function() { return this.lineSep || "\n" }, + + setDirection: docMethodOp(function (dir) { + if (dir != "rtl") dir = "ltr" + if (dir == this.direction) return + this.direction = dir + this.iter(line => line.order = null) + if (this.cm) directionChanged(this.cm) + }) +}) + +// Public alias. +Doc.prototype.eachLine = Doc.prototype.iter + +export default Doc diff --git a/modules/editor/codemirror/src/model/Doc.min.js b/modules/editor/codemirror/src/model/Doc.min.js @@ -0,0 +1,432 @@ +import CodeMirror from "../edit/CodeMirror.js" +import { docMethodOp } from "../display/operations.js" +import { Line } from "../line/line_data.js" +import { clipPos, clipPosArray, Pos } from "../line/pos.js" +import { visualLine } from "../line/spans.js" +import { getBetween, getLine, getLines, isLine, lineNo } from "../line/utils_line.js" +import { classTest } from "../util/dom.js" +import { splitLinesAuto } from "../util/feature_detection.js" +import { createObj, map, isEmpty, sel_dontScroll } from "../util/misc.js" +import { ensureCursorVisible, scrollToCoords } from "../display/scrolling.js" + +import { changeLine, makeChange, makeChangeFromHistory, replaceRange } from "./changes.js" +import { computeReplacedSel } from "./change_measurement.js" +import { BranchChunk, LeafChunk } from "./chunk.js" +import { directionChanged, linkedDocs, updateDoc } from "./document_data.js" +import { copyHistoryArray, History } from "./history.js" +import { addLineWidget } from "./line_widget.js" +import { copySharedMarkers, detachSharedMarkers, findSharedMarkers, markText } from "./mark_text.js" +import { normalizeSelection, Range, simpleSelection } from "./selection.js" +import { extendSelection, extendSelections, setSelection, setSelectionReplaceHistory, setSimpleSelection } from "./selection_updates.js" + +let nextDocId = 0 +let Doc = function(text, mode, firstLine, lineSep, direction) { + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine, lineSep, direction) + 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.cleanGeneration = 1 + this.modeFrontier = this.highlightFrontier = firstLine + let start = Pos(firstLine, 0) + this.sel = simpleSelection(start) + this.history = new History(null) + this.id = ++nextDocId + this.modeOption = mode + this.lineSep = lineSep + this.direction = (direction == "rtl") ? "rtl" : "ltr" + this.extend = false + + if (typeof text == "string") text = this.splitLines(text) + updateDoc(this, {from: start, to: start, text: text}) + setSelection(this, simpleSelection(start), sel_dontScroll) +} + +Doc.prototype = createObj(BranchChunk.prototype, { + constructor: Doc, + // Iterate over the document. Supports two forms -- with only one + // argument, it calls that for each line in the document. With + // three, it iterates over the range given by the first two (with + // the second being non-inclusive). + 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) + }, + + // Non-public interface for adding and removing lines. + insert: function(at, lines) { + let height = 0 + for (let i = 0; i < lines.length; ++i) height += lines[i].height + this.insertInner(at - this.first, lines, height) + }, + remove: function(at, n) { this.removeInner(at - this.first, n) }, + + // From here, the methods are part of the public interface. Most + // are also available from CodeMirror (editor) instances. + + getValue: function(lineSep) { + let lines = getLines(this, this.first, this.first + this.size) + if (lineSep === false) return lines + return lines.join(lineSep || this.lineSeparator()) + }, + setValue: docMethodOp(function(code) { + let top = Pos(this.first, 0), last = this.first + this.size - 1 + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length), + text: this.splitLines(code), origin: "setValue", full: true}, true) + if (this.cm) scrollToCoords(this.cm, 0, 0) + setSelection(this, simpleSelection(top), sel_dontScroll) + }), + 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) { + let lines = getBetween(this, clipPos(this, from), clipPos(this, to)) + if (lineSep === false) return lines + return lines.join(lineSep || this.lineSeparator()) + }, + + getLine: function(line) {let l = this.getLineHandle(line); return l && l.text}, + + 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(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) { + let range = this.sel.primary(), pos + if (start == null || start == "head") pos = range.head + else if (start == "anchor") pos = range.anchor + else if (start == "end" || start == "to" || start === false) pos = range.to() + else pos = range.from() + return pos + }, + listSelections: function() { return this.sel.ranges }, + somethingSelected: function() {return this.sel.somethingSelected()}, + + setCursor: docMethodOp(function(line, ch, options) { + setSimpleSelection(this, clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line), null, options) + }), + setSelection: docMethodOp(function(anchor, head, options) { + setSimpleSelection(this, clipPos(this, anchor), clipPos(this, head || anchor), options) + }), + extendSelection: docMethodOp(function(head, other, options) { + extendSelection(this, clipPos(this, head), other && clipPos(this, other), options) + }), + extendSelections: docMethodOp(function(heads, options) { + extendSelections(this, clipPosArray(this, heads), options) + }), + extendSelectionsBy: docMethodOp(function(f, options) { + let heads = map(this.sel.ranges, f) + extendSelections(this, clipPosArray(this, heads), options) + }), + setSelections: docMethodOp(function(ranges, primary, options) { + if (!ranges.length) return + let out = [] + for (let i = 0; i < ranges.length; i++) + out[i] = new Range(clipPos(this, ranges[i].anchor), + clipPos(this, ranges[i].head)) + if (primary == null) primary = Math.min(ranges.length - 1, this.sel.primIndex) + setSelection(this, normalizeSelection(out, primary), options) + }), + addSelection: docMethodOp(function(anchor, head, options) { + let ranges = this.sel.ranges.slice(0) + ranges.push(new Range(clipPos(this, anchor), clipPos(this, head || anchor))) + setSelection(this, normalizeSelection(ranges, ranges.length - 1), options) + }), + + getSelection: function(lineSep) { + let ranges = this.sel.ranges, lines + for (let i = 0; i < ranges.length; i++) { + let sel = getBetween(this, ranges[i].from(), ranges[i].to()) + lines = lines ? lines.concat(sel) : sel + } + if (lineSep === false) return lines + else return lines.join(lineSep || this.lineSeparator()) + }, + getSelections: function(lineSep) { + let parts = [], ranges = this.sel.ranges + for (let i = 0; i < ranges.length; i++) { + let sel = getBetween(this, ranges[i].from(), ranges[i].to()) + if (lineSep !== false) sel = sel.join(lineSep || this.lineSeparator()) + parts[i] = sel + } + return parts + }, + replaceSelection: function(code, collapse, origin) { + let dup = [] + for (let i = 0; i < this.sel.ranges.length; i++) + dup[i] = code + this.replaceSelections(dup, collapse, origin || "+input") + }, + replaceSelections: docMethodOp(function(code, collapse, origin) { + let changes = [], sel = this.sel + for (let i = 0; i < sel.ranges.length; i++) { + let range = sel.ranges[i] + changes[i] = {from: range.from(), to: range.to(), text: this.splitLines(code[i]), origin: origin} + } + let newSel = collapse && collapse != "end" && computeReplacedSel(this, changes, collapse) + for (let i = changes.length - 1; i >= 0; i--) + makeChange(this, changes[i]) + if (newSel) setSelectionReplaceHistory(this, newSel) + else if (this.cm) ensureCursorVisible(this.cm) + }), + undo: docMethodOp(function() {makeChangeFromHistory(this, "undo")}), + redo: docMethodOp(function() {makeChangeFromHistory(this, "redo")}), + undoSelection: docMethodOp(function() {makeChangeFromHistory(this, "undo", true)}), + redoSelection: docMethodOp(function() {makeChangeFromHistory(this, "redo", true)}), + + setExtending: function(val) {this.extend = val}, + getExtending: function() {return this.extend}, + + historySize: function() { + let hist = this.history, done = 0, undone = 0 + for (let i = 0; i < hist.done.length; i++) if (!hist.done[i].ranges) ++done + for (let i = 0; i < hist.undone.length; i++) if (!hist.undone[i].ranges) ++undone + return {undo: done, redo: undone} + }, + clearHistory: function() {this.history = new History(this.history.maxGeneration)}, + + markClean: function() { + this.cleanGeneration = this.changeGeneration(true) + }, + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastSelOp = 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) { + let hist = this.history = new History(this.history.maxGeneration) + hist.done = copyHistoryArray(histData.done.slice(0), null, true) + hist.undone = copyHistoryArray(histData.undone.slice(0), null, true) + }, + + setGutterMarker: docMethodOp(function(line, gutterID, value) { + return changeLine(this, line, "gutter", line => { + let markers = line.gutterMarkers || (line.gutterMarkers = {}) + markers[gutterID] = value + if (!value && isEmpty(markers)) line.gutterMarkers = null + return true + }) + }), + + clearGutter: docMethodOp(function(gutterID) { + this.iter(line => { + if (line.gutterMarkers && line.gutterMarkers[gutterID]) { + changeLine(this, line, "gutter", () => { + line.gutterMarkers[gutterID] = null + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null + return true + }) + } + }) + }), + + lineInfo: function(line) { + let n + if (typeof line == "number") { + if (!isLine(this, line)) return null + n = line + line = getLine(this, line) + if (!line) return null + } else { + 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} + }, + + addLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", line => { + let prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass" + if (!line[prop]) line[prop] = cls + else if (classTest(cls).test(line[prop])) return false + else line[prop] += " " + cls + return true + }) + }), + removeLineClass: docMethodOp(function(handle, where, cls) { + return changeLine(this, handle, where == "gutter" ? "gutter" : "class", line => { + let prop = where == "text" ? "textClass" + : where == "background" ? "bgClass" + : where == "gutter" ? "gutterClass" : "wrapClass" + let cur = line[prop] + if (!cur) return false + else if (cls == null) line[prop] = null + else { + let found = cur.match(classTest(cls)) + if (!found) return false + let 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: docMethodOp(function(handle, node, options) { + return addLineWidget(this, handle, node, options) + }), + removeLineWidget: function(widget) { widget.clear() }, + + markText: function(from, to, options) { + return markText(this, clipPos(this, from), clipPos(this, to), options, options && options.type || "range") + }, + setBookmark: function(pos, options) { + let realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), + insertLeft: options && options.insertLeft, + clearWhenEmpty: false, shared: options && options.shared, + handleMouseEvents: options && options.handleMouseEvents} + pos = clipPos(this, pos) + return markText(this, pos, pos, realOpts, "bookmark") + }, + findMarksAt: function(pos) { + pos = clipPos(this, pos) + let markers = [], spans = getLine(this, pos.line).markedSpans + if (spans) for (let i = 0; i < spans.length; ++i) { + let 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 + }, + findMarks: function(from, to, filter) { + from = clipPos(this, from); to = clipPos(this, to) + let found = [], lineNo = from.line + this.iter(from.line, to.line + 1, line => { + let spans = line.markedSpans + if (spans) for (let i = 0; i < spans.length; i++) { + let span = spans[i] + if (!(span.to != null && lineNo == from.line && from.ch >= span.to || + span.from == null && lineNo != from.line || + span.from != null && lineNo == to.line && span.from >= to.ch) && + (!filter || filter(span.marker))) + found.push(span.marker.parent || span.marker) + } + ++lineNo + }) + return found + }, + getAllMarks: function() { + let markers = [] + this.iter(line => { + let sps = line.markedSpans + if (sps) for (let i = 0; i < sps.length; ++i) + if (sps[i].from != null) markers.push(sps[i].marker) + }) + return markers + }, + + posFromIndex: function(off) { + let ch, lineNo = this.first, sepSize = this.lineSeparator().length + this.iter(line => { + let sz = line.text.length + sepSize + if (sz > off) { ch = off; return true } + off -= sz + ++lineNo + }) + return clipPos(this, Pos(lineNo, ch)) + }, + indexFromPos: function (coords) { + coords = clipPos(this, coords) + let index = coords.ch + if (coords.line < this.first || coords.ch < 0) return 0 + let sepSize = this.lineSeparator().length + this.iter(this.first, coords.line, line => { // iter aborts when callback returns a truthy value + index += line.text.length + sepSize + }) + return index + }, + + copy: function(copyHistory) { + let doc = new Doc(getLines(this, this.first, this.first + this.size), + this.modeOption, this.first, this.lineSep, this.direction) + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft + doc.sel = this.sel + doc.extend = false + if (copyHistory) { + doc.history.undoDepth = this.history.undoDepth + doc.setHistory(this.getHistory()) + } + return doc + }, + + linkedDoc: function(options) { + if (!options) options = {} + let 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 + let copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from, this.lineSep, this.direction) + if (options.sharedHist) copy.history = this.history + ;(this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist}) + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}] + copySharedMarkers(copy, findSharedMarkers(this)) + return copy + }, + unlinkDoc: function(other) { + if (other instanceof CodeMirror) other = other.doc + if (this.linked) for (let i = 0; i < this.linked.length; ++i) { + let link = this.linked[i] + if (link.doc != other) continue + this.linked.splice(i, 1) + other.unlinkDoc(this) + detachSharedMarkers(findSharedMarkers(this)) + break + } + // If the histories were shared, split them again + if (other.history == this.history) { + let splitIds = [other.id] + linkedDocs(other, doc => splitIds.push(doc.id), true) + other.history = new History(null) + 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}, + + splitLines: function(str) { + if (this.lineSep) return str.split(this.lineSep) + return splitLinesAuto(str) + }, + lineSeparator: function() { return this.lineSep || "\n" }, + + setDirection: docMethodOp(function (dir) { + if (dir != "rtl") dir = "ltr" + if (dir == this.direction) return + this.direction = dir + this.iter(line => line.order = null) + if (this.cm) directionChanged(this.cm) + }) +}) + +// Public alias. +Doc.prototype.eachLine = Doc.prototype.iter + +export default Doc diff --git a/modules/editor/codemirror/src/model/chunk.min.js b/modules/editor/codemirror/src/model/chunk.min.js @@ -0,0 +1,167 @@ +import { cleanUpLine } from "../line/line_data.js" +import { indexOf } from "../util/misc.js" +import { signalLater } from "../util/operation_group.js" + +// The document is represented as a BTree consisting of leaves, with +// chunk of lines in them, and branches, with up to ten leaves or +// other branch nodes below them. The top node is always a branch +// node, and is the document object itself (meaning it has +// additional methods and properties). +// +// All nodes have parent links. The tree is used both to go from +// line numbers to line objects, and to go from objects to numbers. +// It also indexes by height, and is used to convert between height +// and line object, and to find the total height of the document. +// +// See also http://marijnhaverbeke.nl/blog/codemirror-line-tree.html + +export function LeafChunk(lines) { + this.lines = lines + this.parent = null + let height = 0 + for (let i = 0; i < lines.length; ++i) { + lines[i].parent = this + height += lines[i].height + } + this.height = height +} + +LeafChunk.prototype = { + chunkSize() { return this.lines.length }, + + // Remove the n lines at offset 'at'. + removeInner(at, n) { + for (let i = at, e = at + n; i < e; ++i) { + let line = this.lines[i] + this.height -= line.height + cleanUpLine(line) + signalLater(line, "delete") + } + this.lines.splice(at, n) + }, + + // Helper used to collapse a small branch into a single leaf. + collapse(lines) { + lines.push.apply(lines, this.lines) + }, + + // Insert the given array of lines at offset 'at', count them as + // having the given height. + insertInner(at, lines, height) { + this.height += height + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)) + for (let i = 0; i < lines.length; ++i) lines[i].parent = this + }, + + // Used to iterate over a part of the tree. + iterN(at, n, op) { + for (let e = at + n; at < e; ++at) + if (op(this.lines[at])) return true + } +} + +export function BranchChunk(children) { + this.children = children + let size = 0, height = 0 + for (let i = 0; i < children.length; ++i) { + let ch = children[i] + size += ch.chunkSize(); height += ch.height + ch.parent = this + } + this.size = size + this.height = height + this.parent = null +} + +BranchChunk.prototype = { + chunkSize() { return this.size }, + + removeInner(at, n) { + this.size -= n + for (let i = 0; i < this.children.length; ++i) { + let child = this.children[i], sz = child.chunkSize() + if (at < sz) { + let 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 the result is smaller than 25 lines, ensure that it is a + // single leaf node. + if (this.size - n < 25 && + (this.children.length > 1 || !(this.children[0] instanceof LeafChunk))) { + let lines = [] + this.collapse(lines) + this.children = [new LeafChunk(lines)] + this.children[0].parent = this + } + }, + + collapse(lines) { + for (let i = 0; i < this.children.length; ++i) this.children[i].collapse(lines) + }, + + insertInner(at, lines, height) { + this.size += lines.length + this.height += height + for (let i = 0; i < this.children.length; ++i) { + let child = this.children[i], sz = child.chunkSize() + if (at <= sz) { + child.insertInner(at, lines, height) + if (child.lines && child.lines.length > 50) { + // To avoid memory thrashing when child.lines is huge (e.g. first view of a large file), it's never spliced. + // Instead, small slices are taken. They're taken in order because sequential memory accesses are fastest. + let remaining = child.lines.length % 25 + 25 + for (let pos = remaining; pos < child.lines.length;) { + let leaf = new LeafChunk(child.lines.slice(pos, pos += 25)) + child.height -= leaf.height + this.children.splice(++i, 0, leaf) + leaf.parent = this + } + child.lines = child.lines.slice(0, remaining) + this.maybeSpill() + } + break + } + at -= sz + } + }, + + // When a node has grown, check whether it should be split. + maybeSpill() { + if (this.children.length <= 10) return + let me = this + do { + let spilled = me.children.splice(me.children.length - 5, 5) + let sibling = new BranchChunk(spilled) + if (!me.parent) { // Become the parent node + let copy = new BranchChunk(me.children) + copy.parent = me + me.children = [copy, sibling] + me = copy + } else { + me.size -= sibling.size + me.height -= sibling.height + let 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(at, n, op) { + for (let i = 0; i < this.children.length; ++i) { + let child = this.children[i], sz = child.chunkSize() + if (at < sz) { + let used = Math.min(n, sz - at) + if (child.iterN(at, used, op)) return true + if ((n -= used) == 0) break + at = 0 + } else at -= sz + } + } +} diff --git a/modules/editor/codemirror/src/model/document_data.js b/modules/editor/codemirror/src/model/document_data.js @@ -0,0 +1,111 @@ +import { loadMode } from "../display/mode_state.js" +import { runInOp } from "../display/operations.js" +import { regChange } from "../display/view_tracking.js" +import { Line, updateLine } from "../line/line_data.js" +import { findMaxLine } from "../line/spans.js" +import { getLine } from "../line/utils_line.js" +import { estimateLineHeights } from "../measurement/position_measurement.js" +import { addClass, rmClass } from "../util/dom.js" +import { lst } from "../util/misc.js" +import { signalLater } from "../util/operation_group.js" + +// DOCUMENT DATA STRUCTURE + +// By default, updates that start and end at the beginning of a line +// are treated specially, in order to make the association of line +// widgets and marker elements with the text behave more intuitive. +export function isWholeLineUpdate(doc, change) { + return change.from.ch == 0 && change.to.ch == 0 && lst(change.text) == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore) +} + +// Perform a change on the document data structure. +export function updateDoc(doc, change, markedSpans, estimateHeight) { + function spansFor(n) {return markedSpans ? markedSpans[n] : null} + function update(line, text, spans) { + updateLine(line, text, spans, estimateHeight) + signalLater(line, "change", line, change) + } + function linesFor(start, end) { + let result = [] + for (let i = start; i < end; ++i) + result.push(new Line(text[i], spansFor(i), estimateHeight)) + return result + } + + let from = change.from, to = change.to, text = change.text + let firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line) + let lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line + + // Adjust the line structure + if (change.full) { + doc.insert(0, linesFor(0, text.length)) + doc.remove(text.length, doc.size - text.length) + } else if (isWholeLineUpdate(doc, change)) { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + let added = linesFor(0, text.length - 1) + update(lastLine, lastLine.text, lastSpans) + if (nlines) doc.remove(from.line, nlines) + if (added.length) doc.insert(from.line, added) + } else if (firstLine == lastLine) { + if (text.length == 1) { + update(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch), lastSpans) + } else { + let added = linesFor(1, text.length - 1) + added.push(new Line(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight)) + update(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0)) + doc.insert(from.line + 1, added) + } + } 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) + let added = linesFor(1, text.length - 1) + if (nlines > 1) doc.remove(from.line + 1, nlines - 1) + doc.insert(from.line + 1, added) + } + + signalLater(doc, "change", doc, change) +} + +// Call f for all linked documents. +export function linkedDocs(doc, f, sharedHistOnly) { + function propagate(doc, skip, sharedHist) { + if (doc.linked) for (let i = 0; i < doc.linked.length; ++i) { + let rel = doc.linked[i] + if (rel.doc == skip) continue + let shared = sharedHist && rel.sharedHist + if (sharedHistOnly && !shared) continue + f(rel.doc, shared) + propagate(rel.doc, doc, shared) + } + } + propagate(doc, null, true) +} + +// Attach a document to an editor. +export 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) + setDirectionClass(cm) + if (!cm.options.lineWrapping) findMaxLine(cm) + cm.options.mode = doc.modeOption + regChange(cm) +} + +function setDirectionClass(cm) { + ;(cm.doc.direction == "rtl" ? addClass : rmClass)(cm.display.lineDiv, "CodeMirror-rtl") +} + +export function directionChanged(cm) { + runInOp(cm, () => { + setDirectionClass(cm) + regChange(cm) + }) +} diff --git a/modules/editor/codemirror/src/model/history.min.js b/modules/editor/codemirror/src/model/history.min.js @@ -0,0 +1,228 @@ +import { cmp, copyPos } from "../line/pos.js" +import { stretchSpansOverChange } from "../line/spans.js" +import { getBetween } from "../line/utils_line.js" +import { signal } from "../util/event.js" +import { indexOf, lst } from "../util/misc.js" + +import { changeEnd } from "./change_measurement.js" +import { linkedDocs } from "./document_data.js" +import { Selection } from "./selection.js" + +export function History(startGen) { + // Arrays of change events and selections. Doing something adds an + // event to done and clears undo. Undoing moves events from done + // to undone, redoing moves them in the other direction. + this.done = []; this.undone = [] + this.undoDepth = Infinity + // Used to track when changes can be merged into a single undo + // event + this.lastModTime = this.lastSelTime = 0 + this.lastOp = this.lastSelOp = null + this.lastOrigin = this.lastSelOrigin = null + // Used by the isClean() method + this.generation = this.maxGeneration = startGen || 1 +} + +// Create a history change event from an updateDoc-style change +// object. +export function historyChangeFromChange(doc, change) { + let histChange = {from: copyPos(change.from), to: changeEnd(change), text: getBetween(doc, change.from, change.to)} + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1) + linkedDocs(doc, doc => attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1), true) + return histChange +} + +// Pop all selection events off the end of a history array. Stop at +// a change event. +function clearSelectionEvents(array) { + while (array.length) { + let last = lst(array) + if (last.ranges) array.pop() + else break + } +} + +// Find the top change event in the history. Pop off selection +// events that are in the way. +function lastChangeEvent(hist, force) { + if (force) { + clearSelectionEvents(hist.done) + return lst(hist.done) + } else if (hist.done.length && !lst(hist.done).ranges) { + return lst(hist.done) + } else if (hist.done.length > 1 && !hist.done[hist.done.length - 2].ranges) { + hist.done.pop() + return lst(hist.done) + } +} + +// Register a change in the history. Merges changes that are within +// a single operation, or are close together with an origin that +// allows merging (starting with "+") into a single event. +export function addChangeToHistory(doc, change, selAfter, opId) { + let hist = doc.history + hist.undone.length = 0 + let time = +new Date, cur + let last + + if ((hist.lastOp == opId || + hist.lastOrigin == change.origin && change.origin && + ((change.origin.charAt(0) == "+" && doc.cm && hist.lastModTime > time - doc.cm.options.historyEventDelay) || + change.origin.charAt(0) == "*")) && + (cur = lastChangeEvent(hist, hist.lastOp == opId))) { + // Merge this change into the last event + last = lst(cur.changes) + if (cmp(change.from, change.to) == 0 && cmp(change.from, last.to) == 0) { + // 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)) + } + } else { + // Can not be merged, start a new event. + let before = lst(hist.done) + if (!before || !before.ranges) + pushSelectionToHistory(doc.sel, hist.done) + cur = {changes: [historyChangeFromChange(doc, change)], + generation: hist.generation} + hist.done.push(cur) + while (hist.done.length > hist.undoDepth) { + hist.done.shift() + if (!hist.done[0].ranges) hist.done.shift() + } + } + hist.done.push(selAfter) + hist.generation = ++hist.maxGeneration + hist.lastModTime = hist.lastSelTime = time + hist.lastOp = hist.lastSelOp = opId + hist.lastOrigin = hist.lastSelOrigin = change.origin + + if (!last) signal(doc, "historyAdded") +} + +function selectionEventCanBeMerged(doc, origin, prev, sel) { + let ch = origin.charAt(0) + return ch == "*" || + ch == "+" && + prev.ranges.length == sel.ranges.length && + prev.somethingSelected() == sel.somethingSelected() && + new Date - doc.history.lastSelTime <= (doc.cm ? doc.cm.options.historyEventDelay : 500) +} + +// Called whenever the selection changes, sets the new selection as +// the pending selection in the history, and pushes the old pending +// selection into the 'done' array when it was significantly +// different (in number of selected ranges, emptiness, or time). +export function addSelectionToHistory(doc, sel, opId, options) { + let hist = doc.history, origin = options && options.origin + + // A new event is started when the previous origin does not match + // the current, or the origins don't allow matching. Origins + // starting with * are always merged, those starting with + are + // merged when similar and close together in time. + if (opId == hist.lastSelOp || + (origin && hist.lastSelOrigin == origin && + (hist.lastModTime == hist.lastSelTime && hist.lastOrigin == origin || + selectionEventCanBeMerged(doc, origin, lst(hist.done), sel)))) + hist.done[hist.done.length - 1] = sel + else + pushSelectionToHistory(sel, hist.done) + + hist.lastSelTime = +new Date + hist.lastSelOrigin = origin + hist.lastSelOp = opId + if (options && options.clearRedo !== false) + clearSelectionEvents(hist.undone) +} + +export function pushSelectionToHistory(sel, dest) { + let top = lst(dest) + if (!(top && top.ranges && top.equals(sel))) + dest.push(sel) +} + +// Used to store marked span information in the history. +function attachLocalSpans(doc, change, from, to) { + let existing = change["spans_" + doc.id], n = 0 + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), line => { + if (line.markedSpans) + (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans + ++n + }) +} + +// When un/re-doing restores text containing marked spans, those +// that have been explicitly cleared should not be restored. +function removeClearedSpans(spans) { + if (!spans) return null + let out + for (let i = 0; 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 +} + +// Retrieve and filter the old marked spans stored in a change event. +function getOldSpans(doc, change) { + let found = change["spans_" + doc.id] + if (!found) return null + let nw = [] + for (let i = 0; i < change.text.length; ++i) + nw.push(removeClearedSpans(found[i])) + return nw +} + +// Used for un/re-doing changes from the history. Combines the +// result of computing the existing spans with the set of spans that +// existed in the history (so that deleting around a span and then +// undoing brings back the span). +export function mergeOldSpans(doc, change) { + let old = getOldSpans(doc, change) + let stretched = stretchSpansOverChange(doc, change) + if (!old) return stretched + if (!stretched) return old + + for (let i = 0; i < old.length; ++i) { + let oldCur = old[i], stretchCur = stretched[i] + if (oldCur && stretchCur) { + spans: for (let j = 0; j < stretchCur.length; ++j) { + let span = stretchCur[j] + for (let 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 +} + +// Used both to provide a JSON-safe object in .getHistory, and, when +// detaching a document, to split the history in two +export function copyHistoryArray(events, newGroup, instantiateSel) { + let copy = [] + for (let i = 0; i < events.length; ++i) { + let event = events[i] + if (event.ranges) { + copy.push(instantiateSel ? Selection.prototype.deepCopy.call(event) : event) + continue + } + let changes = event.changes, newChanges = [] + copy.push({changes: newChanges}) + for (let j = 0; j < changes.length; ++j) { + let 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 +} diff --git a/modules/editor/codemirror/src/model/selection_updates.js b/modules/editor/codemirror/src/model/selection_updates.js @@ -0,0 +1,208 @@ +import { signalLater } from "../util/operation_group.js" +import { ensureCursorVisible } from "../display/scrolling.js" +import { clipPos, cmp, Pos } from "../line/pos.js" +import { getLine } from "../line/utils_line.js" +import { hasHandler, signal, signalCursorActivity } from "../util/event.js" +import { lst, sel_dontScroll } from "../util/misc.js" + +import { addSelectionToHistory } from "./history.js" +import { normalizeSelection, Range, Selection, simpleSelection } from "./selection.js" + +// The 'scroll' parameter given to many of these indicated whether +// the new cursor position should be scrolled into view after +// modifying the selection. + +// If shift is held or the extend flag is set, extends a range to +// include a given position (and optionally a second position). +// Otherwise, simply returns the range between the given positions. +// Used for cursor motion and such. +export function extendRange(range, head, other, extend) { + if (extend) { + let anchor = range.anchor + if (other) { + let posBefore = cmp(head, anchor) < 0 + if (posBefore != (cmp(other, anchor) < 0)) { + anchor = head + head = other + } else if (posBefore != (cmp(head, other) < 0)) { + head = other + } + } + return new Range(anchor, head) + } else { + return new Range(other || head, head) + } +} + +// Extend the primary selection range, discard the rest. +export function extendSelection(doc, head, other, options, extend) { + if (extend == null) extend = doc.cm && (doc.cm.display.shift || doc.extend) + setSelection(doc, new Selection([extendRange(doc.sel.primary(), head, other, extend)], 0), options) +} + +// Extend all selections (pos is an array of selections with length +// equal the number of selections) +export function extendSelections(doc, heads, options) { + let out = [] + let extend = doc.cm && (doc.cm.display.shift || doc.extend) + for (let i = 0; i < doc.sel.ranges.length; i++) + out[i] = extendRange(doc.sel.ranges[i], heads[i], null, extend) + let newSel = normalizeSelection(out, doc.sel.primIndex) + setSelection(doc, newSel, options) +} + +// Updates a single range in the selection. +export function replaceOneSelection(doc, i, range, options) { + let ranges = doc.sel.ranges.slice(0) + ranges[i] = range + setSelection(doc, normalizeSelection(ranges, doc.sel.primIndex), options) +} + +// Reset the selection to a single range. +export function setSimpleSelection(doc, anchor, head, options) { + setSelection(doc, simpleSelection(anchor, head), options) +} + +// Give beforeSelectionChange handlers a change to influence a +// selection update. +function filterSelectionChange(doc, sel, options) { + let obj = { + ranges: sel.ranges, + update: function(ranges) { + this.ranges = [] + for (let i = 0; i < ranges.length; i++) + this.ranges[i] = new Range(clipPos(doc, ranges[i].anchor), + clipPos(doc, ranges[i].head)) + }, + origin: options && options.origin + } + signal(doc, "beforeSelectionChange", doc, obj) + if (doc.cm) signal(doc.cm, "beforeSelectionChange", doc.cm, obj) + if (obj.ranges != sel.ranges) return normalizeSelection(obj.ranges, obj.ranges.length - 1) + else return sel +} + +export function setSelectionReplaceHistory(doc, sel, options) { + let done = doc.history.done, last = lst(done) + if (last && last.ranges) { + done[done.length - 1] = sel + setSelectionNoUndo(doc, sel, options) + } else { + setSelection(doc, sel, options) + } +} + +// Set a new selection. +export function setSelection(doc, sel, options) { + setSelectionNoUndo(doc, sel, options) + addSelectionToHistory(doc, doc.sel, doc.cm ? doc.cm.curOp.id : NaN, options) +} + +export function setSelectionNoUndo(doc, sel, options) { + if (hasHandler(doc, "beforeSelectionChange") || doc.cm && hasHandler(doc.cm, "beforeSelectionChange")) + sel = filterSelectionChange(doc, sel, options) + + let bias = options && options.bias || + (cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1) + setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true)) + + if (!(options && options.scroll === false) && doc.cm) + ensureCursorVisible(doc.cm) +} + +function setSelectionInner(doc, sel) { + if (sel.equals(doc.sel)) return + + doc.sel = sel + + if (doc.cm) { + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true + signalCursorActivity(doc.cm) + } + signalLater(doc, "cursorActivity", doc) +} + +// Verify that the selection does not partially select any atomic +// marked ranges. +export function reCheckSelection(doc) { + setSelectionInner(doc, skipAtomicInSelection(doc, doc.sel, null, false)) +} + +// Return a selection that does not partially select any atomic +// ranges. +function skipAtomicInSelection(doc, sel, bias, mayClear) { + let out + for (let i = 0; i < sel.ranges.length; i++) { + let range = sel.ranges[i] + let old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i] + let newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear) + let newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear) + if (out || newAnchor != range.anchor || newHead != range.head) { + if (!out) out = sel.ranges.slice(0, i) + out[i] = new Range(newAnchor, newHead) + } + } + return out ? normalizeSelection(out, sel.primIndex) : sel +} + +function skipAtomicInner(doc, pos, oldPos, dir, mayClear) { + let line = getLine(doc, pos.line) + if (line.markedSpans) for (let i = 0; i < line.markedSpans.length; ++i) { + let sp = line.markedSpans[i], m = sp.marker + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= pos.ch : sp.from < pos.ch)) && + (sp.to == null || (m.inclusiveRight ? sp.to >= pos.ch : sp.to > pos.ch))) { + if (mayClear) { + signal(m, "beforeCursorEnter") + if (m.explicitlyCleared) { + if (!line.markedSpans) break + else {--i; continue} + } + } + if (!m.atomic) continue + + if (oldPos) { + let near = m.find(dir < 0 ? 1 : -1), diff + if (dir < 0 ? m.inclusiveRight : m.inclusiveLeft) + near = movePos(doc, near, -dir, near && near.line == pos.line ? line : null) + if (near && near.line == pos.line && (diff = cmp(near, oldPos)) && (dir < 0 ? diff < 0 : diff > 0)) + return skipAtomicInner(doc, near, pos, dir, mayClear) + } + + let far = m.find(dir < 0 ? -1 : 1) + if (dir < 0 ? m.inclusiveLeft : m.inclusiveRight) + far = movePos(doc, far, dir, far.line == pos.line ? line : null) + return far ? skipAtomicInner(doc, far, pos, dir, mayClear) : null + } + } + return pos +} + +// Ensure a given position is not inside an atomic range. +export function skipAtomic(doc, pos, oldPos, bias, mayClear) { + let dir = bias || 1 + let found = skipAtomicInner(doc, pos, oldPos, dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, dir, true)) || + skipAtomicInner(doc, pos, oldPos, -dir, mayClear) || + (!mayClear && skipAtomicInner(doc, pos, oldPos, -dir, true)) + if (!found) { + doc.cantEdit = true + return Pos(doc.first, 0) + } + return found +} + +function movePos(doc, pos, dir, line) { + if (dir < 0 && pos.ch == 0) { + if (pos.line > doc.first) return clipPos(doc, Pos(pos.line - 1)) + else return null + } else if (dir > 0 && pos.ch == (line || getLine(doc, pos.line)).text.length) { + if (pos.line < doc.first + doc.size - 1) return Pos(pos.line + 1, 0) + else return null + } else { + return new Pos(pos.line, pos.ch + dir) + } +} + +export function selectAll(cm) { + cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll) +} diff --git a/modules/editor/codemirror/src/modes.js b/modules/editor/codemirror/src/modes.js @@ -0,0 +1,96 @@ +import { copyObj, createObj } from "./util/misc.js" + +// Known modes, by name and by MIME +export let modes = {}, mimeModes = {} + +// Extra arguments are stored as the mode's dependencies, which is +// used by (legacy) mechanisms like loadmode.js to automatically +// load a mode. (Preferred mechanism is the require/define calls.) +export function defineMode(name, mode) { + if (arguments.length > 2) + mode.dependencies = Array.prototype.slice.call(arguments, 2) + modes[name] = mode +} + +export function defineMIME(mime, spec) { + mimeModes[mime] = spec +} + +// Given a MIME type, a {name, ...options} config object, or a name +// string, return a mode config object. +export function resolveMode(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec] + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + let found = mimeModes[spec.name] + if (typeof found == "string") found = {name: found} + spec = createObj(found, spec) + spec.name = found.name + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { + return resolveMode("application/xml") + } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) { + return resolveMode("application/json") + } + if (typeof spec == "string") return {name: spec} + else return spec || {name: "null"} +} + +// Given a mode spec (anything that resolveMode accepts), find and +// initialize an actual mode object. +export function getMode(options, spec) { + spec = resolveMode(spec) + let mfactory = modes[spec.name] + if (!mfactory) return getMode(options, "text/plain") + let modeObj = mfactory(options, spec) + if (modeExtensions.hasOwnProperty(spec.name)) { + let exts = modeExtensions[spec.name] + for (let prop in exts) { + if (!exts.hasOwnProperty(prop)) continue + if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop] + modeObj[prop] = exts[prop] + } + } + modeObj.name = spec.name + if (spec.helperType) modeObj.helperType = spec.helperType + if (spec.modeProps) for (let prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop] + + return modeObj +} + +// This can be used to attach properties to mode objects from +// outside the actual mode definition. +export let modeExtensions = {} +export function extendMode(mode, properties) { + let exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {}) + copyObj(properties, exts) +} + +export function copyState(mode, state) { + if (state === true) return state + if (mode.copyState) return mode.copyState(state) + let nstate = {} + for (let n in state) { + let val = state[n] + if (val instanceof Array) val = val.concat([]) + nstate[n] = val + } + return nstate +} + +// Given a mode and a state (for that mode), find the inner mode and +// state at the position that the state refers to. +export function innerMode(mode, state) { + let info + while (mode.innerMode) { + info = mode.innerMode(state) + if (!info || info.mode == mode) break + state = info.state + mode = info.mode + } + return info || {mode: mode, state: state} +} + +export function startState(mode, a1, a2) { + return mode.startState ? mode.startState(a1, a2) : true +} diff --git a/modules/editor/codemirror/src/util/StringStream.js b/modules/editor/codemirror/src/util/StringStream.js @@ -0,0 +1,90 @@ +import { countColumn } from "./misc.js" + +// STRING STREAM + +// Fed to the mode parsers, provides helper functions to make +// parsers more succinct. + +class StringStream { + constructor(string, tabSize, lineOracle) { + this.pos = this.start = 0 + this.string = string + this.tabSize = tabSize || 8 + this.lastColumnPos = this.lastColumnValue = 0 + this.lineStart = 0 + this.lineOracle = lineOracle + } + + eol() {return this.pos >= this.string.length} + sol() {return this.pos == this.lineStart} + peek() {return this.string.charAt(this.pos) || undefined} + next() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++) + } + eat(match) { + let ch = this.string.charAt(this.pos) + let ok + if (typeof match == "string") ok = ch == match + else ok = ch && (match.test ? match.test(ch) : match(ch)) + if (ok) {++this.pos; return ch} + } + eatWhile(match) { + let start = this.pos + while (this.eat(match)){} + return this.pos > start + } + eatSpace() { + let start = this.pos + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos + return this.pos > start + } + skipToEnd() {this.pos = this.string.length} + skipTo(ch) { + let found = this.string.indexOf(ch, this.pos) + if (found > -1) {this.pos = found; return true} + } + backUp(n) {this.pos -= n} + column() { + 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 - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + } + indentation() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + } + match(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + let cased = str => caseInsensitive ? str.toLowerCase() : str + let substr = this.string.substr(this.pos, pattern.length) + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length + return true + } + } else { + let 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(){return this.string.slice(this.start, this.pos)} + hideFirstChars(n, inner) { + this.lineStart += n + try { return inner() } + finally { this.lineStart -= n } + } + lookAhead(n) { + let oracle = this.lineOracle + return oracle && oracle.lookAhead(n) + } + baseToken() { + let oracle = this.lineOracle + return oracle && oracle.baseToken(this.pos) + } +} + +export default StringStream diff --git a/modules/editor/codemirror/src/util/StringStream.min.js b/modules/editor/codemirror/src/util/StringStream.min.js @@ -0,0 +1,90 @@ +import { countColumn } from "./misc.js" + +// STRING STREAM + +// Fed to the mode parsers, provides helper functions to make +// parsers more succinct. + +class StringStream { + constructor(string, tabSize, lineOracle) { + this.pos = this.start = 0 + this.string = string + this.tabSize = tabSize || 8 + this.lastColumnPos = this.lastColumnValue = 0 + this.lineStart = 0 + this.lineOracle = lineOracle + } + + eol() {return this.pos >= this.string.length} + sol() {return this.pos == this.lineStart} + peek() {return this.string.charAt(this.pos) || undefined} + next() { + if (this.pos < this.string.length) + return this.string.charAt(this.pos++) + } + eat(match) { + let ch = this.string.charAt(this.pos) + let ok + if (typeof match == "string") ok = ch == match + else ok = ch && (match.test ? match.test(ch) : match(ch)) + if (ok) {++this.pos; return ch} + } + eatWhile(match) { + let start = this.pos + while (this.eat(match)){} + return this.pos > start + } + eatSpace() { + let start = this.pos + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos + return this.pos > start + } + skipToEnd() {this.pos = this.string.length} + skipTo(ch) { + let found = this.string.indexOf(ch, this.pos) + if (found > -1) {this.pos = found; return true} + } + backUp(n) {this.pos -= n} + column() { + 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 - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + } + indentation() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0) + } + match(pattern, consume, caseInsensitive) { + if (typeof pattern == "string") { + let cased = str => caseInsensitive ? str.toLowerCase() : str + let substr = this.string.substr(this.pos, pattern.length) + if (cased(substr) == cased(pattern)) { + if (consume !== false) this.pos += pattern.length + return true + } + } else { + let 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(){return this.string.slice(this.start, this.pos)} + hideFirstChars(n, inner) { + this.lineStart += n + try { return inner() } + finally { this.lineStart -= n } + } + lookAhead(n) { + let oracle = this.lineOracle + return oracle && oracle.lookAhead(n) + } + baseToken() { + let oracle = this.lineOracle + return oracle && oracle.baseToken(this.pos) + } +} + +export default StringStream diff --git a/modules/editor/codemirror/src/util/bidi.js b/modules/editor/codemirror/src/util/bidi.js @@ -0,0 +1,214 @@ +import { lst } from "./misc.js" + +// BIDI HELPERS + +export function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr", 0) + let found = false + for (let i = 0; i < order.length; ++i) { + let 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", i) + found = true + } + } + if (!found) f(from, to, "ltr") +} + +export let bidiOther = null +export function getBidiPartAt(order, ch, sticky) { + let found + bidiOther = null + for (let i = 0; i < order.length; ++i) { + let cur = order[i] + if (cur.from < ch && cur.to > ch) return i + if (cur.to == ch) { + if (cur.from != cur.to && sticky == "before") found = i + else bidiOther = i + } + if (cur.from == ch) { + if (cur.from != cur.to && sticky != "before") found = i + else bidiOther = i + } + } + return found != null ? found : bidiOther +} + +// 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. +let bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + let lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN" + // Character types for codepoints 0x600 to 0x6f9 + let arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111" + function charType(code) { + if (code <= 0xf7) return lowTypes.charAt(code) + else if (0x590 <= code && code <= 0x5f4) return "R" + else if (0x600 <= code && code <= 0x6f9) return arabicTypes.charAt(code - 0x600) + else if (0x6ee <= code && code <= 0x8ac) return "r" + else if (0x2000 <= code && code <= 0x200b) return "w" + else if (code == 0x200c) return "b" + else return "L" + } + + let bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/ + let isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/ + + function BidiSpan(level, from, to) { + this.level = level + this.from = from; this.to = to + } + + return function(str, direction) { + let outerType = direction == "ltr" ? "L" : "R" + + if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) return false + let len = str.length, types = [] + for (let i = 0; i < len; ++i) + types.push(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 (let i = 0, prev = outerType; i < len; ++i) { + let 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 (let i = 0, cur = outerType; i < len; ++i) { + let 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 (let i = 1, prev = types[0]; i < len - 1; ++i) { + let 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 (let i = 0; i < len; ++i) { + let type = types[i] + if (type == ",") types[i] = "N" + else if (type == "%") { + let end + for (end = i + 1; end < len && types[end] == "%"; ++end) {} + let replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N" + for (let 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 (let i = 0, cur = outerType; i < len; ++i) { + let 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 (let i = 0; i < len; ++i) { + if (isNeutral.test(types[i])) { + let end + for (end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} + let before = (i ? types[i-1] : outerType) == "L" + let after = (end < len ? types[end] : outerType) == "L" + let replace = before == after ? (before ? "L" : "R") : outerType + for (let 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. + let order = [], m + for (let i = 0; i < len;) { + if (countsAsLeft.test(types[i])) { + let start = i + for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} + order.push(new BidiSpan(0, start, i)) + } else { + let pos = i, at = order.length + for (++i; i < len && types[i] != "L"; ++i) {} + for (let j = pos; j < i;) { + if (countsAsNum.test(types[j])) { + if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)) + let nstart = j + for (++j; j < i && countsAsNum.test(types[j]); ++j) {} + order.splice(at, 0, new BidiSpan(2, nstart, j)) + pos = j + } else ++j + } + if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)) + } + } + if (direction == "ltr") { + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length + order.unshift(new BidiSpan(0, 0, m[0].length)) + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length + order.push(new BidiSpan(0, len - m[0].length, len)) + } + } + + return direction == "rtl" ? order.reverse() : order + } +})() + +// Get the bidi ordering for the given line (and cache it). Returns +// false for lines that are fully left-to-right, and an array of +// BidiSpan objects otherwise. +export function getOrder(line, direction) { + let order = line.order + if (order == null) order = line.order = bidiOrdering(line.text, direction) + return order +} diff --git a/modules/editor/codemirror/src/util/bidi.min.js b/modules/editor/codemirror/src/util/bidi.min.js @@ -0,0 +1,214 @@ +import { lst } from "./misc.js" + +// BIDI HELPERS + +export function iterateBidiSections(order, from, to, f) { + if (!order) return f(from, to, "ltr", 0) + let found = false + for (let i = 0; i < order.length; ++i) { + let 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", i) + found = true + } + } + if (!found) f(from, to, "ltr") +} + +export let bidiOther = null +export function getBidiPartAt(order, ch, sticky) { + let found + bidiOther = null + for (let i = 0; i < order.length; ++i) { + let cur = order[i] + if (cur.from < ch && cur.to > ch) return i + if (cur.to == ch) { + if (cur.from != cur.to && sticky == "before") found = i + else bidiOther = i + } + if (cur.from == ch) { + if (cur.from != cur.to && sticky != "before") found = i + else bidiOther = i + } + } + return found != null ? found : bidiOther +} + +// 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. +let bidiOrdering = (function() { + // Character types for codepoints 0 to 0xff + let lowTypes = "bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN" + // Character types for codepoints 0x600 to 0x6f9 + let arabicTypes = "nnnnnnNNr%%r,rNNmmmmmmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmmmnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmnNmmmmmmrrmmNmmmmrr1111111111" + function charType(code) { + if (code <= 0xf7) return lowTypes.charAt(code) + else if (0x590 <= code && code <= 0x5f4) return "R" + else if (0x600 <= code && code <= 0x6f9) return arabicTypes.charAt(code - 0x600) + else if (0x6ee <= code && code <= 0x8ac) return "r" + else if (0x2000 <= code && code <= 0x200b) return "w" + else if (code == 0x200c) return "b" + else return "L" + } + + let bidiRE = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/ + let isNeutral = /[stwN]/, isStrong = /[LRr]/, countsAsLeft = /[Lb1n]/, countsAsNum = /[1n]/ + + function BidiSpan(level, from, to) { + this.level = level + this.from = from; this.to = to + } + + return function(str, direction) { + let outerType = direction == "ltr" ? "L" : "R" + + if (str.length == 0 || direction == "ltr" && !bidiRE.test(str)) return false + let len = str.length, types = [] + for (let i = 0; i < len; ++i) + types.push(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 (let i = 0, prev = outerType; i < len; ++i) { + let 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 (let i = 0, cur = outerType; i < len; ++i) { + let 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 (let i = 1, prev = types[0]; i < len - 1; ++i) { + let 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 (let i = 0; i < len; ++i) { + let type = types[i] + if (type == ",") types[i] = "N" + else if (type == "%") { + let end + for (end = i + 1; end < len && types[end] == "%"; ++end) {} + let replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N" + for (let 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 (let i = 0, cur = outerType; i < len; ++i) { + let 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 (let i = 0; i < len; ++i) { + if (isNeutral.test(types[i])) { + let end + for (end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} + let before = (i ? types[i-1] : outerType) == "L" + let after = (end < len ? types[end] : outerType) == "L" + let replace = before == after ? (before ? "L" : "R") : outerType + for (let 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. + let order = [], m + for (let i = 0; i < len;) { + if (countsAsLeft.test(types[i])) { + let start = i + for (++i; i < len && countsAsLeft.test(types[i]); ++i) {} + order.push(new BidiSpan(0, start, i)) + } else { + let pos = i, at = order.length + for (++i; i < len && types[i] != "L"; ++i) {} + for (let j = pos; j < i;) { + if (countsAsNum.test(types[j])) { + if (pos < j) order.splice(at, 0, new BidiSpan(1, pos, j)) + let nstart = j + for (++j; j < i && countsAsNum.test(types[j]); ++j) {} + order.splice(at, 0, new BidiSpan(2, nstart, j)) + pos = j + } else ++j + } + if (pos < i) order.splice(at, 0, new BidiSpan(1, pos, i)) + } + } + if (direction == "ltr") { + if (order[0].level == 1 && (m = str.match(/^\s+/))) { + order[0].from = m[0].length + order.unshift(new BidiSpan(0, 0, m[0].length)) + } + if (lst(order).level == 1 && (m = str.match(/\s+$/))) { + lst(order).to -= m[0].length + order.push(new BidiSpan(0, len - m[0].length, len)) + } + } + + return direction == "rtl" ? order.reverse() : order + } +})() + +// Get the bidi ordering for the given line (and cache it). Returns +// false for lines that are fully left-to-right, and an array of +// BidiSpan objects otherwise. +export function getOrder(line, direction) { + let order = line.order + if (order == null) order = line.order = bidiOrdering(line.text, direction) + return order +} diff --git a/modules/editor/codemirror/src/util/browser.min.js b/modules/editor/codemirror/src/util/browser.min.js @@ -0,0 +1,33 @@ +// Kludges for bugs and behavior differences that can't be feature +// detected are enabled based on userAgent etc sniffing. +let userAgent = navigator.userAgent +let platform = navigator.platform + +export let gecko = /gecko\/\d/i.test(userAgent) +let ie_upto10 = /MSIE \d/.test(userAgent) +let ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(userAgent) +let edge = /Edge\/(\d+)/.exec(userAgent) +export let ie = ie_upto10 || ie_11up || edge +export let ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]) +export let webkit = !edge && /WebKit\//.test(userAgent) +let qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent) +export let chrome = !edge && /Chrome\//.test(userAgent) +export let presto = /Opera\//.test(userAgent) +export let safari = /Apple Computer/.test(navigator.vendor) +export let mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent) +export let phantom = /PhantomJS/.test(userAgent) + +export let ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent) +export let android = /Android/.test(userAgent) +// This is woefully incomplete. Suggestions for alternative methods welcome. +export let mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent) +export let mac = ios || /Mac/.test(platform) +export let chromeOS = /\bCrOS\b/.test(userAgent) +export let windows = /win/i.test(platform) + +let presto_version = presto && userAgent.match(/Version\/(\d*\.\d*)/) +if (presto_version) presto_version = Number(presto_version[1]) +if (presto_version && presto_version >= 15) { presto = false; webkit = true } +// Some browsers use the wrong event properties to signal cmd/ctrl on OS X +export let flipCtrlCmd = mac && (qtwebkit || presto && (presto_version == null || presto_version < 12.11)) +export let captureRightClick = gecko || (ie && ie_version >= 9) diff --git a/modules/editor/codemirror/src/util/feature_detection.js b/modules/editor/codemirror/src/util/feature_detection.js @@ -0,0 +1,84 @@ +import { elt, range, removeChildren, removeChildrenAndAdd } from "./dom.js" +import { ie, ie_version } from "./browser.js" + +// Detect drag-and-drop +export let 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 && ie_version < 9) return false + let div = elt('div') + return "draggable" in div || "dragDrop" in div +}() + +let zwspSupported +export function zeroWidthElement(measure) { + if (zwspSupported == null) { + let 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 && ie_version < 8) + } + let node = zwspSupported ? elt("span", "\u200b") : + elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px") + node.setAttribute("cm-text", "") + return node +} + +// Feature-detect IE's crummy client rect reporting for bidi text +let badBidiRects +export function hasBadBidiRects(measure) { + if (badBidiRects != null) return badBidiRects + let txt = removeChildrenAndAdd(measure, document.createTextNode("A\u062eA")) + let r0 = range(txt, 0, 1).getBoundingClientRect() + let r1 = range(txt, 1, 2).getBoundingClientRect() + removeChildren(measure) + if (!r0 || r0.left == r0.right) return false // Safari returns null in some cases (#2780) + return badBidiRects = (r1.right - r0.right < 3) +} + +// See if "".split is the broken IE version, if so, provide an +// alternative way to split lines. +export let splitLinesAuto = "\n\nb".split(/\n/).length != 3 ? string => { + let pos = 0, result = [], l = string.length + while (pos <= l) { + let nl = string.indexOf("\n", pos) + if (nl == -1) nl = string.length + let line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl) + let 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 +} : string => string.split(/\r\n?|\n/) + +export let hasSelection = window.getSelection ? te => { + try { return te.selectionStart != te.selectionEnd } + catch(e) { return false } +} : te => { + let range + try {range = te.ownerDocument.selection.createRange()} + catch(e) {} + if (!range || range.parentElement() != te) return false + return range.compareEndPoints("StartToEnd", range) != 0 +} + +export let hasCopyEvent = (() => { + let e = elt("div") + if ("oncopy" in e) return true + e.setAttribute("oncopy", "return;") + return typeof e.oncopy == "function" +})() + +let badZoomedRects = null +export function hasBadZoomedRects(measure) { + if (badZoomedRects != null) return badZoomedRects + let node = removeChildrenAndAdd(measure, elt("span", "x")) + let normal = node.getBoundingClientRect() + let fromRange = range(node, 0, 1).getBoundingClientRect() + return badZoomedRects = Math.abs(normal.left - fromRange.left) > 1 +} diff --git a/modules/editor/codemirror/test/comment_test.min.js b/modules/editor/codemirror/test/comment_test.min.js @@ -0,0 +1,114 @@ +namespace = "comment_"; + +(function() { + function test(name, mode, run, before, after) { + return testCM(name, function(cm) { + run(cm); + eq(cm.getValue(), after); + }, {value: before, mode: mode}); + } + + var simpleProg = "function foo() {\n return bar;\n}"; + var inlineBlock = "foo(/* bar */ true);"; + var inlineBlocks = "foo(/* bar */ true, /* baz */ false);"; + var multiLineInlineBlock = ["above();", "foo(/* bar */ true);", "below();"]; + + test("block", "javascript", function(cm) { + cm.blockComment(Pos(0, 3), Pos(3, 0), {blockCommentLead: " *"}); + }, simpleProg + "\n", "/* function foo() {\n * return bar;\n * }\n */"); + + test("blockToggle", "javascript", function(cm) { + cm.blockComment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"}); + cm.uncomment(Pos(0, 3), Pos(2, 0), {blockCommentLead: " *"}); + }, simpleProg, simpleProg); + + test("blockToggle2", "javascript", function(cm) { + cm.setCursor({line: 0, ch: 7 /* inside the block comment */}); + cm.execCommand("toggleComment"); + }, inlineBlock, "foo(bar true);"); + + // This test should work but currently fails. + // test("blockToggle3", "javascript", function(cm) { + // cm.setCursor({line: 0, ch: 7 /* inside the first block comment */}); + // cm.execCommand("toggleComment"); + // }, inlineBlocks, "foo(bar true, /* baz */ false);"); + + test("line", "javascript", function(cm) { + cm.lineComment(Pos(1, 1), Pos(1, 1)); + }, simpleProg, "function foo() {\n// return bar;\n}"); + + test("lineToggle", "javascript", function(cm) { + cm.lineComment(Pos(0, 0), Pos(2, 1)); + cm.uncomment(Pos(0, 0), Pos(2, 1)); + }, simpleProg, simpleProg); + + test("fallbackToBlock", "css", function(cm) { + cm.lineComment(Pos(0, 0), Pos(2, 1)); + }, "html {\n border: none;\n}", "/* html {\n border: none;\n} */"); + + test("fallbackToLine", "ruby", function(cm) { + cm.blockComment(Pos(0, 0), Pos(1)); + }, "def blah()\n return hah\n", "# def blah()\n# return hah\n"); + + test("ignoreExternalBlockComments", "javascript", function(cm) { + cm.execCommand("toggleComment"); + }, inlineBlocks, "// " + inlineBlocks); + + test("ignoreExternalBlockComments2", "javascript", function(cm) { + cm.setCursor({line: 0, ch: null /* eol */}); + cm.execCommand("toggleComment"); + }, inlineBlocks, "// " + inlineBlocks); + + test("ignoreExternalBlockCommentsMultiLineAbove", "javascript", function(cm) { + cm.setSelection({line: 0, ch: 0}, {line: 1, ch: 1}); + cm.execCommand("toggleComment"); + }, multiLineInlineBlock.join("\n"), ["// " + multiLineInlineBlock[0], + "// " + multiLineInlineBlock[1], + multiLineInlineBlock[2]].join("\n")); + + test("ignoreExternalBlockCommentsMultiLineBelow", "javascript", function(cm) { + cm.setSelection({line: 1, ch: 13 /* after end of block comment */}, {line: 2, ch: 1}); + cm.execCommand("toggleComment"); + }, multiLineInlineBlock.join("\n"), [multiLineInlineBlock[0], + "// " + multiLineInlineBlock[1], + "// " + multiLineInlineBlock[2]].join("\n")); + + test("commentRange", "javascript", function(cm) { + cm.blockComment(Pos(1, 2), Pos(1, 13), {fullLines: false}); + }, simpleProg, "function foo() {\n /*return bar;*/\n}"); + + test("indented", "javascript", function(cm) { + cm.lineComment(Pos(1, 0), Pos(2), {indent: true}); + }, simpleProg, "function foo() {\n// return bar;\n// }"); + + test("singleEmptyLine", "javascript", function(cm) { + cm.setCursor(1); + cm.execCommand("toggleComment"); + }, "a;\n\nb;", "a;\n// \nb;"); + + test("dontMessWithStrings", "javascript", function(cm) { + cm.execCommand("toggleComment"); + }, "console.log(\"/*string*/\");", "// console.log(\"/*string*/\");"); + + test("dontMessWithStrings2", "javascript", function(cm) { + cm.execCommand("toggleComment"); + }, "console.log(\"// string\");", "// console.log(\"// string\");"); + + test("dontMessWithStrings3", "javascript", function(cm) { + cm.execCommand("toggleComment"); + }, "// console.log(\"// string\");", "console.log(\"// string\");"); + + test("includeLastLine", "javascript", function(cm) { + cm.execCommand("selectAll") + cm.execCommand("toggleComment") + }, "// foo\n// bar\nbaz", "// // foo\n// // bar\n// baz") + + test("uncommentWithTrailingBlockEnd", "xml", function(cm) { + cm.execCommand("toggleComment") + }, "<!-- foo --> -->", "foo -->") + + test("dontCommentInComment", "xml", function(cm) { + cm.setCursor(1, 0) + cm.execCommand("toggleComment") + }, "<!-- foo\nbar -->", "<!-- foo\nbar -->") +})(); diff --git a/modules/editor/codemirror/test/driver.js b/modules/editor/codemirror/test/driver.js @@ -0,0 +1,142 @@ +var tests = [], filters = [], allNames = []; + +function Failure(why) {this.message = why;} +Failure.prototype.toString = function() { return this.message; }; + +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 test(name, run, expectedFail) { + // Force unique names + var originalName = name; + var i = 2; // Second function would be NAME_2 + while (indexOf(allNames, name) !== -1){ + name = originalName + "_" + i; + i++; + } + allNames.push(name); + // Add test + tests.push({name: name, func: run, expectedFail: expectedFail}); + return name; +} +var namespace = ""; +function testCM(name, run, opts, expectedFail) { + return test(namespace + name, function() { + var place = document.getElementById("testground"), cm = window.cm = CodeMirror(place, opts); + var successful = false; + try { + run(cm); + successful = true; + } finally { + if (!successful || verbose) { + place.style.visibility = "visible"; + } else { + place.removeChild(cm.getWrapperElement()); + } + } + }, expectedFail); +} + +function runTests(callback) { + var totalTime = 0; + function step(i) { + for (;;) { + if (i === tests.length) { + running = false; + return callback("done"); + } + var test = tests[i], skip = false; + if (filters.length) { + skip = true; + for (var j = 0; j < filters.length; j++) + if (test.name.match(filters[j])) skip = false; + } + if (skip) { + callback("skipped", test.name, message); + i++; + } else { + break; + } + } + var expFail = test.expectedFail, startTime = +new Date, threw = false; + try { + var message = test.func(); + } catch(e) { + threw = true; + if (expFail) callback("expected", test.name); + else if (e instanceof Failure) callback("fail", test.name, e.message); + else { + var pos = /(?:\bat |@).*?([^\/:]+):(\d+)/.exec(e.stack); + if (pos) console["log"](e.stack); + callback("error", test.name, e.toString() + (pos ? " (" + pos[1] + ":" + pos[2] + ")" : "")); + } + } + if (!threw) { + if (expFail) callback("fail", test.name, message || "expected failure, but passed"); + else callback("ok", test.name, message); + } + if (!quit) { // Run next test + var delay = 0; + totalTime += (+new Date) - startTime; + if (totalTime > 500){ + totalTime = 0; + delay = 50; + } + setTimeout(function(){step(i + 1);}, delay); + } else { // Quit tests + running = false; + return null; + } + } + step(0); +} + +function label(str, msg) { + if (msg) return str + " (" + msg + ")"; + return str; +} +function eq(a, b, msg) { + if (a != b) throw new Failure(label(a + " != " + b, msg)); +} +function near(a, b, margin, msg) { + if (Math.abs(a - b) > margin) + throw new Failure(label(a + " is not close to " + b + " (" + margin + ")", msg)); +} +function eqCharPos(a, b, msg) { + function str(p) { return "{line:" + p.line + ",ch:" + p.ch + ",sticky:" + p.sticky + "}"; } + if (a == b) return; + if (a == null) throw new Failure(label("comparing null to " + str(b), msg)); + if (b == null) throw new Failure(label("comparing " + str(a) + " to null", msg)); + if (a.line != b.line || a.ch != b.ch) throw new Failure(label(str(a) + " != " + str(b), msg)); +} +function eqCursorPos(a, b, msg) { + eqCharPos(a, b, msg); + if (a) eq(a.sticky, b.sticky, msg ? msg + ' (sticky)' : 'sticky'); +} +function is(a, msg) { + if (!a) throw new Failure(label("assertion failed", msg)); +} + +function countTests() { + if (!filters.length) return tests.length; + var sum = 0; + for (var i = 0; i < tests.length; ++i) { + var name = tests[i].name; + for (var j = 0; j < filters.length; j++) { + if (name.match(filters[j])) { + ++sum; + break; + } + } + } + return sum; +} + +function parseTestFilter(s) { + if (/_\*$/.test(s)) return new RegExp("^" + s.slice(0, s.length - 2), "i"); + else return new RegExp(s, "i"); +} diff --git a/modules/editor/codemirror/test/index.html b/modules/editor/codemirror/test/index.html @@ -0,0 +1,269 @@ +<!doctype html> + +<meta charset="utf-8"/> +<title>CodeMirror: Test Suite</title> +<link rel=stylesheet href="../doc/docs.css"> + +<link rel="stylesheet" href="../lib/codemirror.css"> +<link rel="stylesheet" href="mode_test.css"> +<script src="../doc/activebookmark.js"></script> +<script src="../lib/codemirror.js"></script> +<script src="../mode/meta.js"></script> +<script src="../addon/mode/overlay.js"></script> +<script src="../addon/mode/multiplex.js"></script> +<script src="../addon/search/searchcursor.js"></script> +<script src="../addon/dialog/dialog.js"></script> +<script src="../addon/edit/matchbrackets.js"></script> +<script src="../addon/hint/sql-hint.js"></script> +<script src="../addon/comment/comment.js"></script> +<script src="../addon/mode/simple.js"></script> +<script src="../mode/css/css.js"></script> +<script src="../mode/clike/clike.js"></script> +<!-- clike must be after css or vim and sublime tests will fail --> +<script src="../mode/cypher/cypher.js"></script> +<script src="../mode/d/d.js"></script> +<script src="../mode/gfm/gfm.js"></script> +<script src="../mode/haml/haml.js"></script> +<script src="../mode/htmlmixed/htmlmixed.js"></script> +<script src="../mode/javascript/javascript.js"></script> +<script src="../mode/jsx/jsx.js"></script> +<script src="../mode/markdown/markdown.js"></script> +<script src="../mode/php/php.js"></script> +<script src="../mode/python/python.js"></script> +<script src="../mode/powershell/powershell.js"></script> +<script src="../mode/ruby/ruby.js"></script> +<script src="../mode/sass/sass.js"></script> +<script src="../mode/shell/shell.js"></script> +<script src="../mode/slim/slim.js"></script> +<script src="../mode/soy/soy.js"></script> +<script src="../mode/sql/sql.js"></script> +<script src="../mode/stex/stex.js"></script> +<script src="../mode/swift/swift.js"></script> +<script src="../mode/textile/textile.js"></script> +<script src="../mode/verilog/verilog.js"></script> +<script src="../mode/xml/xml.js"></script> +<script src="../mode/xquery/xquery.js"></script> +<script src="../keymap/emacs.js"></script> +<script src="../keymap/sublime.js"></script> +<script src="../keymap/vim.js"></script> +<script src="../mode/rust/rust.js"></script> +<script src="../mode/mscgen/mscgen.js"></script> +<script src="../mode/dylan/dylan.js"></script> + +<style type="text/css"> + .ok {color: #090;} + .fail {color: #e00;} + .error {color: #c90;} + .done {font-weight: bold;} + #progress { + background: #45d; + color: white; + text-shadow: 0 0 1px #45d, 0 0 2px #45d, 0 0 3px #45d; + font-weight: bold; + white-space: pre; + } + #testground { + visibility: hidden; + } + #testground.offscreen { + visibility: visible; + position: absolute; + left: -10000px; + top: -10000px; + } + .CodeMirror { border: 1px solid black; } +</style> + +<div id=nav> + <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../doc/logo.png"></a> + + <ul> + <li><a href="../index.html">Home</a> + <li><a href="../doc/manual.html">Manual</a> + <li><a href="https://github.com/codemirror/codemirror">Code</a> + </ul> + <ul> + <li><a class=active href="#">Test suite</a> + </ul> +</div> + +<article> + <h2>Test Suite</h2> + + <p>A limited set of programmatic sanity tests for CodeMirror.</p> + + <div style="border: 1px solid black; padding: 1px; max-width: 700px;"> + <div style="width: 0px;" id=progress><div style="padding: 3px;">Ran <span id="progress_ran">0</span><span id="progress_total"> of 0</span> tests</div></div> + </div> + <p id=status>Please enable JavaScript...</p> + <div id=output></div> + + <div id=testground></div> + + <script src="driver.js"></script> + <script src="test.js"></script> + <script src="doc_test.js"></script> + <script src="multi_test.js"></script> + <script src="contenteditable_test.js"></script> + <script src="scroll_test.js"></script> + <script src="comment_test.js"></script> + <script src="search_test.js"></script> + <script src="mode_test.js"></script> + + <script src="../mode/clike/test.js"></script> + <script src="../mode/css/test.js"></script> + <script src="../mode/css/gss_test.js"></script> + <script src="../mode/css/scss_test.js"></script> + <script src="../mode/css/less_test.js"></script> + <script src="../mode/cypher/test.js"></script> + <script src="../mode/d/test.js"></script> + <script src="../mode/gfm/test.js"></script> + <script src="../mode/haml/test.js"></script> + <script src="../mode/javascript/test.js"></script> + <script src="../mode/jsx/test.js"></script> + <script src="../mode/markdown/test.js"></script> + <script src="../mode/php/test.js"></script> + <script src="../mode/powershell/test.js"></script> + <script src="../mode/ruby/test.js"></script> + <script src="../mode/sass/test.js"></script> + <script src="../mode/shell/test.js"></script> + <script src="../mode/slim/test.js"></script> + <script src="../mode/soy/test.js"></script> + <script src="../mode/stex/test.js"></script> + <script src="../mode/swift/test.js"></script> + <script src="../mode/textile/test.js"></script> + <script src="../mode/verilog/test.js"></script> + <script src="../mode/xml/test.js"></script> + <script src="../mode/xquery/test.js"></script> + <script src="../mode/python/test.js"></script> + <script src="../mode/rust/test.js"></script> + <script src="../mode/mscgen/mscgen_test.js"></script> + <script src="../mode/mscgen/xu_test.js"></script> + <script src="../mode/mscgen/msgenny_test.js"></script> + <script src="../mode/dylan/test.js"></script> + <script src="../addon/mode/multiplex_test.js"></script> + <script src="emacs_test.js"></script> + <script src="sql-hint-test.js"></script> + <script src="sublime_test.js"></script> + <script src="vim_test.js"></script> + <script> + window.onload = runHarness; + CodeMirror.on(window, 'hashchange', runHarness); + + function esc(str) { + return str.replace(/[<&]/, function(ch) { return ch == "<" ? "&lt;" : "&amp;"; }); + } + + var output = document.getElementById("output"), + progress = document.getElementById("progress"), + progressRan = document.getElementById("progress_ran").childNodes[0], + progressTotal = document.getElementById("progress_total").childNodes[0]; + var count = 0, + failed = 0, + skipped = 0, + bad = "", + running = false, // Flag that states tests are running + quit = false, // Flag to quit tests ASAP + verbose = false; // Adds message for *every* test to output + + function runHarness(){ + if (running) { + quit = true; + setStatus("Restarting tests...", '', true); + setTimeout(function(){runHarness();}, 500); + return; + } + filters = []; + verbose = false; + if (window.location.hash.substr(1)){ + var strings = window.location.hash.substr(1).split(","); + while (strings.length) { + var s = strings.shift(); + if (s === "verbose") + verbose = true; + else + filters.push(parseTestFilter(decodeURIComponent(s))); + } + } + quit = false; + running = true; + setStatus("Loading tests..."); + count = 0; + failed = 0; + skipped = 0; + bad = ""; + totalTests = countTests(); + progressTotal.nodeValue = " of " + totalTests; + progressRan.nodeValue = count; + output.innerHTML = ''; + document.getElementById("testground").innerHTML = "<form>" + + "<textarea id=\"code\" name=\"code\"></textarea>" + + "<input type=submit value=ok name=submit>" + + "</form>"; + runTests(displayTest); + } + + function setStatus(message, className, force){ + if (quit && !force) return; + if (!message) throw("must provide message"); + var status = document.getElementById("status").childNodes[0]; + status.nodeValue = message; + status.parentNode.className = className; + } + function addOutput(name, className, code){ + var newOutput = document.createElement("dl"); + var newTitle = document.createElement("dt"); + newTitle.className = className; + newTitle.appendChild(document.createTextNode(name)); + newOutput.appendChild(newTitle); + var newMessage = document.createElement("dd"); + newMessage.innerHTML = code; + newOutput.appendChild(newTitle); + newOutput.appendChild(newMessage); + output.appendChild(newOutput); + } + function displayTest(type, name, customMessage) { + var message = "???"; + if (type != "done" && type != "skipped") ++count; + progress.style.width = (count * (progress.parentNode.clientWidth - 2) / (totalTests || 1)) + "px"; + progressRan.nodeValue = count; + if (type == "ok") { + message = "Test '" + name + "' passed"; + if (!verbose) customMessage = false; + } else if (type == "skipped") { + message = "Test '" + name + "' skipped"; + ++skipped; + if (!verbose) customMessage = false; + } else if (type == "expected") { + message = "Test '" + name + "' failed as expected"; + if (!verbose) customMessage = false; + } else if (type == "error" || type == "fail") { + ++failed; + message = "Test '" + name + "' failed"; + } else if (type == "done") { + if (failed) { + type += " fail"; + message = failed + " failure" + (failed > 1 ? "s" : ""); + } else if (count < totalTests) { + failed = totalTests - count; + type += " fail"; + message = failed + " failure" + (failed > 1 ? "s" : ""); + } else { + type += " ok"; + message = "All passed"; + if (skipped) { + message += " (" + skipped + " skipped)"; + } + } + progressTotal.nodeValue = ''; + customMessage = true; // Hack to avoid adding to output + } + if (verbose && !customMessage) customMessage = message; + setStatus(message, type); + if (customMessage && customMessage.length > 0) { + addOutput(name, type, customMessage); + } + } + </script> + +</article> diff --git a/modules/editor/codemirror/test/lint.min.js b/modules/editor/codemirror/test/lint.min.js @@ -0,0 +1,20 @@ +var blint = require("blint"); + +["mode", "lib", "addon", "keymap"].forEach(function(dir) { + blint.checkDir(dir, { + browser: true, + allowedGlobals: ["CodeMirror", "define", "test", "requirejs"], + ecmaVersion: 5, + tabs: dir == "lib" + }); +}); + +["src"].forEach(function(dir) { + blint.checkDir(dir, { + browser: true, + ecmaVersion: 6, + semicolons: false + }); +}); + +module.exports = {ok: blint.success()}; diff --git a/modules/editor/codemirror/test/mode_test.css b/modules/editor/codemirror/test/mode_test.css @@ -0,0 +1,23 @@ +.mt-output .mt-token { + border: 1px solid #ddd; + white-space: pre; + font-family: "Consolas", monospace; + text-align: center; +} + +.mt-output .mt-style { + font-size: x-small; +} + +.mt-output .mt-state { + font-size: x-small; + vertical-align: top; +} + +.mt-output .mt-state-row { + display: none; +} + +.mt-state-unhide .mt-output .mt-state-row { + display: table-row; +} diff --git a/modules/editor/codemirror/test/mode_test.min.js b/modules/editor/codemirror/test/mode_test.min.js @@ -0,0 +1,193 @@ +/** + * Helper to test CodeMirror highlighting modes. It pretty prints output of the + * highlighter and can check against expected styles. + * + * Mode tests are registered by calling test.mode(testName, mode, + * tokens), where mode is a mode object as returned by + * CodeMirror.getMode, and tokens is an array of lines that make up + * the test. + * + * These lines are strings, in which styled stretches of code are + * enclosed in brackets `[]`, and prefixed by their style. For + * example, `[keyword if]`. Brackets in the code itself must be + * duplicated to prevent them from being interpreted as token + * boundaries. For example `a[[i]]` for `a[i]`. If a token has + * multiple styles, the styles must be separated by ampersands, for + * example `[tag&error </hmtl>]`. + * + * See the test.js files in the css, markdown, gfm, and stex mode + * directories for examples. + */ +(function() { + function findSingle(str, pos, ch) { + for (;;) { + var found = str.indexOf(ch, pos); + if (found == -1) return null; + if (str.charAt(found + 1) != ch) return found; + pos = found + 2; + } + } + + var styleName = /[\w&-_]+/g; + function parseTokens(strs) { + var tokens = [], plain = ""; + for (var i = 0; i < strs.length; ++i) { + if (i) plain += "\n"; + var str = strs[i], pos = 0; + while (pos < str.length) { + var style = null, text; + if (str.charAt(pos) == "[" && str.charAt(pos+1) != "[") { + styleName.lastIndex = pos + 1; + var m = styleName.exec(str); + style = m[0].replace(/&/g, " "); + var textStart = pos + style.length + 2; + var end = findSingle(str, textStart, "]"); + if (end == null) throw new Error("Unterminated token at " + pos + " in '" + str + "'" + style); + text = str.slice(textStart, end); + pos = end + 1; + } else { + var end = findSingle(str, pos, "["); + if (end == null) end = str.length; + text = str.slice(pos, end); + pos = end; + } + text = text.replace(/\[\[|\]\]/g, function(s) {return s.charAt(0);}); + tokens.push({style: style, text: text}); + plain += text; + } + } + return {tokens: tokens, plain: plain}; + } + + test.mode = function(name, mode, tokens, modeName) { + var data = parseTokens(tokens); + return test((modeName || mode.name) + "_" + name, function() { + return compare(data.plain, data.tokens, mode); + }); + }; + + function esc(str) { + return str.replace('&', '&amp;').replace('<', '&lt;').replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;"); + } + + function compare(text, expected, mode) { + + var expectedOutput = []; + for (var i = 0; i < expected.length; ++i) { + var sty = expected[i].style; + if (sty && sty.indexOf(" ")) sty = sty.split(' ').sort().join(' '); + expectedOutput.push({style: sty, text: expected[i].text}); + } + + var observedOutput = highlight(text, mode); + + var s = ""; + var diff = highlightOutputsDifferent(expectedOutput, observedOutput); + if (diff != null) { + s += '<div class="mt-test mt-fail">'; + s += '<pre>' + esc(text) + '</pre>'; + s += '<div class="cm-s-default">'; + s += 'expected:'; + s += prettyPrintOutputTable(expectedOutput, diff); + s += 'observed: [<a onclick="this.parentElement.className+=\' mt-state-unhide\'">display states</a>]'; + s += prettyPrintOutputTable(observedOutput, diff); + s += '</div>'; + s += '</div>'; + } + if (observedOutput.indentFailures) { + for (var i = 0; i < observedOutput.indentFailures.length; i++) + s += "<div class='mt-test mt-fail'>" + esc(observedOutput.indentFailures[i]) + "</div>"; + } + if (s) throw new Failure(s); + } + + function stringify(obj) { + function replacer(key, obj) { + if (typeof obj == "function") { + var m = obj.toString().match(/function\s*[^\s(]*/); + return m ? m[0] : "function"; + } + return obj; + } + if (window.JSON && JSON.stringify) + return JSON.stringify(obj, replacer, 2); + return "[unsupported]"; // Fail safely if no native JSON. + } + + function highlight(string, mode) { + var state = mode.startState(); + + var lines = string.replace(/\r\n/g,'\n').split('\n'); + var st = [], pos = 0; + for (var i = 0; i < lines.length; ++i) { + var line = lines[i], newLine = true; + if (mode.indent) { + var ws = line.match(/^\s*/)[0]; + var indent = mode.indent(state, line.slice(ws.length)); + if (indent != CodeMirror.Pass && indent != ws.length) + (st.indentFailures || (st.indentFailures = [])).push( + "Indentation of line " + (i + 1) + " is " + indent + " (expected " + ws.length + ")"); + } + var stream = new CodeMirror.StringStream(line, 4, { + lookAhead: function(n) { return lines[i + n] } + }); + if (line == "" && mode.blankLine) mode.blankLine(state); + /* Start copied code from CodeMirror.highlight */ + while (!stream.eol()) { + for (var j = 0; j < 10 && stream.start >= stream.pos; j++) + var compare = mode.token(stream, state); + if (j == 10) + throw new Failure("Failed to advance the stream." + stream.string + " " + stream.pos); + var substr = stream.current(); + if (compare && compare.indexOf(" ") > -1) compare = compare.split(' ').sort().join(' '); + stream.start = stream.pos; + if (pos && st[pos-1].style == compare && !newLine) { + st[pos-1].text += substr; + } else if (substr) { + st[pos++] = {style: compare, text: substr, state: stringify(state)}; + } + // Give up when line is ridiculously long + if (stream.pos > 5000) { + st[pos++] = {style: null, text: this.text.slice(stream.pos)}; + break; + } + newLine = false; + } + } + + return st; + } + + function highlightOutputsDifferent(o1, o2) { + var minLen = Math.min(o1.length, o2.length); + for (var i = 0; i < minLen; ++i) + if (o1[i].style != o2[i].style || o1[i].text != o2[i].text) return i; + if (o1.length > minLen || o2.length > minLen) return minLen; + } + + function prettyPrintOutputTable(output, diffAt) { + var s = '<table class="mt-output">'; + s += '<tr>'; + for (var i = 0; i < output.length; ++i) { + var style = output[i].style, val = output[i].text; + s += + '<td class="mt-token"' + (i == diffAt ? " style='background: pink'" : "") + '>' + + '<span class="cm-' + esc(String(style)) + '">' + + esc(val.replace(/ /g,'\xb7')) + // · MIDDLE DOT + '</span>' + + '</td>'; + } + s += '</tr><tr>'; + for (var i = 0; i < output.length; ++i) { + s += '<td class="mt-style"><span>' + (output[i].style || null) + '</span></td>'; + } + if(output[0].state) { + s += '</tr><tr class="mt-state-row" title="State AFTER each token">'; + for (var i = 0; i < output.length; ++i) { + s += '<td class="mt-state"><pre>' + esc(output[i].state) + '</pre></td>'; + } + } + s += '</tr></table>'; + return s; + } +})(); diff --git a/modules/editor/codemirror/test/multi_test.min.js b/modules/editor/codemirror/test/multi_test.min.js @@ -0,0 +1,285 @@ +(function() { + namespace = "multi_"; + + function hasSelections(cm) { + var sels = cm.listSelections(); + var given = (arguments.length - 1) / 4; + if (sels.length != given) + throw new Failure("expected " + given + " selections, found " + sels.length); + for (var i = 0, p = 1; i < given; i++, p += 4) { + var anchor = Pos(arguments[p], arguments[p + 1]); + var head = Pos(arguments[p + 2], arguments[p + 3]); + eqCharPos(sels[i].anchor, anchor, "anchor of selection " + i); + eqCharPos(sels[i].head, head, "head of selection " + i); + } + } + function hasCursors(cm) { + var sels = cm.listSelections(); + var given = (arguments.length - 1) / 2; + if (sels.length != given) + throw new Failure("expected " + given + " selections, found " + sels.length); + for (var i = 0, p = 1; i < given; i++, p += 2) { + eqCursorPos(sels[i].anchor, sels[i].head, "something selected for " + i); + var head = Pos(arguments[p], arguments[p + 1]); + eqCharPos(sels[i].head, head, "selection " + i); + } + } + + testCM("getSelection", function(cm) { + select(cm, {anchor: Pos(0, 0), head: Pos(1, 2)}, {anchor: Pos(2, 2), head: Pos(2, 0)}); + eq(cm.getSelection(), "1234\n56\n90"); + eq(cm.getSelection(false).join("|"), "1234|56|90"); + eq(cm.getSelections().join("|"), "1234\n56|90"); + }, {value: "1234\n5678\n90"}); + + testCM("setSelection", function(cm) { + select(cm, Pos(3, 0), Pos(0, 0), {anchor: Pos(2, 5), head: Pos(1, 0)}); + hasSelections(cm, 0, 0, 0, 0, + 2, 5, 1, 0, + 3, 0, 3, 0); + cm.setSelection(Pos(1, 2), Pos(1, 1)); + hasSelections(cm, 1, 2, 1, 1); + select(cm, {anchor: Pos(1, 1), head: Pos(2, 4)}, + {anchor: Pos(0, 0), head: Pos(1, 3)}, + Pos(3, 0), Pos(2, 2)); + hasSelections(cm, 0, 0, 2, 4, + 3, 0, 3, 0); + cm.setSelections([{anchor: Pos(0, 1), head: Pos(0, 2)}, + {anchor: Pos(1, 1), head: Pos(1, 2)}, + {anchor: Pos(2, 1), head: Pos(2, 2)}], 1); + eqCharPos(cm.getCursor("head"), Pos(1, 2)); + eqCharPos(cm.getCursor("anchor"), Pos(1, 1)); + eqCharPos(cm.getCursor("from"), Pos(1, 1)); + eqCharPos(cm.getCursor("to"), Pos(1, 2)); + cm.setCursor(Pos(1, 1)); + hasCursors(cm, 1, 1); + }, {value: "abcde\nabcde\nabcde\n"}); + + testCM("somethingSelected", function(cm) { + select(cm, Pos(0, 1), {anchor: Pos(0, 3), head: Pos(0, 5)}); + eq(cm.somethingSelected(), true); + select(cm, Pos(0, 1), Pos(0, 3), Pos(0, 5)); + eq(cm.somethingSelected(), false); + }, {value: "123456789"}); + + testCM("extendSelection", function(cm) { + select(cm, Pos(0, 1), Pos(1, 1), Pos(2, 1)); + cm.setExtending(true); + cm.extendSelections([Pos(0, 2), Pos(1, 0), Pos(2, 3)]); + hasSelections(cm, 0, 1, 0, 2, + 1, 1, 1, 0, + 2, 1, 2, 3); + cm.extendSelection(Pos(2, 4), Pos(2, 0)); + hasSelections(cm, 2, 4, 2, 0); + }, {value: "1234\n1234\n1234"}); + + testCM("addSelection", function(cm) { + select(cm, Pos(0, 1), Pos(1, 1)); + cm.addSelection(Pos(0, 0), Pos(0, 4)); + hasSelections(cm, 0, 0, 0, 4, + 1, 1, 1, 1); + cm.addSelection(Pos(2, 2)); + hasSelections(cm, 0, 0, 0, 4, + 1, 1, 1, 1, + 2, 2, 2, 2); + }, {value: "1234\n1234\n1234"}); + + testCM("replaceSelection", function(cm) { + var selections = [{anchor: Pos(0, 0), head: Pos(0, 1)}, + {anchor: Pos(0, 2), head: Pos(0, 3)}, + {anchor: Pos(0, 4), head: Pos(0, 5)}, + {anchor: Pos(2, 1), head: Pos(2, 4)}, + {anchor: Pos(2, 5), head: Pos(2, 6)}]; + var val = "123456\n123456\n123456"; + cm.setValue(val); + cm.setSelections(selections); + cm.replaceSelection("ab", "around"); + eq(cm.getValue(), "ab2ab4ab6\n123456\n1ab5ab"); + hasSelections(cm, 0, 0, 0, 2, + 0, 3, 0, 5, + 0, 6, 0, 8, + 2, 1, 2, 3, + 2, 4, 2, 6); + cm.setValue(val); + cm.setSelections(selections); + cm.replaceSelection("", "around"); + eq(cm.getValue(), "246\n123456\n15"); + hasSelections(cm, 0, 0, 0, 0, + 0, 1, 0, 1, + 0, 2, 0, 2, + 2, 1, 2, 1, + 2, 2, 2, 2); + cm.setValue(val); + cm.setSelections(selections); + cm.replaceSelection("X\nY\nZ", "around"); + hasSelections(cm, 0, 0, 2, 1, + 2, 2, 4, 1, + 4, 2, 6, 1, + 8, 1, 10, 1, + 10, 2, 12, 1); + cm.replaceSelection("a", "around"); + hasSelections(cm, 0, 0, 0, 1, + 0, 2, 0, 3, + 0, 4, 0, 5, + 2, 1, 2, 2, + 2, 3, 2, 4); + cm.replaceSelection("xy", "start"); + hasSelections(cm, 0, 0, 0, 0, + 0, 3, 0, 3, + 0, 6, 0, 6, + 2, 1, 2, 1, + 2, 4, 2, 4); + cm.replaceSelection("z\nf"); + hasSelections(cm, 1, 1, 1, 1, + 2, 1, 2, 1, + 3, 1, 3, 1, + 6, 1, 6, 1, + 7, 1, 7, 1); + eq(cm.getValue(), "z\nfxy2z\nfxy4z\nfxy6\n123456\n1z\nfxy5z\nfxy"); + }); + + function select(cm) { + var sels = []; + for (var i = 1; i < arguments.length; i++) { + var arg = arguments[i]; + if (arg.head) sels.push(arg); + else sels.push({head: arg, anchor: arg}); + } + cm.setSelections(sels, sels.length - 1); + } + + testCM("indentSelection", function(cm) { + select(cm, Pos(0, 1), Pos(1, 1)); + cm.indentSelection(4); + eq(cm.getValue(), " foo\n bar\nbaz"); + + select(cm, Pos(0, 2), Pos(0, 3), Pos(0, 4)); + cm.indentSelection(-2); + eq(cm.getValue(), " foo\n bar\nbaz"); + + select(cm, {anchor: Pos(0, 0), head: Pos(1, 2)}, + {anchor: Pos(1, 3), head: Pos(2, 0)}); + cm.indentSelection(-2); + eq(cm.getValue(), "foo\n bar\nbaz"); + }, {value: "foo\nbar\nbaz"}); + + testCM("killLine", function(cm) { + select(cm, Pos(0, 1), Pos(0, 2), Pos(1, 1)); + cm.execCommand("killLine"); + eq(cm.getValue(), "f\nb\nbaz"); + cm.execCommand("killLine"); + eq(cm.getValue(), "fbbaz"); + cm.setValue("foo\nbar\nbaz"); + select(cm, Pos(0, 1), {anchor: Pos(0, 2), head: Pos(2, 1)}); + cm.execCommand("killLine"); + eq(cm.getValue(), "faz"); + }, {value: "foo\nbar\nbaz"}); + + testCM("deleteLine", function(cm) { + select(cm, Pos(0, 0), + {head: Pos(0, 1), anchor: Pos(2, 0)}, + Pos(4, 0)); + cm.execCommand("deleteLine"); + eq(cm.getValue(), "4\n6\n7"); + select(cm, Pos(2, 1)); + cm.execCommand("deleteLine"); + eq(cm.getValue(), "4\n6\n"); + }, {value: "1\n2\n3\n4\n5\n6\n7"}); + + testCM("deleteH", function(cm) { + select(cm, Pos(0, 4), {anchor: Pos(1, 4), head: Pos(1, 5)}); + cm.execCommand("delWordAfter"); + eq(cm.getValue(), "foo bar baz\nabc ef ghi\n"); + cm.execCommand("delWordAfter"); + eq(cm.getValue(), "foo baz\nabc ghi\n"); + cm.execCommand("delCharBefore"); + cm.execCommand("delCharBefore"); + eq(cm.getValue(), "fo baz\nab ghi\n"); + select(cm, Pos(0, 3), Pos(0, 4), Pos(0, 5)); + cm.execCommand("delWordAfter"); + eq(cm.getValue(), "fo \nab ghi\n"); + }, {value: "foo bar baz\nabc def ghi\n"}); + + testCM("goLineStart", function(cm) { + select(cm, Pos(0, 2), Pos(0, 3), Pos(1, 1)); + cm.execCommand("goLineStart"); + hasCursors(cm, 0, 0, 1, 0); + select(cm, Pos(1, 1), Pos(0, 1)); + cm.setExtending(true); + cm.execCommand("goLineStart"); + hasSelections(cm, 0, 1, 0, 0, + 1, 1, 1, 0); + }, {value: "foo\nbar\nbaz"}); + + testCM("moveV", function(cm) { + select(cm, Pos(0, 2), Pos(1, 2)); + cm.execCommand("goLineDown"); + hasCursors(cm, 1, 2, 2, 2); + cm.execCommand("goLineUp"); + hasCursors(cm, 0, 2, 1, 2); + cm.execCommand("goLineUp"); + hasCursors(cm, 0, 0, 0, 2); + cm.execCommand("goLineUp"); + hasCursors(cm, 0, 0); + select(cm, Pos(0, 2), Pos(1, 2)); + cm.setExtending(true); + cm.execCommand("goLineDown"); + hasSelections(cm, 0, 2, 2, 2); + }, {value: "12345\n12345\n12345"}); + + testCM("moveH", function(cm) { + select(cm, Pos(0, 1), Pos(0, 3), Pos(0, 5), Pos(2, 3)); + cm.execCommand("goCharRight"); + hasCursors(cm, 0, 2, 0, 4, 1, 0, 2, 4); + cm.execCommand("goCharLeft"); + hasCursors(cm, 0, 1, 0, 3, 0, 5, 2, 3); + for (var i = 0; i < 15; i++) + cm.execCommand("goCharRight"); + hasCursors(cm, 2, 4, 2, 5); + }, {value: "12345\n12345\n12345"}); + + testCM("newlineAndIndent", function(cm) { + select(cm, Pos(0, 5), Pos(1, 5)); + cm.execCommand("newlineAndIndent"); + hasCursors(cm, 1, 2, 3, 2); + eq(cm.getValue(), "x = [\n 1];\ny = [\n 2];"); + cm.undo(); + eq(cm.getValue(), "x = [1];\ny = [2];"); + hasCursors(cm, 0, 5, 1, 5); + select(cm, Pos(0, 5), Pos(0, 6)); + cm.execCommand("newlineAndIndent"); + hasCursors(cm, 1, 2, 2, 0); + eq(cm.getValue(), "x = [\n 1\n];\ny = [2];"); + }, {value: "x = [1];\ny = [2];", mode: "javascript"}); + + testCM("goDocStartEnd", function(cm) { + select(cm, Pos(0, 1), Pos(1, 1)); + cm.execCommand("goDocStart"); + hasCursors(cm, 0, 0); + select(cm, Pos(0, 1), Pos(1, 1)); + cm.execCommand("goDocEnd"); + hasCursors(cm, 1, 3); + select(cm, Pos(0, 1), Pos(1, 1)); + cm.setExtending(true); + cm.execCommand("goDocEnd"); + hasSelections(cm, 1, 1, 1, 3); + }, {value: "abc\ndef"}); + + testCM("selectionHistory", function(cm) { + for (var i = 0; i < 3; ++i) + cm.addSelection(Pos(0, i * 2), Pos(0, i * 2 + 1)); + cm.execCommand("undoSelection"); + eq(cm.getSelection(), "1\n2"); + cm.execCommand("undoSelection"); + eq(cm.getSelection(), "1"); + cm.execCommand("undoSelection"); + eq(cm.getSelection(), ""); + eqCharPos(cm.getCursor(), Pos(0, 0)); + cm.execCommand("redoSelection"); + eq(cm.getSelection(), "1"); + cm.execCommand("redoSelection"); + eq(cm.getSelection(), "1\n2"); + cm.execCommand("redoSelection"); + eq(cm.getSelection(), "1\n2\n3"); + }, {value: "1 2 3"}); +})(); diff --git a/modules/editor/codemirror/test/phantom_driver.min.js b/modules/editor/codemirror/test/phantom_driver.min.js @@ -0,0 +1,31 @@ +var page = require('webpage').create(); + +page.open("http://localhost:3000/test/index.html", function (status) { + if (status != "success") { + console.log("page couldn't be loaded successfully"); + phantom.exit(1); + } + waitFor(function () { + return page.evaluate(function () { + var output = document.getElementById('status'); + if (!output) { return false; } + return (/^(\d+ failures?|all passed)/i).test(output.innerText); + }); + }, function () { + var failed = page.evaluate(function () { return window.failed; }); + var output = page.evaluate(function () { + return document.getElementById('output').innerText + "\n" + + document.getElementById('status').innerText; + }); + console.log(output); + phantom.exit(failed > 0 ? 1 : 0); + }); +}); + +function waitFor (test, cb) { + if (test()) { + cb(); + } else { + setTimeout(function () { waitFor(test, cb); }, 250); + } +} diff --git a/modules/editor/codemirror/test/scroll_test.js b/modules/editor/codemirror/test/scroll_test.js @@ -0,0 +1,126 @@ +(function() { + "use strict"; + + namespace = "scroll_"; + + testCM("bars_hidden", function(cm) { + for (var i = 0;; i++) { + var wrapBox = cm.getWrapperElement().getBoundingClientRect(); + var scrollBox = cm.getScrollerElement().getBoundingClientRect(); + is(wrapBox.bottom < scrollBox.bottom - 10); + is(wrapBox.right < scrollBox.right - 10); + if (i == 1) break; + cm.getWrapperElement().style.height = "auto"; + cm.refresh(); + } + }); + + function barH(cm) { return byClassName(cm.getWrapperElement(), "CodeMirror-hscrollbar")[0]; } + function barV(cm) { return byClassName(cm.getWrapperElement(), "CodeMirror-vscrollbar")[0]; } + + function displayBottom(cm, scrollbar) { + if (scrollbar && cm.display.scroller.offsetHeight > cm.display.scroller.clientHeight) + return barH(cm).getBoundingClientRect().top; + else + return cm.getWrapperElement().getBoundingClientRect().bottom - 1; + } + + function displayRight(cm, scrollbar) { + if (scrollbar && cm.display.scroller.offsetWidth > cm.display.scroller.clientWidth) + return barV(cm).getBoundingClientRect().left; + else + return cm.getWrapperElement().getBoundingClientRect().right - 1; + } + + function testMovedownFixed(cm, hScroll) { + cm.setSize("100px", "100px"); + if (hScroll) cm.setValue(new Array(100).join("x")); + var bottom = displayBottom(cm, hScroll); + for (var i = 0; i < 30; i++) { + cm.replaceSelection("x\n"); + var cursorBottom = cm.cursorCoords(null, "window").bottom; + is(cursorBottom <= bottom); + } + is(cursorBottom >= bottom - 5); + } + + testCM("movedown_fixed", function(cm) {testMovedownFixed(cm, false);}); + testCM("movedown_hscroll_fixed", function(cm) {testMovedownFixed(cm, true);}); + + function testMovedownResize(cm, hScroll) { + cm.getWrapperElement().style.height = "auto"; + if (hScroll) cm.setValue(new Array(100).join("x")); + cm.refresh(); + for (var i = 0; i < 30; i++) { + cm.replaceSelection("x\n"); + var bottom = displayBottom(cm, hScroll); + var cursorBottom = cm.cursorCoords(null, "window").bottom; + is(cursorBottom <= bottom); + is(cursorBottom >= bottom - 5); + } + } + + testCM("movedown_resize", function(cm) {testMovedownResize(cm, false);}); + testCM("movedown_hscroll_resize", function(cm) {testMovedownResize(cm, true);}); + + function testMoveright(cm, wrap, scroll) { + cm.setSize("100px", "100px"); + if (wrap) cm.setOption("lineWrapping", true); + if (scroll) { + cm.setValue("\n" + new Array(100).join("x\n")); + cm.setCursor(Pos(0, 0)); + } + var right = displayRight(cm, scroll); + for (var i = 0; i < 10; i++) { + cm.replaceSelection("xxxxxxxxxx"); + var cursorRight = cm.cursorCoords(null, "window").right; + is(cursorRight < right); + } + if (!wrap) is(cursorRight > right - 20); + } + + testCM("moveright", function(cm) {testMoveright(cm, false, false);}); + testCM("moveright_wrap", function(cm) {testMoveright(cm, true, false);}); + testCM("moveright_scroll", function(cm) {testMoveright(cm, false, true);}); + testCM("moveright_scroll_wrap", function(cm) {testMoveright(cm, true, true);}); + + testCM("suddenly_wide", function(cm) { + addDoc(cm, 100, 100); + cm.replaceSelection(new Array(600).join("l ") + "\n"); + cm.execCommand("goLineUp"); + cm.execCommand("goLineEnd"); + is(barH(cm).scrollLeft > cm.getScrollerElement().scrollLeft - 1); + }); + + testCM("wrap_changes_height", function(cm) { + var line = new Array(20).join("a ") + "\n"; + cm.setValue(new Array(20).join(line)); + var box = cm.getWrapperElement().getBoundingClientRect(); + cm.setSize(cm.cursorCoords(Pos(0), "window").right - box.left + 2, + cm.cursorCoords(Pos(19, 0), "window").bottom - box.top + 2); + cm.setCursor(Pos(19, 0)); + cm.replaceSelection("\n"); + is(cm.cursorCoords(null, "window").bottom < displayBottom(cm, false)); + }, {lineWrapping: true}); + + testCM("height_auto_with_gutter_expect_no_scroll_after_line_delete", function(cm) { + cm.setSize(null, "auto"); + cm.setValue("x\n"); + cm.execCommand("goDocEnd"); + cm.execCommand("delCharBefore"); + eq(cm.getScrollInfo().top, 0); + cm.scrollTo(null, 10); + is(cm.getScrollInfo().top < 5); + }, {lineNumbers: true}); + + testCM("bidi_ensureCursorVisible", function(cm) { + cm.setValue("<dd>وضع الاستخدام. عندما لا تعطى، وهذا الافتراضي إلى الطريقة الاولى\n"); + cm.execCommand("goLineStart"); + eq(cm.getScrollInfo().left, 0); + cm.execCommand("goCharRight"); + cm.execCommand("goCharRight"); + cm.execCommand("goCharRight"); + eqCursorPos(cm.getCursor(), Pos(0, 3, "before")); + eq(cm.getScrollInfo().left, 0); + }, {lineWrapping: false}); +})(); diff --git a/modules/editor/codemirror/test/search_test.min.js b/modules/editor/codemirror/test/search_test.min.js @@ -0,0 +1,85 @@ +(function() { + "use strict"; + + function run(doc, query, options) { + var cursor = doc.getSearchCursor(query, null, options); + for (var i = 3; i < arguments.length; i += 4) { + var found = cursor.findNext(); + is(found, "not enough results (forward)"); + eqCharPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, forward, " + (i - 3) / 4); + eqCharPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, forward, " + (i - 3) / 4); + } + is(!cursor.findNext(), "too many matches (forward)"); + for (var i = arguments.length - 4; i >= 3; i -= 4) { + var found = cursor.findPrevious(); + is(found, "not enough results (backwards)"); + eqCharPos(Pos(arguments[i], arguments[i + 1]), cursor.from(), "from, backwards, " + (i - 3) / 4); + eqCharPos(Pos(arguments[i + 2], arguments[i + 3]), cursor.to(), "to, backwards, " + (i - 3) / 4); + } + is(!cursor.findPrevious(), "too many matches (backwards)"); + } + + function test(name, f) { window.test("search_" + name, f) } + + test("simple", function() { + var doc = new CodeMirror.Doc("abcdefg\nabcdefg") + run(doc, "cde", false, 0, 2, 0, 5, 1, 2, 1, 5); + }); + + test("multiline", function() { + var doc = new CodeMirror.Doc("hallo\na\nb\ngoodbye") + run(doc, "llo\na\nb\ngoo", false, 0, 2, 3, 3); + run(doc, "blah\na\nb\nhall", false); + run(doc, "bye\nx\neye", false); + }); + + test("regexp", function() { + var doc = new CodeMirror.Doc("abcde\nabcde") + run(doc, /bcd/, false, 0, 1, 0, 4, 1, 1, 1, 4); + run(doc, /BCD/, false); + run(doc, /BCD/i, false, 0, 1, 0, 4, 1, 1, 1, 4); + }); + + test("regexpMultiline", function() { + var doc = new CodeMirror.Doc("foo foo\nbar\nbaz") + run(doc, /fo[^]*az/, {multiline: true}, 0, 0, 2, 3) + run(doc, /[oa][^u]/, {multiline: true}, 0, 1, 0, 3, 0, 5, 0, 7, 1, 1, 1, 3, 2, 1, 2, 3) + run(doc, /[a][^u]{2}/, {multiline: true}, 1, 1, 2, 0) + }) + + test("insensitive", function() { + var doc = new CodeMirror.Doc("hallo\nHALLO\noink\nhAllO") + run(doc, "All", false, 3, 1, 3, 4); + run(doc, "All", true, 0, 1, 0, 4, 1, 1, 1, 4, 3, 1, 3, 4); + }); + + test("multilineInsensitive", function() { + var doc = new CodeMirror.Doc("zie ginds komT\nDe Stoomboot\nuit Spanje weer aan") + run(doc, "komt\nde stoomboot\nuit", false); + run(doc, "komt\nde stoomboot\nuit", {caseFold: true}, 0, 10, 2, 3); + run(doc, "kOMt\ndE stOOmboot\nuiT", {caseFold: true}, 0, 10, 2, 3); + }); + + test("multilineInsensitiveSlow", function() { + var text = "" + for (var i = 0; i < 1000; i++) text += "foo\nbar\n" + var doc = new CodeMirror.Doc("find\nme\n" + text + "find\nme\n") + var t0 = +new Date + run(doc, /find\nme/, {multiline: true}, 0, 0, 1, 2, 2002, 0, 2003, 2) + is(+new Date - t0 < 100) + }) + + test("expandingCaseFold", function() { + var doc = new CodeMirror.Doc("<b>İİ İİ</b>\n<b>uu uu</b>") + run(doc, "</b>", true, 0, 8, 0, 12, 1, 8, 1, 12); + run(doc, "İİ", true, 0, 3, 0, 5, 0, 6, 0, 8); + }); + + test("normalize", function() { + if (!String.prototype.normalize) return + var doc = new CodeMirror.Doc("yılbaşı\n수 있을까\nLe taux d'humidité à London") + run(doc, "s", false, 0, 5, 0, 6) + run(doc, "이", false, 1, 2, 1, 3) + run(doc, "a", false, 0, 4, 0, 5, 2, 4, 2, 5, 2, 19, 2, 20) + }) +})(); diff --git a/modules/editor/codemirror/test/sublime_test.min.js b/modules/editor/codemirror/test/sublime_test.min.js @@ -0,0 +1,307 @@ +(function() { + "use strict"; + + var Pos = CodeMirror.Pos; + namespace = "sublime_"; + + function stTest(name) { + var actions = Array.prototype.slice.call(arguments, 1); + testCM(name, function(cm) { + for (var i = 0; i < actions.length; i++) { + var action = actions[i]; + if (typeof action == "string" && i == 0) + cm.setValue(action); + else if (typeof action == "string") + cm.execCommand(action); + else if (action instanceof Pos) + cm.setCursor(action); + else + action(cm); + } + }); + } + + function at(line, ch, msg) { + return function(cm) { + eq(cm.listSelections().length, 1); + eqCursorPos(cm.getCursor("head"), Pos(line, ch), msg); + eqCursorPos(cm.getCursor("anchor"), Pos(line, ch), msg); + }; + } + + function val(content, msg) { + return function(cm) { eq(cm.getValue(), content, msg); }; + } + + function argsToRanges(args) { + if (args.length % 4) throw new Error("Wrong number of arguments for ranges."); + var ranges = []; + for (var i = 0; i < args.length; i += 4) + ranges.push({anchor: Pos(args[i], args[i + 1]), + head: Pos(args[i + 2], args[i + 3])}); + return ranges; + } + + function setSel() { + var ranges = argsToRanges(arguments); + return function(cm) { cm.setSelections(ranges, 0); }; + } + + function hasSel() { + var ranges = argsToRanges(arguments); + return function(cm) { + var sels = cm.listSelections(); + if (sels.length != ranges.length) + throw new Failure("Expected " + ranges.length + " selections, but found " + sels.length); + for (var i = 0; i < sels.length; i++) { + eqCharPos(sels[i].anchor, ranges[i].anchor, "anchor " + i); + eqCharPos(sels[i].head, ranges[i].head, "head " + i); + } + }; + } + + stTest("bySubword", "the foo_bar DooDahBah \n a", + "goSubwordLeft", at(0, 0), + "goSubwordRight", at(0, 3), + "goSubwordRight", at(0, 7), + "goSubwordRight", at(0, 11), + "goSubwordRight", at(0, 15), + "goSubwordRight", at(0, 18), + "goSubwordRight", at(0, 21), + "goSubwordRight", at(0, 22), + "goSubwordRight", at(1, 0), + "goSubwordRight", at(1, 2), + "goSubwordRight", at(1, 2), + "goSubwordLeft", at(1, 1), + "goSubwordLeft", at(1, 0), + "goSubwordLeft", at(0, 22), + "goSubwordLeft", at(0, 18), + "goSubwordLeft", at(0, 15), + "goSubwordLeft", at(0, 12), + "goSubwordLeft", at(0, 8), + "goSubwordLeft", at(0, 4), + "goSubwordLeft", at(0, 0)); + + stTest("splitSelectionByLine", "abc\ndef\nghi", + setSel(0, 1, 2, 2), + "splitSelectionByLine", + hasSel(0, 1, 0, 3, + 1, 0, 1, 3, + 2, 0, 2, 2)); + + stTest("splitSelectionByLineMulti", "abc\ndef\nghi\njkl", + setSel(0, 1, 1, 1, + 1, 2, 3, 2, + 3, 3, 3, 3), + "splitSelectionByLine", + hasSel(0, 1, 0, 3, + 1, 0, 1, 1, + 1, 2, 1, 3, + 2, 0, 2, 3, + 3, 0, 3, 2, + 3, 3, 3, 3)); + + stTest("selectLine", "abc\ndef\nghi", + setSel(0, 1, 0, 1, + 2, 0, 2, 1), + "selectLine", + hasSel(0, 0, 1, 0, + 2, 0, 2, 3), + setSel(0, 1, 1, 0), + "selectLine", + hasSel(0, 0, 2, 0)); + + stTest("insertLineAfter", "abcde\nfghijkl\nmn", + setSel(0, 1, 0, 1, + 0, 3, 0, 3, + 1, 2, 1, 2, + 1, 3, 1, 5), "insertLineAfter", + hasSel(1, 0, 1, 0, + 3, 0, 3, 0), val("abcde\n\nfghijkl\n\nmn")); + + stTest("insertLineBefore", "abcde\nfghijkl\nmn", + setSel(0, 1, 0, 1, + 0, 3, 0, 3, + 1, 2, 1, 2, + 1, 3, 1, 5), "insertLineBefore", + hasSel(0, 0, 0, 0, + 2, 0, 2, 0), val("\nabcde\n\nfghijkl\nmn")); + + stTest("selectNextOccurrence", "a foo bar\nfoobar foo", + setSel(0, 2, 0, 5), + "selectNextOccurrence", hasSel(0, 2, 0, 5, + 1, 0, 1, 3), + "selectNextOccurrence", hasSel(0, 2, 0, 5, + 1, 0, 1, 3, + 1, 7, 1, 10), + "selectNextOccurrence", hasSel(0, 2, 0, 5, + 1, 0, 1, 3, + 1, 7, 1, 10), + Pos(0, 3), "selectNextOccurrence", hasSel(0, 2, 0, 5), + "selectNextOccurrence", hasSel(0, 2, 0, 5, + 1, 7, 1, 10), + setSel(0, 6, 0, 9), + "selectNextOccurrence", hasSel(0, 6, 0, 9, + 1, 3, 1, 6)); + + stTest("selectScope", "foo(a) {\n bar[1, 2];\n}", + "selectScope", hasSel(0, 0, 2, 1), + Pos(0, 4), "selectScope", hasSel(0, 4, 0, 5), + Pos(0, 5), "selectScope", hasSel(0, 4, 0, 5), + Pos(0, 6), "selectScope", hasSel(0, 0, 2, 1), + Pos(0, 8), "selectScope", hasSel(0, 8, 2, 0), + Pos(1, 2), "selectScope", hasSel(0, 8, 2, 0), + Pos(1, 6), "selectScope", hasSel(1, 6, 1, 10), + Pos(1, 9), "selectScope", hasSel(1, 6, 1, 10)); + + stTest("goToBracket", "foo(a) {\n bar[1, 2];\n}", + Pos(0, 0), "goToBracket", at(0, 0), + Pos(0, 4), "goToBracket", at(0, 5), "goToBracket", at(0, 4), + Pos(0, 8), "goToBracket", at(2, 0), "goToBracket", at(0, 8), + Pos(1, 2), "goToBracket", at(2, 0), + Pos(1, 7), "goToBracket", at(1, 10), "goToBracket", at(1, 6)); + + stTest("swapLine", "1\n2\n3---\n4\n5", + "swapLineDown", val("2\n1\n3---\n4\n5"), + "swapLineUp", val("1\n2\n3---\n4\n5"), + "swapLineUp", val("1\n2\n3---\n4\n5"), + Pos(4, 1), "swapLineDown", val("1\n2\n3---\n4\n5"), + setSel(0, 1, 0, 1, + 1, 0, 2, 0, + 2, 2, 2, 2), + "swapLineDown", val("4\n1\n2\n3---\n5"), + hasSel(1, 1, 1, 1, + 2, 0, 3, 0, + 3, 2, 3, 2), + "swapLineUp", val("1\n2\n3---\n4\n5"), + hasSel(0, 1, 0, 1, + 1, 0, 2, 0, + 2, 2, 2, 2)); + + stTest("swapLineEmptyBottomSel", "1\n2\n3", + setSel(0, 1, 1, 0), + "swapLineDown", val("2\n1\n3"), hasSel(1, 1, 2, 0), + "swapLineUp", val("1\n2\n3"), hasSel(0, 1, 1, 0), + "swapLineUp", val("1\n2\n3"), hasSel(0, 0, 0, 0)); + + stTest("swapLineUpFromEnd", "a\nb\nc", + Pos(2, 1), "swapLineUp", + hasSel(1, 1, 1, 1), val("a\nc\nb")); + + stTest("joinLines", "abc\ndef\nghi\njkl", + "joinLines", val("abc def\nghi\njkl"), at(0, 4), + "undo", + setSel(0, 2, 1, 1), "joinLines", + val("abc def ghi\njkl"), hasSel(0, 2, 0, 8), + "undo", + setSel(0, 1, 0, 1, + 1, 1, 1, 1, + 3, 1, 3, 1), "joinLines", + val("abc def ghi\njkl"), hasSel(0, 4, 0, 4, + 0, 8, 0, 8, + 1, 3, 1, 3)); + + stTest("duplicateLine", "abc\ndef\nghi", + Pos(1, 0), "duplicateLine", val("abc\ndef\ndef\nghi"), at(2, 0), + "undo", + setSel(0, 1, 0, 1, + 1, 1, 1, 1, + 2, 1, 2, 1), "duplicateLine", + val("abc\nabc\ndef\ndef\nghi\nghi"), hasSel(1, 1, 1, 1, + 3, 1, 3, 1, + 5, 1, 5, 1)); + stTest("duplicateLineSelection", "abcdef", + setSel(0, 1, 0, 1, + 0, 2, 0, 4, + 0, 5, 0, 5), + "duplicateLine", + val("abcdef\nabcdcdef\nabcdcdef"), hasSel(2, 1, 2, 1, + 2, 4, 2, 6, + 2, 7, 2, 7)); + + stTest("selectLinesUpward", "123\n345\n789\n012", + setSel(0, 1, 0, 1, + 1, 1, 1, 3, + 2, 0, 2, 0, + 3, 0, 3, 0), + "selectLinesUpward", + hasSel(0, 1, 0, 1, + 0, 3, 0, 3, + 1, 0, 1, 0, + 1, 1, 1, 3, + 2, 0, 2, 0, + 3, 0, 3, 0)); + + stTest("selectLinesDownward", "123\n345\n789\n012", + setSel(0, 1, 0, 1, + 1, 1, 1, 3, + 2, 0, 2, 0, + 3, 0, 3, 0), + "selectLinesDownward", + hasSel(0, 1, 0, 1, + 1, 1, 1, 3, + 2, 0, 2, 0, + 2, 3, 2, 3, + 3, 0, 3, 0)); + + stTest("sortLines", "c\nb\na\nC\nB\nA", + "sortLines", val("A\nB\nC\na\nb\nc"), + "undo", + setSel(0, 0, 2, 0, + 3, 0, 5, 0), + "sortLines", val("b\nc\na\nB\nC\nA"), + hasSel(0, 0, 2, 0, + 3, 0, 5, 0), + "undo", + setSel(1, 0, 5, 0), "sortLinesInsensitive", val("c\na\nB\nb\nC\nA")); + + stTest("bookmarks", "abc\ndef\nghi\njkl", + Pos(0, 1), "toggleBookmark", + setSel(1, 1, 1, 2), "toggleBookmark", + setSel(2, 1, 2, 2), "toggleBookmark", + "nextBookmark", hasSel(0, 1, 0, 1), + "nextBookmark", hasSel(1, 1, 1, 2), + "nextBookmark", hasSel(2, 1, 2, 2), + "prevBookmark", hasSel(1, 1, 1, 2), + "prevBookmark", hasSel(0, 1, 0, 1), + "prevBookmark", hasSel(2, 1, 2, 2), + "prevBookmark", hasSel(1, 1, 1, 2), + "toggleBookmark", + "prevBookmark", hasSel(2, 1, 2, 2), + "prevBookmark", hasSel(0, 1, 0, 1), + "selectBookmarks", hasSel(0, 1, 0, 1, + 2, 1, 2, 2), + "clearBookmarks", + Pos(0, 0), "selectBookmarks", at(0, 0)); + + stTest("smartBackspace", " foo\n bar", + setSel(0, 2, 0, 2, 1, 4, 1, 4, 1, 6, 1, 6), "smartBackspace", + val("foo\n br")) + + stTest("upAndDowncaseAtCursor", "abc\ndef x\nghI", + setSel(0, 1, 0, 3, + 1, 1, 1, 1, + 1, 4, 1, 4), "upcaseAtCursor", + val("aBC\nDEF x\nghI"), hasSel(0, 1, 0, 3, + 1, 3, 1, 3, + 1, 4, 1, 4), + "downcaseAtCursor", + val("abc\ndef x\nghI"), hasSel(0, 1, 0, 3, + 1, 3, 1, 3, + 1, 4, 1, 4)); + + stTest("mark", "abc\ndef\nghi", + Pos(1, 1), "setSublimeMark", + Pos(2, 1), "selectToSublimeMark", hasSel(2, 1, 1, 1), + Pos(0, 1), "swapWithSublimeMark", at(1, 1), "swapWithSublimeMark", at(0, 1), + "deleteToSublimeMark", val("aef\nghi"), + "sublimeYank", val("abc\ndef\nghi"), at(1, 1)); + + stTest("findUnder", "foo foobar a", + "findUnder", hasSel(0, 4, 0, 7), + "findUnder", hasSel(0, 0, 0, 3), + "findUnderPrevious", hasSel(0, 4, 0, 7), + "findUnderPrevious", hasSel(0, 0, 0, 3), + Pos(0, 4), "findUnder", hasSel(0, 4, 0, 10), + Pos(0, 11), "findUnder", hasSel(0, 11, 0, 11)); +})(); diff --git a/modules/editor/codemirror/test/vim_test.min.js b/modules/editor/codemirror/test/vim_test.min.js @@ -0,0 +1,4238 @@ +var Pos = CodeMirror.Pos; +CodeMirror.Vim.suppressErrorLogging = true; + +var code = '' + +' wOrd1 (#%\n' + +' word3] \n' + +'aopop pop 0 1 2 3 4\n' + +' (a) [b] {c} \n' + +'int getchar(void) {\n' + +' static char buf[BUFSIZ];\n' + +' static char *bufp = buf;\n' + +' if (n == 0) { /* buffer is empty */\n' + +' n = read(0, buf, sizeof buf);\n' + +' bufp = buf;\n' + +' }\n' + +'\n' + +' return (--n >= 0) ? (unsigned char) *bufp++ : EOF;\n' + +' \n' + +'}\n'; + +var lines = (function() { + lineText = code.split('\n'); + var ret = []; + for (var i = 0; i < lineText.length; i++) { + ret[i] = { + line: i, + length: lineText[i].length, + lineText: lineText[i], + textStart: /^\s*/.exec(lineText[i])[0].length + }; + } + return ret; +})(); +var endOfDocument = makeCursor(lines.length - 1, + lines[lines.length - 1].length); +var wordLine = lines[0]; +var bigWordLine = lines[1]; +var charLine = lines[2]; +var bracesLine = lines[3]; +var seekBraceLine = lines[4]; + +var word1 = { + start: new Pos(wordLine.line, 1), + end: new Pos(wordLine.line, 5) +}; +var word2 = { + start: new Pos(wordLine.line, word1.end.ch + 2), + end: new Pos(wordLine.line, word1.end.ch + 4) +}; +var word3 = { + start: new Pos(bigWordLine.line, 1), + end: new Pos(bigWordLine.line, 5) +}; +var bigWord1 = word1; +var bigWord2 = word2; +var bigWord3 = { + start: new Pos(bigWordLine.line, 1), + end: new Pos(bigWordLine.line, 7) +}; +var bigWord4 = { + start: new Pos(bigWordLine.line, bigWord1.end.ch + 3), + end: new Pos(bigWordLine.line, bigWord1.end.ch + 7) +}; + +var oChars = [ new Pos(charLine.line, 1), + new Pos(charLine.line, 3), + new Pos(charLine.line, 7) ]; +var pChars = [ new Pos(charLine.line, 2), + new Pos(charLine.line, 4), + new Pos(charLine.line, 6), + new Pos(charLine.line, 8) ]; +var numChars = [ new Pos(charLine.line, 10), + new Pos(charLine.line, 12), + new Pos(charLine.line, 14), + new Pos(charLine.line, 16), + new Pos(charLine.line, 18)]; +var parens1 = { + start: new Pos(bracesLine.line, 1), + end: new Pos(bracesLine.line, 3) +}; +var squares1 = { + start: new Pos(bracesLine.line, 5), + end: new Pos(bracesLine.line, 7) +}; +var curlys1 = { + start: new Pos(bracesLine.line, 9), + end: new Pos(bracesLine.line, 11) +}; +var seekOutside = { + start: new Pos(seekBraceLine.line, 1), + end: new Pos(seekBraceLine.line, 16) +}; +var seekInside = { + start: new Pos(seekBraceLine.line, 14), + end: new Pos(seekBraceLine.line, 11) +}; + +function copyCursor(cur) { + return new Pos(cur.line, cur.ch); +} + +function forEach(arr, func) { + for (var i = 0; i < arr.length; i++) { + func(arr[i], i, arr); + } +} + +function testVim(name, run, opts, expectedFail) { + var vimOpts = { + lineNumbers: true, + vimMode: true, + showCursorWhenSelecting: true, + value: code + }; + for (var prop in opts) { + if (opts.hasOwnProperty(prop)) { + vimOpts[prop] = opts[prop]; + } + } + return test('vim_' + name, function() { + var place = document.getElementById("testground"); + var cm = CodeMirror(place, vimOpts); + var vim = CodeMirror.Vim.maybeInitVimState_(cm); + + function doKeysFn(cm) { + return function(args) { + if (args instanceof Array) { + arguments = args; + } + for (var i = 0; i < arguments.length; i++) { + var result = CodeMirror.Vim.handleKey(cm, arguments[i]); + if (!result && cm.state.vim.insertMode) { + cm.replaceSelections(fillArray(arguments[i], cm.listSelections().length)); + } + } + } + } + function doInsertModeKeysFn(cm) { + return function(args) { + if (args instanceof Array) { arguments = args; } + function executeHandler(handler) { + if (typeof handler == 'string') { + CodeMirror.commands[handler](cm); + } else { + handler(cm); + } + return true; + } + for (var i = 0; i < arguments.length; i++) { + var key = arguments[i]; + // Find key in keymap and handle. + var handled = CodeMirror.lookupKey(key, cm.getOption('keyMap'), executeHandler, cm); + // Record for insert mode. + if (handled == "handled" && cm.state.vim.insertMode && arguments[i] != 'Esc') { + var lastChange = CodeMirror.Vim.getVimGlobalState_().macroModeState.lastInsertModeChanges; + if (lastChange && (key.indexOf('Delete') != -1 || key.indexOf('Backspace') != -1)) { + lastChange.changes.push(new CodeMirror.Vim.InsertModeKey(key)); + } + } + } + } + } + function doExFn(cm) { + return function(command) { + cm.openDialog = helpers.fakeOpenDialog(command); + helpers.doKeys(':'); + } + } + function assertCursorAtFn(cm) { + return function(line, ch) { + var pos; + if (ch == null && typeof line.line == 'number') { + pos = line; + } else { + pos = makeCursor(line, ch); + } + eqCursorPos(cm.getCursor(), pos); + } + } + function fakeOpenDialog(result) { + return function(text, callback) { + return callback(result); + } + } + function fakeOpenNotification(matcher) { + return function(text) { + matcher(text); + } + } + var helpers = { + doKeys: doKeysFn(cm), + // Warning: Only emulates keymap events, not character insertions. Use + // replaceRange to simulate character insertions. + // Keys are in CodeMirror format, NOT vim format. + doInsertModeKeys: doInsertModeKeysFn(cm), + doEx: doExFn(cm), + assertCursorAt: assertCursorAtFn(cm), + fakeOpenDialog: fakeOpenDialog, + fakeOpenNotification: fakeOpenNotification, + getRegisterController: function() { + return CodeMirror.Vim.getRegisterController(); + } + } + CodeMirror.Vim.resetVimGlobalState_(); + var successful = false; + var savedOpenNotification = cm.openNotification; + var savedOpenDialog = cm.openDialog; + try { + run(cm, vim, helpers); + successful = true; + } finally { + cm.openNotification = savedOpenNotification; + cm.openDialog = savedOpenDialog; + if (!successful || verbose) { + place.style.visibility = "visible"; + } else { + place.removeChild(cm.getWrapperElement()); + } + } + }, expectedFail); +}; +testVim('qq@q', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'q', 'l', 'l', 'q'); + helpers.assertCursorAt(0,2); + helpers.doKeys('@', 'q'); + helpers.assertCursorAt(0,4); +}, { value: ' '}); +testVim('@@', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'q', 'l', 'l', 'q'); + helpers.assertCursorAt(0,2); + helpers.doKeys('@', 'q'); + helpers.assertCursorAt(0,4); + helpers.doKeys('@', '@'); + helpers.assertCursorAt(0,6); +}, { value: ' '}); +var jumplistScene = ''+ + 'word\n'+ + '(word)\n'+ + '{word\n'+ + 'word.\n'+ + '\n'+ + 'word search\n'+ + '}word\n'+ + 'word\n'+ + 'word\n'; +function testJumplist(name, keys, endPos, startPos, dialog) { + endPos = makeCursor(endPos[0], endPos[1]); + startPos = makeCursor(startPos[0], startPos[1]); + testVim(name, function(cm, vim, helpers) { + CodeMirror.Vim.resetVimGlobalState_(); + if(dialog)cm.openDialog = helpers.fakeOpenDialog('word'); + cm.setCursor(startPos); + helpers.doKeys.apply(null, keys); + helpers.assertCursorAt(endPos); + }, {value: jumplistScene}); +}; +testJumplist('jumplist_H', ['H', '<C-o>'], [5,2], [5,2]); +testJumplist('jumplist_M', ['M', '<C-o>'], [2,2], [2,2]); +testJumplist('jumplist_L', ['L', '<C-o>'], [2,2], [2,2]); +testJumplist('jumplist_[[', ['[', '[', '<C-o>'], [5,2], [5,2]); +testJumplist('jumplist_]]', [']', ']', '<C-o>'], [2,2], [2,2]); +testJumplist('jumplist_G', ['G', '<C-o>'], [5,2], [5,2]); +testJumplist('jumplist_gg', ['g', 'g', '<C-o>'], [5,2], [5,2]); +testJumplist('jumplist_%', ['%', '<C-o>'], [1,5], [1,5]); +testJumplist('jumplist_{', ['{', '<C-o>'], [1,5], [1,5]); +testJumplist('jumplist_}', ['}', '<C-o>'], [1,5], [1,5]); +testJumplist('jumplist_\'', ['m', 'a', 'h', '\'', 'a', 'h', '<C-i>'], [1,0], [1,5]); +testJumplist('jumplist_`', ['m', 'a', 'h', '`', 'a', 'h', '<C-i>'], [1,5], [1,5]); +testJumplist('jumplist_*_cachedCursor', ['*', '<C-o>'], [1,3], [1,3]); +testJumplist('jumplist_#_cachedCursor', ['#', '<C-o>'], [1,3], [1,3]); +testJumplist('jumplist_n', ['#', 'n', '<C-o>'], [1,1], [2,3]); +testJumplist('jumplist_N', ['#', 'N', '<C-o>'], [1,1], [2,3]); +testJumplist('jumplist_repeat_<c-o>', ['*', '*', '*', '3', '<C-o>'], [2,3], [2,3]); +testJumplist('jumplist_repeat_<c-i>', ['*', '*', '*', '3', '<C-o>', '2', '<C-i>'], [5,0], [2,3]); +testJumplist('jumplist_repeated_motion', ['3', '*', '<C-o>'], [2,3], [2,3]); +testJumplist('jumplist_/', ['/', '<C-o>'], [2,3], [2,3], 'dialog'); +testJumplist('jumplist_?', ['?', '<C-o>'], [2,3], [2,3], 'dialog'); +testJumplist('jumplist_skip_deleted_mark<c-o>', + ['*', 'n', 'n', 'k', 'd', 'k', '<C-o>', '<C-o>', '<C-o>'], + [0,2], [0,2]); +testJumplist('jumplist_skip_deleted_mark<c-i>', + ['*', 'n', 'n', 'k', 'd', 'k', '<C-o>', '<C-i>', '<C-i>'], + [1,0], [0,2]); + +/** + * @param name Name of the test + * @param keys An array of keys or a string with a single key to simulate. + * @param endPos The expected end position of the cursor. + * @param startPos The position the cursor should start at, defaults to 0, 0. + */ +function testMotion(name, keys, endPos, startPos) { + testVim(name, function(cm, vim, helpers) { + if (!startPos) { + startPos = new Pos(0, 0); + } + cm.setCursor(startPos); + helpers.doKeys(keys); + helpers.assertCursorAt(endPos); + }); +}; + +function makeCursor(line, ch) { + return new Pos(line, ch); +}; + +function offsetCursor(cur, offsetLine, offsetCh) { + return new Pos(cur.line + offsetLine, cur.ch + offsetCh); +}; + +// Motion tests +testMotion('|', '|', makeCursor(0, 0), makeCursor(0,4)); +testMotion('|_repeat', ['3', '|'], makeCursor(0, 2), makeCursor(0,4)); +testMotion('h', 'h', makeCursor(0, 0), word1.start); +testMotion('h_repeat', ['3', 'h'], offsetCursor(word1.end, 0, -3), word1.end); +testMotion('l', 'l', makeCursor(0, 1)); +testMotion('l_repeat', ['2', 'l'], makeCursor(0, 2)); +testMotion('j', 'j', offsetCursor(word1.end, 1, 0), word1.end); +testMotion('j_repeat', ['2', 'j'], offsetCursor(word1.end, 2, 0), word1.end); +testMotion('j_repeat_clip', ['1000', 'j'], endOfDocument); +testMotion('k', 'k', offsetCursor(word3.end, -1, 0), word3.end); +testMotion('k_repeat', ['2', 'k'], makeCursor(0, 4), makeCursor(2, 4)); +testMotion('k_repeat_clip', ['1000', 'k'], makeCursor(0, 4), makeCursor(2, 4)); +testMotion('w', 'w', word1.start); +testMotion('w_multiple_newlines_no_space', 'w', makeCursor(12, 2), makeCursor(11, 2)); +testMotion('w_multiple_newlines_with_space', 'w', makeCursor(14, 0), makeCursor(12, 51)); +testMotion('w_repeat', ['2', 'w'], word2.start); +testMotion('w_wrap', ['w'], word3.start, word2.start); +testMotion('w_endOfDocument', 'w', endOfDocument, endOfDocument); +testMotion('w_start_to_end', ['1000', 'w'], endOfDocument, makeCursor(0, 0)); +testMotion('W', 'W', bigWord1.start); +testMotion('W_repeat', ['2', 'W'], bigWord3.start, bigWord1.start); +testMotion('e', 'e', word1.end); +testMotion('e_repeat', ['2', 'e'], word2.end); +testMotion('e_wrap', 'e', word3.end, word2.end); +testMotion('e_endOfDocument', 'e', endOfDocument, endOfDocument); +testMotion('e_start_to_end', ['1000', 'e'], endOfDocument, makeCursor(0, 0)); +testMotion('b', 'b', word3.start, word3.end); +testMotion('b_repeat', ['2', 'b'], word2.start, word3.end); +testMotion('b_wrap', 'b', word2.start, word3.start); +testMotion('b_startOfDocument', 'b', makeCursor(0, 0), makeCursor(0, 0)); +testMotion('b_end_to_start', ['1000', 'b'], makeCursor(0, 0), endOfDocument); +testMotion('ge', ['g', 'e'], word2.end, word3.end); +testMotion('ge_repeat', ['2', 'g', 'e'], word1.end, word3.start); +testMotion('ge_wrap', ['g', 'e'], word2.end, word3.start); +testMotion('ge_startOfDocument', ['g', 'e'], makeCursor(0, 0), + makeCursor(0, 0)); +testMotion('ge_end_to_start', ['1000', 'g', 'e'], makeCursor(0, 0), endOfDocument); +testMotion('gg', ['g', 'g'], makeCursor(lines[0].line, lines[0].textStart), + makeCursor(3, 1)); +testMotion('gg_repeat', ['3', 'g', 'g'], + makeCursor(lines[2].line, lines[2].textStart)); +testMotion('G', 'G', + makeCursor(lines[lines.length - 1].line, lines[lines.length - 1].textStart), + makeCursor(3, 1)); +testMotion('G_repeat', ['3', 'G'], makeCursor(lines[2].line, + lines[2].textStart)); +// TODO: Make the test code long enough to test Ctrl-F and Ctrl-B. +testMotion('0', '0', makeCursor(0, 0), makeCursor(0, 8)); +testMotion('^', '^', makeCursor(0, lines[0].textStart), makeCursor(0, 8)); +testMotion('+', '+', makeCursor(1, lines[1].textStart), makeCursor(0, 8)); +testMotion('-', '-', makeCursor(0, lines[0].textStart), makeCursor(1, 4)); +testMotion('_', ['6','_'], makeCursor(5, lines[5].textStart), makeCursor(0, 8)); +testMotion('$', '$', makeCursor(0, lines[0].length - 1), makeCursor(0, 1)); +testMotion('$_repeat', ['2', '$'], makeCursor(1, lines[1].length - 1), + makeCursor(0, 3)); +testMotion('f', ['f', 'p'], pChars[0], makeCursor(charLine.line, 0)); +testMotion('f_repeat', ['2', 'f', 'p'], pChars[2], pChars[0]); +testMotion('f_num', ['f', '2'], numChars[2], makeCursor(charLine.line, 0)); +testMotion('t', ['t','p'], offsetCursor(pChars[0], 0, -1), + makeCursor(charLine.line, 0)); +testMotion('t_repeat', ['2', 't', 'p'], offsetCursor(pChars[2], 0, -1), + pChars[0]); +testMotion('F', ['F', 'p'], pChars[0], pChars[1]); +testMotion('F_repeat', ['2', 'F', 'p'], pChars[0], pChars[2]); +testMotion('T', ['T', 'p'], offsetCursor(pChars[0], 0, 1), pChars[1]); +testMotion('T_repeat', ['2', 'T', 'p'], offsetCursor(pChars[0], 0, 1), pChars[2]); +testMotion('%_parens', ['%'], parens1.end, parens1.start); +testMotion('%_squares', ['%'], squares1.end, squares1.start); +testMotion('%_braces', ['%'], curlys1.end, curlys1.start); +testMotion('%_seek_outside', ['%'], seekOutside.end, seekOutside.start); +testMotion('%_seek_inside', ['%'], seekInside.end, seekInside.start); +testVim('%_seek_skip', function(cm, vim, helpers) { + cm.setCursor(0,0); + helpers.doKeys(['%']); + helpers.assertCursorAt(0,9); +}, {value:'01234"("()'}); +testVim('%_skip_string', function(cm, vim, helpers) { + cm.setCursor(0,0); + helpers.doKeys(['%']); + helpers.assertCursorAt(0,4); + cm.setCursor(0,2); + helpers.doKeys(['%']); + helpers.assertCursorAt(0,0); +}, {value:'(")")'}); +testVim('%_skip_comment', function(cm, vim, helpers) { + cm.setCursor(0,0); + helpers.doKeys(['%']); + helpers.assertCursorAt(0,6); + cm.setCursor(0,3); + helpers.doKeys(['%']); + helpers.assertCursorAt(0,0); +}, {value:'(/*)*/)'}); +// Make sure that moving down after going to the end of a line always leaves you +// at the end of a line, but preserves the offset in other cases +testVim('Changing lines after Eol operation', function(cm, vim, helpers) { + cm.setCursor(0,0); + helpers.doKeys(['$']); + helpers.doKeys(['j']); + // After moving to Eol and then down, we should be at Eol of line 2 + helpers.assertCursorAt(new Pos(1, lines[1].length - 1)); + helpers.doKeys(['j']); + // After moving down, we should be at Eol of line 3 + helpers.assertCursorAt(new Pos(2, lines[2].length - 1)); + helpers.doKeys(['h']); + helpers.doKeys(['j']); + // After moving back one space and then down, since line 4 is shorter than line 2, we should + // be at Eol of line 2 - 1 + helpers.assertCursorAt(new Pos(3, lines[3].length - 1)); + helpers.doKeys(['j']); + helpers.doKeys(['j']); + // After moving down again, since line 3 has enough characters, we should be back to the + // same place we were at on line 1 + helpers.assertCursorAt(new Pos(5, lines[2].length - 2)); +}); +//making sure gj and gk recover from clipping +testVim('gj_gk_clipping', function(cm,vim,helpers){ + cm.setCursor(0, 1); + helpers.doKeys('g','j','g','j'); + helpers.assertCursorAt(2, 1); + helpers.doKeys('g','k','g','k'); + helpers.assertCursorAt(0, 1); +},{value: 'line 1\n\nline 2'}); +//testing a mix of j/k and gj/gk +testVim('j_k_and_gj_gk', function(cm,vim,helpers){ + cm.setSize(120); + cm.setCursor(0, 0); + //go to the last character on the first line + helpers.doKeys('$'); + //move up/down on the column within the wrapped line + //side-effect: cursor is not locked to eol anymore + helpers.doKeys('g','k'); + var cur=cm.getCursor(); + eq(cur.line,0); + is((cur.ch<176),'gk didn\'t move cursor back (1)'); + helpers.doKeys('g','j'); + helpers.assertCursorAt(0, 176); + //should move to character 177 on line 2 (j/k preserve character index within line) + helpers.doKeys('j'); + //due to different line wrapping, the cursor can be on a different screen-x now + //gj and gk preserve screen-x on movement, much like moveV + helpers.doKeys('3','g','k'); + cur=cm.getCursor(); + eq(cur.line,1); + is((cur.ch<176),'gk didn\'t move cursor back (2)'); + helpers.doKeys('g','j','2','g','j'); + //should return to the same character-index + helpers.doKeys('k'); + helpers.assertCursorAt(0, 176); +},{ lineWrapping:true, value: 'This line is intentially long to test movement of gj and gk over wrapped lines. I will start on the end of this line, then make a step up and back to set the origin for j and k.\nThis line is supposed to be even longer than the previous. I will jump here and make another wiggle with gj and gk, before I jump back to the line above. Both wiggles should not change my cursor\'s target character but both j/k and gj/gk change each other\'s reference position.'}); +testVim('gj_gk', function(cm, vim, helpers) { + if (phantom) return; + cm.setSize(120); + // Test top of document edge case. + cm.setCursor(0, 4); + helpers.doKeys('g', 'j'); + helpers.doKeys('10', 'g', 'k'); + helpers.assertCursorAt(0, 4); + + // Test moving down preserves column position. + helpers.doKeys('g', 'j'); + var pos1 = cm.getCursor(); + var expectedPos2 = new Pos(0, (pos1.ch - 4) * 2 + 4); + helpers.doKeys('g', 'j'); + helpers.assertCursorAt(expectedPos2); + + // Move to the last character + cm.setCursor(0, 0); + // Move left to reset HSPos + helpers.doKeys('h'); + // Test bottom of document edge case. + helpers.doKeys('100', 'g', 'j'); + var endingPos = cm.getCursor(); + is(endingPos != 0, 'gj should not be on wrapped line 0'); + var topLeftCharCoords = cm.charCoords(makeCursor(0, 0)); + var endingCharCoords = cm.charCoords(endingPos); + is(topLeftCharCoords.left == endingCharCoords.left, 'gj should end up on column 0'); +},{ lineNumbers: false, lineWrapping:true, value: 'Thislineisintentionallylongtotestmovementofgjandgkoverwrappedlines.' }); +testVim('}', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('}'); + helpers.assertCursorAt(1, 0); + cm.setCursor(0, 0); + helpers.doKeys('2', '}'); + helpers.assertCursorAt(4, 0); + cm.setCursor(0, 0); + helpers.doKeys('6', '}'); + helpers.assertCursorAt(5, 0); +}, { value: 'a\n\nb\nc\n\nd' }); +testVim('{', function(cm, vim, helpers) { + cm.setCursor(5, 0); + helpers.doKeys('{'); + helpers.assertCursorAt(4, 0); + cm.setCursor(5, 0); + helpers.doKeys('2', '{'); + helpers.assertCursorAt(1, 0); + cm.setCursor(5, 0); + helpers.doKeys('6', '{'); + helpers.assertCursorAt(0, 0); +}, { value: 'a\n\nb\nc\n\nd' }); +testVim('paragraph_motions', function(cm, vim, helpers) { + cm.setCursor(10, 0); + helpers.doKeys('{'); + helpers.assertCursorAt(4, 0); + helpers.doKeys('{'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('2', '}'); + helpers.assertCursorAt(7, 0); + helpers.doKeys('2', '}'); + helpers.assertCursorAt(16, 0); + + cm.setCursor(9, 0); + helpers.doKeys('}'); + helpers.assertCursorAt(14, 0); + + cm.setCursor(6, 0); + helpers.doKeys('}'); + helpers.assertCursorAt(7, 0); + + // ip inside empty space + cm.setCursor(10, 0); + helpers.doKeys('v', 'i', 'p'); + eqCursorPos(Pos(7, 0), cm.getCursor('anchor')); + eqCursorPos(Pos(12, 0), cm.getCursor('head')); + helpers.doKeys('i', 'p'); + eqCursorPos(Pos(7, 0), cm.getCursor('anchor')); + eqCursorPos(Pos(13, 1), cm.getCursor('head')); + helpers.doKeys('2', 'i', 'p'); + eqCursorPos(Pos(7, 0), cm.getCursor('anchor')); + eqCursorPos(Pos(16, 1), cm.getCursor('head')); + + // should switch to visualLine mode + cm.setCursor(14, 0); + helpers.doKeys('<Esc>', 'v', 'i', 'p'); + helpers.assertCursorAt(14, 0); + + cm.setCursor(14, 0); + helpers.doKeys('<Esc>', 'V', 'i', 'p'); + eqCursorPos(Pos(16, 1), cm.getCursor('head')); + + // ap inside empty space + cm.setCursor(10, 0); + helpers.doKeys('<Esc>', 'v', 'a', 'p'); + eqCursorPos(Pos(7, 0), cm.getCursor('anchor')); + eqCursorPos(Pos(13, 1), cm.getCursor('head')); + helpers.doKeys('a', 'p'); + eqCursorPos(Pos(7, 0), cm.getCursor('anchor')); + eqCursorPos(Pos(16, 1), cm.getCursor('head')); + + cm.setCursor(13, 0); + helpers.doKeys('v', 'a', 'p'); + eqCursorPos(Pos(13, 0), cm.getCursor('anchor')); + eqCursorPos(Pos(14, 0), cm.getCursor('head')); + + cm.setCursor(16, 0); + helpers.doKeys('v', 'a', 'p'); + eqCursorPos(Pos(14, 0), cm.getCursor('anchor')); + eqCursorPos(Pos(16, 1), cm.getCursor('head')); + + cm.setCursor(0, 0); + helpers.doKeys('v', 'a', 'p'); + eqCursorPos(Pos(0, 0), cm.getCursor('anchor')); + eqCursorPos(Pos(4, 0), cm.getCursor('head')); + + cm.setCursor(0, 0); + helpers.doKeys('d', 'i', 'p'); + var register = helpers.getRegisterController().getRegister(); + eq('a\na\n', register.toString()); + is(register.linewise); + helpers.doKeys('3', 'j', 'p'); + helpers.doKeys('y', 'i', 'p'); + is(register.linewise); + eq('b\na\na\nc\n', register.toString()); +}, { value: 'a\na\n\n\n\nb\nc\n\n\n\n\n\n\nd\n\ne\nf' }); + +// Operator tests +testVim('dl', function(cm, vim, helpers) { + var curStart = makeCursor(0, 0); + cm.setCursor(curStart); + helpers.doKeys('d', 'l'); + eq('word1 ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' ', register.toString()); + is(!register.linewise); + eqCursorPos(curStart, cm.getCursor()); +}, { value: ' word1 ' }); +testVim('dl_eol', function(cm, vim, helpers) { + cm.setCursor(0, 6); + helpers.doKeys('d', 'l'); + eq(' word1', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' ', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 5); +}, { value: ' word1 ' }); +testVim('dl_repeat', function(cm, vim, helpers) { + var curStart = makeCursor(0, 0); + cm.setCursor(curStart); + helpers.doKeys('2', 'd', 'l'); + eq('ord1 ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' w', register.toString()); + is(!register.linewise); + eqCursorPos(curStart, cm.getCursor()); +}, { value: ' word1 ' }); +testVim('dh', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'h'); + eq(' wrd1 ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('o', register.toString()); + is(!register.linewise); + eqCursorPos(offsetCursor(curStart, 0 , -1), cm.getCursor()); +}, { value: ' word1 ' }); +testVim('dj', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'j'); + eq(' word3', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' word1\nword2\n', register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, 1); +}, { value: ' word1\nword2\n word3' }); +testVim('dj_end_of_document', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'j'); + eq('', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' word1 \n', register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1 ' }); +testVim('dk', function(cm, vim, helpers) { + var curStart = makeCursor(1, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'k'); + eq(' word3', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' word1\nword2\n', register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, 1); +}, { value: ' word1\nword2\n word3' }); +testVim('dk_start_of_document', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('d', 'k'); + eq('', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' word1 \n', register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1 ' }); +testVim('dw_space', function(cm, vim, helpers) { + var curStart = makeCursor(0, 0); + cm.setCursor(curStart); + helpers.doKeys('d', 'w'); + eq('word1 ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' ', register.toString()); + is(!register.linewise); + eqCursorPos(curStart, cm.getCursor()); +}, { value: ' word1 ' }); +testVim('dw_word', function(cm, vim, helpers) { + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); + helpers.doKeys('d', 'w'); + eq(' word2', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1 ', register.toString()); + is(!register.linewise); + eqCursorPos(curStart, cm.getCursor()); +}, { value: ' word1 word2' }); +testVim('dw_unicode_word', function(cm, vim, helpers) { + helpers.doKeys('d', 'w'); + eq(cm.getValue().length, 10); + helpers.doKeys('d', 'w'); + eq(cm.getValue().length, 6); + helpers.doKeys('d', 'w'); + eq(cm.getValue().length, 5); + helpers.doKeys('d', 'e'); + eq(cm.getValue().length, 2); +}, { value: ' \u0562\u0561\u0580\u0587\xbbe\xb5g ' }); +testVim('dw_only_word', function(cm, vim, helpers) { + // Test that if there is only 1 word left, dw deletes till the end of the + // line. + cm.setCursor(0, 1); + helpers.doKeys('d', 'w'); + eq(' ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1 ', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1 ' }); +testVim('dw_eol', function(cm, vim, helpers) { + // Assert that dw does not delete the newline if last word to delete is at end + // of line. + cm.setCursor(0, 1); + helpers.doKeys('d', 'w'); + eq(' \nword2', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1\nword2' }); +testVim('dw_eol_with_multiple_newlines', function(cm, vim, helpers) { + // Assert that dw does not delete the newline if last word to delete is at end + // of line and it is followed by multiple newlines. + cm.setCursor(0, 1); + helpers.doKeys('d', 'w'); + eq(' \n\nword2', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1\n\nword2' }); +testVim('dw_empty_line_followed_by_whitespace', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq(' \nword', cm.getValue()); +}, { value: '\n \nword' }); +testVim('dw_empty_line_followed_by_word', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq('word', cm.getValue()); +}, { value: '\nword' }); +testVim('dw_empty_line_followed_by_empty_line', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq('\n', cm.getValue()); +}, { value: '\n\n' }); +testVim('dw_whitespace_followed_by_whitespace', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq('\n \n', cm.getValue()); +}, { value: ' \n \n' }); +testVim('dw_whitespace_followed_by_empty_line', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq('\n\n', cm.getValue()); +}, { value: ' \n\n' }); +testVim('dw_word_whitespace_word', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'w'); + eq('\n \nword2', cm.getValue()); +}, { value: 'word1\n \nword2'}) +testVim('dw_end_of_document', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('d', 'w'); + eq('\nab', cm.getValue()); +}, { value: '\nabc' }); +testVim('dw_repeat', function(cm, vim, helpers) { + // Assert that dw does delete newline if it should go to the next line, and + // that repeat works properly. + cm.setCursor(0, 1); + helpers.doKeys('d', '2', 'w'); + eq(' ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1\nword2', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 0); +}, { value: ' word1\nword2' }); +testVim('de_word_start_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'e'); + eq('\n\n', cm.getValue()); +}, { value: 'word\n\n' }); +testVim('de_word_end_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(0, 3); + helpers.doKeys('d', 'e'); + eq('wor', cm.getValue()); +}, { value: 'word\n\n\n' }); +testVim('de_whitespace_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'e'); + eq('', cm.getValue()); +}, { value: ' \n\n\n' }); +testVim('de_end_of_document', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('d', 'e'); + eq('\nab', cm.getValue()); +}, { value: '\nabc' }); +testVim('db_empty_lines', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('d', 'b'); + eq('\n\n', cm.getValue()); +}, { value: '\n\n\n' }); +testVim('db_word_start_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('d', 'b'); + eq('\nword', cm.getValue()); +}, { value: '\n\nword' }); +testVim('db_word_end_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(2, 3); + helpers.doKeys('d', 'b'); + eq('\n\nd', cm.getValue()); +}, { value: '\n\nword' }); +testVim('db_whitespace_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('d', 'b'); + eq('', cm.getValue()); +}, { value: '\n \n' }); +testVim('db_start_of_document', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'b'); + eq('abc\n', cm.getValue()); +}, { value: 'abc\n' }); +testVim('dge_empty_lines', function(cm, vim, helpers) { + cm.setCursor(1, 0); + helpers.doKeys('d', 'g', 'e'); + // Note: In real VIM the result should be '', but it's not quite consistent, + // since 2 newlines are deleted. But in the similar case of word\n\n, only + // 1 newline is deleted. We'll diverge from VIM's behavior since it's much + // easier this way. + eq('\n', cm.getValue()); +}, { value: '\n\n' }); +testVim('dge_word_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(1, 0); + helpers.doKeys('d', 'g', 'e'); + eq('wor\n', cm.getValue()); +}, { value: 'word\n\n'}); +testVim('dge_whitespace_and_empty_lines', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('d', 'g', 'e'); + eq('', cm.getValue()); +}, { value: '\n \n' }); +testVim('dge_start_of_document', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('d', 'g', 'e'); + eq('bc\n', cm.getValue()); +}, { value: 'abc\n' }); +testVim('d_inclusive', function(cm, vim, helpers) { + // Assert that when inclusive is set, the character the cursor is on gets + // deleted too. + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); + helpers.doKeys('d', 'e'); + eq(' ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1', register.toString()); + is(!register.linewise); + eqCursorPos(curStart, cm.getCursor()); +}, { value: ' word1 ' }); +testVim('d_reverse', function(cm, vim, helpers) { + // Test that deleting in reverse works. + cm.setCursor(1, 0); + helpers.doKeys('d', 'b'); + eq(' word2 ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1\n', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 1); +}, { value: ' word1\nword2 ' }); +testVim('dd', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedBuffer = cm.getRange(new Pos(0, 0), + new Pos(1, 0)); + var expectedLineCount = cm.lineCount() - 1; + helpers.doKeys('d', 'd'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, lines[1].textStart); +}); +testVim('dd_prefix_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedBuffer = cm.getRange(new Pos(0, 0), + new Pos(2, 0)); + var expectedLineCount = cm.lineCount() - 2; + helpers.doKeys('2', 'd', 'd'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, lines[2].textStart); +}); +testVim('dd_motion_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedBuffer = cm.getRange(new Pos(0, 0), + new Pos(2, 0)); + var expectedLineCount = cm.lineCount() - 2; + helpers.doKeys('d', '2', 'd'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, lines[2].textStart); +}); +testVim('dd_multiply_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedBuffer = cm.getRange(new Pos(0, 0), + new Pos(6, 0)); + var expectedLineCount = cm.lineCount() - 6; + helpers.doKeys('2', 'd', '3', 'd'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, lines[6].textStart); +}); +testVim('dd_lastline', function(cm, vim, helpers) { + cm.setCursor(cm.lineCount(), 0); + var expectedLineCount = cm.lineCount() - 1; + helpers.doKeys('d', 'd'); + eq(expectedLineCount, cm.lineCount()); + helpers.assertCursorAt(cm.lineCount() - 1, 0); +}); +testVim('dd_only_line', function(cm, vim, helpers) { + cm.setCursor(0, 0); + var expectedRegister = cm.getValue() + "\n"; + helpers.doKeys('d','d'); + eq(1, cm.lineCount()); + eq('', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedRegister, register.toString()); +}, { value: "thisistheonlyline" }); +// Yank commands should behave the exact same as d commands, expect that nothing +// gets deleted. +testVim('yw_repeat', function(cm, vim, helpers) { + // Assert that yw does yank newline if it should go to the next line, and + // that repeat works properly. + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); + helpers.doKeys('y', '2', 'w'); + eq(' word1\nword2', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1\nword2', register.toString()); + is(!register.linewise); + eqCursorPos(curStart, cm.getCursor()); +}, { value: ' word1\nword2' }); +testVim('yy_multiply_repeat', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + var expectedBuffer = cm.getRange(new Pos(0, 0), + new Pos(6, 0)); + var expectedLineCount = cm.lineCount(); + helpers.doKeys('2', 'y', '3', 'y'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + eqCursorPos(curStart, cm.getCursor()); +}); +testVim('2dd_blank_P', function(cm, vim, helpers) { + helpers.doKeys('2', 'd', 'd', 'P'); + eq('\na\n\n', cm.getValue()); +}, { value: '\na\n\n' }); +// Change commands behave like d commands except that it also enters insert +// mode. In addition, when the change is linewise, an additional newline is +// inserted so that insert mode starts on that line. +testVim('cw', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('c', '2', 'w'); + eq(' word3', cm.getValue()); + helpers.assertCursorAt(0, 0); +}, { value: 'word1 word2 word3'}); +testVim('cw_repeat', function(cm, vim, helpers) { + // Assert that cw does delete newline if it should go to the next line, and + // that repeat works properly. + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); + helpers.doKeys('c', '2', 'w'); + eq(' ', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word1\nword2', register.toString()); + is(!register.linewise); + eqCursorPos(curStart, cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: ' word1\nword2' }); +testVim('cc_multiply_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedBuffer = cm.getRange(new Pos(0, 0), + new Pos(6, 0)); + var expectedLineCount = cm.lineCount() - 5; + helpers.doKeys('2', 'c', '3', 'c'); + eq(expectedLineCount, cm.lineCount()); + var register = helpers.getRegisterController().getRegister(); + eq(expectedBuffer, register.toString()); + is(register.linewise); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('ct', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('c', 't', 'w'); + eq(' word1 word3', cm.getValue()); + helpers.doKeys('<Esc>', 'c', '|'); + eq(' word3', cm.getValue()); + helpers.assertCursorAt(0, 0); + helpers.doKeys('<Esc>', '2', 'u', 'w', 'h'); + helpers.doKeys('c', '2', 'g', 'e'); + eq(' wordword3', cm.getValue()); +}, { value: ' word1 word2 word3'}); +testVim('cc_should_not_append_to_document', function(cm, vim, helpers) { + var expectedLineCount = cm.lineCount(); + cm.setCursor(cm.lastLine(), 0); + helpers.doKeys('c', 'c'); + eq(expectedLineCount, cm.lineCount()); +}); +function fillArray(val, times) { + var arr = []; + for (var i = 0; i < times; i++) { + arr.push(val); + } + return arr; +} +testVim('c_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'l', 'c'); + var replacement = fillArray('hello', 3); + cm.replaceSelections(replacement); + eq('1hello\n5hello\nahellofg', cm.getValue()); + helpers.doKeys('<Esc>'); + cm.setCursor(2, 3); + helpers.doKeys('<C-v>', '2', 'k', 'h', 'C'); + replacement = fillArray('world', 3); + cm.replaceSelections(replacement); + eq('1hworld\n5hworld\nahworld', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); +testVim('c_visual_block_replay', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('<C-v>', '2', 'j', 'l', 'c'); + var replacement = fillArray('fo', 3); + cm.replaceSelections(replacement); + eq('1fo4\n5fo8\nafodefg', cm.getValue()); + helpers.doKeys('<Esc>'); + cm.setCursor(0, 0); + helpers.doKeys('.'); + eq('foo4\nfoo8\nfoodefg', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); + +testVim('d_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'l', 'd'); + eq('1\n5\nafg', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); +testVim('D_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('<C-v>', '2', 'j', 'l', 'D'); + eq('1\n5\na', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); + +testVim('s_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'l', 's'); + var replacement = fillArray('hello{', 3); + cm.replaceSelections(replacement); + eq('1hello{\n5hello{\nahello{fg\n', cm.getValue()); + helpers.doKeys('<Esc>'); + cm.setCursor(2, 3); + helpers.doKeys('<C-v>', '1', 'k', 'h', 'S'); + replacement = fillArray('world', 1); + cm.replaceSelections(replacement); + eq('1hello{\n world\n', cm.getValue()); +}, {value: '1234\n5678\nabcdefg\n'}); + +// Swapcase commands edit in place and do not modify registers. +testVim('g~w_repeat', function(cm, vim, helpers) { + // Assert that dw does delete newline if it should go to the next line, and + // that repeat works properly. + var curStart = makeCursor(0, 1); + cm.setCursor(curStart); + helpers.doKeys('g', '~', '2', 'w'); + eq(' WORD1\nWORD2', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + eqCursorPos(curStart, cm.getCursor()); +}, { value: ' word1\nword2' }); +testVim('g~g~', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + var expectedLineCount = cm.lineCount(); + var expectedValue = cm.getValue().toUpperCase(); + helpers.doKeys('2', 'g', '~', '3', 'g', '~'); + eq(expectedValue, cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + eqCursorPos(curStart, cm.getCursor()); +}, { value: ' word1\nword2\nword3\nword4\nword5\nword6' }); +testVim('gu_and_gU', function(cm, vim, helpers) { + var curStart = makeCursor(0, 7); + var value = cm.getValue(); + cm.setCursor(curStart); + helpers.doKeys('2', 'g', 'U', 'w'); + eq(cm.getValue(), 'wa wb xX WC wd'); + eqCursorPos(curStart, cm.getCursor()); + helpers.doKeys('2', 'g', 'u', 'w'); + eq(cm.getValue(), value); + + helpers.doKeys('2', 'g', 'U', 'B'); + eq(cm.getValue(), 'wa WB Xx wc wd'); + eqCursorPos(makeCursor(0, 3), cm.getCursor()); + + cm.setCursor(makeCursor(0, 4)); + helpers.doKeys('g', 'u', 'i', 'w'); + eq(cm.getValue(), 'wa wb Xx wc wd'); + eqCursorPos(makeCursor(0, 3), cm.getCursor()); + + // TODO: support gUgU guu + // eqCursorPos(makeCursor(0, 0), cm.getCursor()); + + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); +}, { value: 'wa wb xx wc wd' }); +testVim('visual_block_~', function(cm, vim, helpers) { + cm.setCursor(1, 1); + helpers.doKeys('<C-v>', 'l', 'l', 'j', '~'); + helpers.assertCursorAt(1, 1); + eq('hello\nwoRLd\naBCDe', cm.getValue()); + cm.setCursor(2, 0); + helpers.doKeys('v', 'l', 'l', '~'); + helpers.assertCursorAt(2, 0); + eq('hello\nwoRLd\nAbcDe', cm.getValue()); +},{value: 'hello\nwOrld\nabcde' }); +testVim('._swapCase_visualBlock', function(cm, vim, helpers) { + helpers.doKeys('<C-v>', 'j', 'j', 'l', '~'); + cm.setCursor(0, 3); + helpers.doKeys('.'); + eq('HelLO\nWorLd\nAbcdE', cm.getValue()); +},{value: 'hEllo\nwOrlD\naBcDe' }); +testVim('._delete_visualBlock', function(cm, vim, helpers) { + helpers.doKeys('<C-v>', 'j', 'x'); + eq('ive\ne\nsome\nsugar', cm.getValue()); + helpers.doKeys('.'); + eq('ve\n\nsome\nsugar', cm.getValue()); + helpers.doKeys('j', 'j', '.'); + eq('ve\n\nome\nugar', cm.getValue()); + helpers.doKeys('u', '<C-r>', '.'); + eq('ve\n\nme\ngar', cm.getValue()); +},{value: 'give\nme\nsome\nsugar' }); +testVim('>{motion}', function(cm, vim, helpers) { + cm.setCursor(1, 3); + var expectedLineCount = cm.lineCount(); + var expectedValue = ' word1\n word2\nword3 '; + helpers.doKeys('>', 'k'); + eq(expectedValue, cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 3); +}, { value: ' word1\nword2\nword3 ', indentUnit: 2 }); +testVim('>>', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedLineCount = cm.lineCount(); + var expectedValue = ' word1\n word2\nword3 '; + helpers.doKeys('2', '>', '>'); + eq(expectedValue, cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 3); +}, { value: ' word1\nword2\nword3 ', indentUnit: 2 }); +testVim('<{motion}', function(cm, vim, helpers) { + cm.setCursor(1, 3); + var expectedLineCount = cm.lineCount(); + var expectedValue = ' word1\nword2\nword3 '; + helpers.doKeys('<', 'k'); + eq(expectedValue, cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 1); +}, { value: ' word1\n word2\nword3 ', indentUnit: 2 }); +testVim('<<', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedLineCount = cm.lineCount(); + var expectedValue = ' word1\nword2\nword3 '; + helpers.doKeys('2', '<', '<'); + eq(expectedValue, cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 1); +}, { value: ' word1\n word2\nword3 ', indentUnit: 2 }); + +// Edit tests +function testEdit(name, before, pos, edit, after) { + return testVim(name, function(cm, vim, helpers) { + var ch = before.search(pos) + var line = before.substring(0, ch).split('\n').length - 1; + if (line) { + ch = before.substring(0, ch).split('\n').pop().length; + } + cm.setCursor(line, ch); + helpers.doKeys.apply(this, edit.split('')); + eq(after, cm.getValue()); + }, {value: before}); +} + +// These Delete tests effectively cover word-wise Change, Visual & Yank. +// Tabs are used as differentiated whitespace to catch edge cases. +// Normal word: +testEdit('diw_mid_spc', 'foo \tbAr\t baz', /A/, 'diw', 'foo \t\t baz'); +testEdit('daw_mid_spc', 'foo \tbAr\t baz', /A/, 'daw', 'foo \tbaz'); +testEdit('diw_mid_punct', 'foo \tbAr.\t baz', /A/, 'diw', 'foo \t.\t baz'); +testEdit('daw_mid_punct', 'foo \tbAr.\t baz', /A/, 'daw', 'foo.\t baz'); +testEdit('diw_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'diw', 'foo \t,.\t baz'); +testEdit('daw_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'daw', 'foo \t,.\t baz'); +testEdit('diw_start_spc', 'bAr \tbaz', /A/, 'diw', ' \tbaz'); +testEdit('daw_start_spc', 'bAr \tbaz', /A/, 'daw', 'baz'); +testEdit('diw_start_punct', 'bAr. \tbaz', /A/, 'diw', '. \tbaz'); +testEdit('daw_start_punct', 'bAr. \tbaz', /A/, 'daw', '. \tbaz'); +testEdit('diw_end_spc', 'foo \tbAr', /A/, 'diw', 'foo \t'); +testEdit('daw_end_spc', 'foo \tbAr', /A/, 'daw', 'foo'); +testEdit('diw_end_punct', 'foo \tbAr.', /A/, 'diw', 'foo \t.'); +testEdit('daw_end_punct', 'foo \tbAr.', /A/, 'daw', 'foo.'); +// Big word: +testEdit('diW_mid_spc', 'foo \tbAr\t baz', /A/, 'diW', 'foo \t\t baz'); +testEdit('daW_mid_spc', 'foo \tbAr\t baz', /A/, 'daW', 'foo \tbaz'); +testEdit('diW_mid_punct', 'foo \tbAr.\t baz', /A/, 'diW', 'foo \t\t baz'); +testEdit('daW_mid_punct', 'foo \tbAr.\t baz', /A/, 'daW', 'foo \tbaz'); +testEdit('diW_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'diW', 'foo \t\t baz'); +testEdit('daW_mid_punct2', 'foo \t,bAr.\t baz', /A/, 'daW', 'foo \tbaz'); +testEdit('diW_start_spc', 'bAr\t baz', /A/, 'diW', '\t baz'); +testEdit('daW_start_spc', 'bAr\t baz', /A/, 'daW', 'baz'); +testEdit('diW_start_punct', 'bAr.\t baz', /A/, 'diW', '\t baz'); +testEdit('daW_start_punct', 'bAr.\t baz', /A/, 'daW', 'baz'); +testEdit('diW_end_spc', 'foo \tbAr', /A/, 'diW', 'foo \t'); +testEdit('daW_end_spc', 'foo \tbAr', /A/, 'daW', 'foo'); +testEdit('diW_end_punct', 'foo \tbAr.', /A/, 'diW', 'foo \t'); +testEdit('daW_end_punct', 'foo \tbAr.', /A/, 'daW', 'foo'); +// Deleting text objects +// Open and close on same line +testEdit('di(_open_spc', 'foo (bAr) baz', /\(/, 'di(', 'foo () baz'); +testEdit('di)_open_spc', 'foo (bAr) baz', /\(/, 'di)', 'foo () baz'); +testEdit('dib_open_spc', 'foo (bAr) baz', /\(/, 'dib', 'foo () baz'); +testEdit('da(_open_spc', 'foo (bAr) baz', /\(/, 'da(', 'foo baz'); +testEdit('da)_open_spc', 'foo (bAr) baz', /\(/, 'da)', 'foo baz'); + +testEdit('di(_middle_spc', 'foo (bAr) baz', /A/, 'di(', 'foo () baz'); +testEdit('di)_middle_spc', 'foo (bAr) baz', /A/, 'di)', 'foo () baz'); +testEdit('da(_middle_spc', 'foo (bAr) baz', /A/, 'da(', 'foo baz'); +testEdit('da)_middle_spc', 'foo (bAr) baz', /A/, 'da)', 'foo baz'); + +testEdit('di(_close_spc', 'foo (bAr) baz', /\)/, 'di(', 'foo () baz'); +testEdit('di)_close_spc', 'foo (bAr) baz', /\)/, 'di)', 'foo () baz'); +testEdit('da(_close_spc', 'foo (bAr) baz', /\)/, 'da(', 'foo baz'); +testEdit('da)_close_spc', 'foo (bAr) baz', /\)/, 'da)', 'foo baz'); + +// delete around and inner b. +testEdit('dab_on_(_should_delete_around_()block', 'o( in(abc) )', /\(a/, 'dab', 'o( in )'); + +// delete around and inner B. +testEdit('daB_on_{_should_delete_around_{}block', 'o{ in{abc} }', /{a/, 'daB', 'o{ in }'); +testEdit('diB_on_{_should_delete_inner_{}block', 'o{ in{abc} }', /{a/, 'diB', 'o{ in{} }'); + +testEdit('da{_on_{_should_delete_inner_block', 'o{ in{abc} }', /{a/, 'da{', 'o{ in }'); +testEdit('di[_on_(_should_not_delete', 'foo (bAr) baz', /\(/, 'di[', 'foo (bAr) baz'); +testEdit('di[_on_)_should_not_delete', 'foo (bAr) baz', /\)/, 'di[', 'foo (bAr) baz'); +testEdit('da[_on_(_should_not_delete', 'foo (bAr) baz', /\(/, 'da[', 'foo (bAr) baz'); +testEdit('da[_on_)_should_not_delete', 'foo (bAr) baz', /\)/, 'da[', 'foo (bAr) baz'); +testMotion('di(_outside_should_stay', ['d', 'i', '('], new Pos(0, 0), new Pos(0, 0)); + +// Open and close on different lines, equally indented +testEdit('di{_middle_spc', 'a{\n\tbar\n}b', /r/, 'di{', 'a{}b'); +testEdit('di}_middle_spc', 'a{\n\tbar\n}b', /r/, 'di}', 'a{}b'); +testEdit('da{_middle_spc', 'a{\n\tbar\n}b', /r/, 'da{', 'ab'); +testEdit('da}_middle_spc', 'a{\n\tbar\n}b', /r/, 'da}', 'ab'); +testEdit('daB_middle_spc', 'a{\n\tbar\n}b', /r/, 'daB', 'ab'); + +// open and close on diff lines, open indented less than close +testEdit('di{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'di{', 'a{}b'); +testEdit('di}_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'di}', 'a{}b'); +testEdit('da{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'da{', 'ab'); +testEdit('da}_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'da}', 'ab'); + +// open and close on diff lines, open indented more than close +testEdit('di[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di[', 'a\t[]b'); +testEdit('di]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'di]', 'a\t[]b'); +testEdit('da[_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da[', 'a\tb'); +testEdit('da]_middle_spc', 'a\t[\n\tbar\n]b', /r/, 'da]', 'a\tb'); + +function testSelection(name, before, pos, keys, sel) { + return testVim(name, function(cm, vim, helpers) { + var ch = before.search(pos) + var line = before.substring(0, ch).split('\n').length - 1; + if (line) { + ch = before.substring(0, ch).split('\n').pop().length; + } + cm.setCursor(line, ch); + helpers.doKeys.apply(this, keys.split('')); + eq(sel, cm.getSelection()); + }, {value: before}); +} +testSelection('viw_middle_spc', 'foo \tbAr\t baz', /A/, 'viw', 'bAr'); +testSelection('vaw_middle_spc', 'foo \tbAr\t baz', /A/, 'vaw', 'bAr\t '); +testSelection('viw_middle_punct', 'foo \tbAr,\t baz', /A/, 'viw', 'bAr'); +testSelection('vaW_middle_punct', 'foo \tbAr,\t baz', /A/, 'vaW', 'bAr,\t '); +testSelection('viw_start_spc', 'foo \tbAr\t baz', /b/, 'viw', 'bAr'); +testSelection('viw_end_spc', 'foo \tbAr\t baz', /r/, 'viw', 'bAr'); +testSelection('viw_eol', 'foo \tbAr', /r/, 'viw', 'bAr'); +testSelection('vi{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'vi{', '\n\tbar\n\t'); +testSelection('va{_middle_spc', 'a{\n\tbar\n\t}b', /r/, 'va{', '{\n\tbar\n\t}'); + +testVim('mouse_select', function(cm, vim, helpers) { + cm.setSelection(Pos(0, 2), Pos(0, 4), {origin: '*mouse'}); + is(cm.state.vim.visualMode); + is(!cm.state.vim.visualLine); + is(!cm.state.vim.visualBlock); + helpers.doKeys('<Esc>'); + is(!cm.somethingSelected()); + helpers.doKeys('g', 'v'); + eq('cd', cm.getSelection()); +}, {value: 'abcdef'}); + +// Operator-motion tests +testVim('D', function(cm, vim, helpers) { + cm.setCursor(0, 3); + helpers.doKeys('D'); + eq(' wo\nword2\n word3', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('rd1', register.toString()); + is(!register.linewise); + helpers.assertCursorAt(0, 2); +}, { value: ' word1\nword2\n word3' }); +testVim('C', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('C'); + eq(' wo\nword2\n word3', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('rd1', register.toString()); + is(!register.linewise); + eqCursorPos(curStart, cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: ' word1\nword2\n word3' }); +testVim('Y', function(cm, vim, helpers) { + var curStart = makeCursor(0, 3); + cm.setCursor(curStart); + helpers.doKeys('Y'); + eq(' word1\nword2\n word3', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq(' word1\n', register.toString()); + is(register.linewise); + helpers.assertCursorAt(0, 3); +}, { value: ' word1\nword2\n word3' }); +testVim('~', function(cm, vim, helpers) { + helpers.doKeys('3', '~'); + eq('ABCdefg', cm.getValue()); + helpers.assertCursorAt(0, 3); +}, { value: 'abcdefg' }); + +// Action tests +testVim('ctrl-a', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('<C-a>'); + eq('-9', cm.getValue()); + helpers.assertCursorAt(0, 1); + helpers.doKeys('2','<C-a>'); + eq('-7', cm.getValue()); +}, {value: '-10'}); +testVim('ctrl-x', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('<C-x>'); + eq('-1', cm.getValue()); + helpers.assertCursorAt(0, 1); + helpers.doKeys('2','<C-x>'); + eq('-3', cm.getValue()); +}, {value: '0'}); +testVim('<C-x>/<C-a> search forward', function(cm, vim, helpers) { + forEach(['<C-x>', '<C-a>'], function(key) { + cm.setCursor(0, 0); + helpers.doKeys(key); + helpers.assertCursorAt(0, 5); + helpers.doKeys('l'); + helpers.doKeys(key); + helpers.assertCursorAt(0, 10); + cm.setCursor(0, 11); + helpers.doKeys(key); + helpers.assertCursorAt(0, 11); + }); +}, {value: '__jmp1 jmp2 jmp'}); +testVim('insert_ctrl_w', function(cm, vim, helpers) { + var curStart = makeCursor(0, 10); + cm.setCursor(curStart); + helpers.doKeys('a'); + helpers.doKeys('<C-w>'); + eq('word1/', cm.getValue()); + var register = helpers.getRegisterController().getRegister(); + eq('word2', register.toString()); + is(!register.linewise); + var curEnd = makeCursor(0, 6); + eqCursorPos(curEnd, cm.getCursor()); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: 'word1/word2' }); +testVim('a', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('a'); + helpers.assertCursorAt(0, 2); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('a_eol', function(cm, vim, helpers) { + cm.setCursor(0, lines[0].length - 1); + helpers.doKeys('a'); + helpers.assertCursorAt(0, lines[0].length); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('A_endOfSelectedArea', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('v', 'j', 'l'); + helpers.doKeys('A'); + helpers.assertCursorAt(1, 2); + eq('vim-insert', cm.getOption('keyMap')); +}, {value: 'foo\nbar'}); +testVim('i', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('i'); + helpers.assertCursorAt(0, 1); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('i_repeat', function(cm, vim, helpers) { + helpers.doKeys('3', 'i'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('<Esc>'); + eq('testtesttest', cm.getValue()); + helpers.assertCursorAt(0, 11); +}, { value: '' }); +testVim('i_repeat_delete', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('2', 'i'); + cm.replaceRange('z', cm.getCursor()); + helpers.doInsertModeKeys('Backspace', 'Backspace'); + helpers.doKeys('<Esc>'); + eq('abe', cm.getValue()); + helpers.assertCursorAt(0, 1); +}, { value: 'abcde' }); +testVim('insert', function(cm, vim, helpers) { + helpers.doKeys('i'); + eq('vim-insert', cm.getOption('keyMap')); + eq(false, cm.state.overwrite); + helpers.doKeys('<Ins>'); + eq('vim-replace', cm.getOption('keyMap')); + eq(true, cm.state.overwrite); + helpers.doKeys('<Ins>'); + eq('vim-insert', cm.getOption('keyMap')); + eq(false, cm.state.overwrite); +}); +testVim('i_backspace', function(cm, vim, helpers) { + cm.setCursor(0, 10); + helpers.doKeys('i'); + helpers.doInsertModeKeys('Backspace'); + helpers.assertCursorAt(0, 9); + eq('012345678', cm.getValue()); +}, { value: '0123456789'}); +testVim('i_overwrite_backspace', function(cm, vim, helpers) { + cm.setCursor(0, 10); + helpers.doKeys('i'); + helpers.doKeys('<Ins>'); + helpers.doInsertModeKeys('Backspace'); + helpers.assertCursorAt(Pos(0, 9, "after")); + eq('0123456789', cm.getValue()); +}, { value: '0123456789'}); +testVim('A', function(cm, vim, helpers) { + helpers.doKeys('A'); + helpers.assertCursorAt(0, lines[0].length); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('A_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'A'); + var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' '); + replacement.pop(); + cm.replaceSelections(replacement); + eq('testhello\nmehello\npleahellose', cm.getValue()); + helpers.doKeys('<Esc>'); + cm.setCursor(0, 0); + helpers.doKeys('.'); + // TODO this doesn't work yet + // eq('teshellothello\nme hello hello\nplehelloahellose', cm.getValue()); +}, {value: 'test\nme\nplease'}); +testVim('I', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('I'); + helpers.assertCursorAt(0, lines[0].textStart); + eq('vim-insert', cm.getOption('keyMap')); +}); +testVim('I_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('3', 'I'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('<Esc>'); + eq('testtesttestblah', cm.getValue()); + helpers.assertCursorAt(0, 11); +}, { value: 'blah' }); +testVim('I_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('<C-v>', '2', 'j', 'l', 'l', 'I'); + var replacement = new Array(cm.listSelections().length+1).join('hello ').split(' '); + replacement.pop(); + cm.replaceSelections(replacement); + eq('hellotest\nhellome\nhelloplease', cm.getValue()); +}, {value: 'test\nme\nplease'}); +testVim('o', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('o'); + eq('word1\n\nword2', cm.getValue()); + helpers.assertCursorAt(1, 0); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: 'word1\nword2' }); +testVim('o_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('3', 'o'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('<Esc>'); + eq('\ntest\ntest\ntest', cm.getValue()); + helpers.assertCursorAt(3, 3); +}, { value: '' }); +testVim('O', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('O'); + eq('\nword1\nword2', cm.getValue()); + helpers.assertCursorAt(0, 0); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: 'word1\nword2' }); +testVim('J', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('J'); + var expectedValue = 'word1 word2\nword3\n word4'; + eq(expectedValue, cm.getValue()); + helpers.assertCursorAt(0, expectedValue.indexOf('word2') - 1); +}, { value: 'word1 \n word2\nword3\n word4' }); +testVim('J_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('3', 'J'); + var expectedValue = 'word1 word2 word3\n word4'; + eq(expectedValue, cm.getValue()); + helpers.assertCursorAt(0, expectedValue.indexOf('word3') - 1); +}, { value: 'word1 \n word2\nword3\n word4' }); +testVim('p', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false); + helpers.doKeys('p'); + eq('__abc\ndef_', cm.getValue()); + helpers.assertCursorAt(1, 2); +}, { value: '___' }); +testVim('p_register', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().getRegister('a').setText('abc\ndef', false); + helpers.doKeys('"', 'a', 'p'); + eq('__abc\ndef_', cm.getValue()); + helpers.assertCursorAt(1, 2); +}, { value: '___' }); +testVim('p_wrong_register', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().getRegister('a').setText('abc\ndef', false); + helpers.doKeys('p'); + eq('___', cm.getValue()); + helpers.assertCursorAt(0, 1); +}, { value: '___' }); +testVim('p_line', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().pushText('"', 'yank', ' a\nd\n', true); + helpers.doKeys('2', 'p'); + eq('___\n a\nd\n a\nd', cm.getValue()); + helpers.assertCursorAt(1, 2); +}, { value: '___' }); +testVim('p_lastline', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().pushText('"', 'yank', ' a\nd', true); + helpers.doKeys('2', 'p'); + eq('___\n a\nd\n a\nd', cm.getValue()); + helpers.assertCursorAt(1, 2); +}, { value: '___' }); +testVim(']p_first_indent_is_smaller', function(cm, vim, helpers) { + helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true); + helpers.doKeys(']', 'p'); + eq(' ___\n abc\n def', cm.getValue()); +}, { value: ' ___' }); +testVim(']p_first_indent_is_larger', function(cm, vim, helpers) { + helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true); + helpers.doKeys(']', 'p'); + eq(' ___\n abc\ndef', cm.getValue()); +}, { value: ' ___' }); +testVim(']p_with_tab_indents', function(cm, vim, helpers) { + helpers.getRegisterController().pushText('"', 'yank', '\t\tabc\n\t\t\tdef\n', true); + helpers.doKeys(']', 'p'); + eq('\t___\n\tabc\n\t\tdef', cm.getValue()); +}, { value: '\t___', indentWithTabs: true}); +testVim(']p_with_spaces_translated_to_tabs', function(cm, vim, helpers) { + helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true); + helpers.doKeys(']', 'p'); + eq('\t___\n\tabc\n\t\tdef', cm.getValue()); +}, { value: '\t___', indentWithTabs: true, tabSize: 2 }); +testVim('[p', function(cm, vim, helpers) { + helpers.getRegisterController().pushText('"', 'yank', ' abc\n def\n', true); + helpers.doKeys('[', 'p'); + eq(' abc\n def\n ___', cm.getValue()); +}, { value: ' ___' }); +testVim('P', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().pushText('"', 'yank', 'abc\ndef', false); + helpers.doKeys('P'); + eq('_abc\ndef__', cm.getValue()); + helpers.assertCursorAt(1, 3); +}, { value: '___' }); +testVim('P_line', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.getRegisterController().pushText('"', 'yank', ' a\nd\n', true); + helpers.doKeys('2', 'P'); + eq(' a\nd\n a\nd\n___', cm.getValue()); + helpers.assertCursorAt(0, 2); +}, { value: '___' }); +testVim('r', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('3', 'r', 'u'); + eq('wuuuet\nanother', cm.getValue(),'3r failed'); + helpers.assertCursorAt(0, 3); + cm.setCursor(0, 4); + helpers.doKeys('v', 'j', 'h', 'r', '<Space>'); + eq('wuuu \n her', cm.getValue(),'Replacing selection by space-characters failed'); + cm.setValue("ox"); + helpers.doKeys('r', '<C-c>'); + eq('ox', cm.getValue()); + helpers.doKeys('r', '<Del>'); + eq('ox', cm.getValue()); + helpers.doKeys('r', '<CR>'); + eq('\nx', cm.getValue()); +}, { value: 'wordet\nanother' }); +testVim('r_visual_block', function(cm, vim, helpers) { + cm.setCursor(2, 3); + helpers.doKeys('<C-v>', 'k', 'k', 'h', 'h', 'r', 'l'); + eq('1lll\n5lll\nalllefg', cm.getValue()); + helpers.doKeys('<C-v>', 'l', 'j', 'r', '<Space>'); + eq('1 l\n5 l\nalllefg', cm.getValue()); + cm.setCursor(2, 0); + helpers.doKeys('o'); + helpers.doKeys('<Esc>'); + cm.replaceRange('\t\t', cm.getCursor()); + helpers.doKeys('<C-v>', 'h', 'h', 'r', 'r'); + eq('1 l\n5 l\nalllefg\nrrrrrrrr', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); +testVim('R', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('R'); + helpers.assertCursorAt(0, 1); + eq('vim-replace', cm.getOption('keyMap')); + is(cm.state.overwrite, 'Setting overwrite state failed'); +}); +testVim('mark', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 't'); + cm.setCursor(0, 0); + helpers.doKeys('`', 't'); + helpers.assertCursorAt(2, 2); + cm.setCursor(2, 0); + cm.replaceRange(' h', cm.getCursor()); + cm.setCursor(0, 0); + helpers.doKeys('\'', 't'); + helpers.assertCursorAt(2, 3); +}); +testVim('mark\'', function(cm, vim, helpers) { + cm.setCursor(2, 2); + cm.setCursor(0, 0); + helpers.doKeys('`', '\''); + helpers.assertCursorAt(2, 2); + cm.setCursor(2, 0); + cm.replaceRange(' h', cm.getCursor()); + cm.setCursor(0, 0); + helpers.doKeys('\'', '\''); + helpers.assertCursorAt(2, 3); +}); +testVim('mark.', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('O', 'testing', '<Esc>'); + cm.setCursor(3, 3); + helpers.doKeys('\'', '.'); + helpers.assertCursorAt(0, 0); + cm.setCursor(4, 4); + helpers.doKeys('`', '.'); + helpers.assertCursorAt(0, 6); +}); +testVim('jumpToMark_next', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 't'); + cm.setCursor(0, 0); + helpers.doKeys(']', '`'); + helpers.assertCursorAt(2, 2); + cm.setCursor(0, 0); + helpers.doKeys(']', '\''); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_next_repeat', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(0, 0); + helpers.doKeys('2', ']', '`'); + helpers.assertCursorAt(3, 2); + cm.setCursor(0, 0); + helpers.doKeys('2', ']', '\''); + helpers.assertCursorAt(3, 1); +}); +testVim('jumpToMark_next_sameline', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 4); + helpers.doKeys('m', 'b'); + cm.setCursor(2, 2); + helpers.doKeys(']', '`'); + helpers.assertCursorAt(2, 4); +}); +testVim('jumpToMark_next_onlyprev', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('m', 'a'); + cm.setCursor(4, 0); + helpers.doKeys(']', '`'); + helpers.assertCursorAt(4, 0); +}); +testVim('jumpToMark_next_nomark', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys(']', '`'); + helpers.assertCursorAt(2, 2); + helpers.doKeys(']', '\''); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_next_linewise_over', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(3, 4); + helpers.doKeys('m', 'b'); + cm.setCursor(2, 1); + helpers.doKeys(']', '\''); + helpers.assertCursorAt(3, 1); +}); +testVim('jumpToMark_next_action', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 't'); + cm.setCursor(0, 0); + helpers.doKeys('d', ']', '`'); + helpers.assertCursorAt(0, 0); + var actual = cm.getLine(0); + var expected = 'pop pop 0 1 2 3 4'; + eq(actual, expected, "Deleting while jumping to the next mark failed."); +}); +testVim('jumpToMark_next_line_action', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 't'); + cm.setCursor(0, 0); + helpers.doKeys('d', ']', '\''); + helpers.assertCursorAt(0, 1); + var actual = cm.getLine(0); + var expected = ' (a) [b] {c} ' + eq(actual, expected, "Deleting while jumping to the next mark line failed."); +}); +testVim('jumpToMark_prev', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 't'); + cm.setCursor(4, 0); + helpers.doKeys('[', '`'); + helpers.assertCursorAt(2, 2); + cm.setCursor(4, 0); + helpers.doKeys('[', '\''); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_prev_repeat', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(5, 0); + helpers.doKeys('2', '[', '`'); + helpers.assertCursorAt(3, 2); + cm.setCursor(5, 0); + helpers.doKeys('2', '[', '\''); + helpers.assertCursorAt(3, 1); +}); +testVim('jumpToMark_prev_sameline', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 4); + helpers.doKeys('m', 'b'); + cm.setCursor(2, 2); + helpers.doKeys('[', '`'); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_prev_onlynext', function(cm, vim, helpers) { + cm.setCursor(4, 4); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 0); + helpers.doKeys('[', '`'); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_prev_nomark', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('[', '`'); + helpers.assertCursorAt(2, 2); + helpers.doKeys('[', '\''); + helpers.assertCursorAt(2, 0); +}); +testVim('jumpToMark_prev_linewise_over', function(cm, vim, helpers) { + cm.setCursor(2, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(3, 4); + helpers.doKeys('m', 'b'); + cm.setCursor(3, 6); + helpers.doKeys('[', '\''); + helpers.assertCursorAt(2, 0); +}); +testVim('delmark_single', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('m', 't'); + helpers.doEx('delmarks t'); + cm.setCursor(0, 0); + helpers.doKeys('`', 't'); + helpers.assertCursorAt(0, 0); +}); +testVim('delmark_range', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'd'); + cm.setCursor(5, 2); + helpers.doKeys('m', 'e'); + helpers.doEx('delmarks b-d'); + cm.setCursor(0, 0); + helpers.doKeys('`', 'a'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'b'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'c'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'd'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'e'); + helpers.assertCursorAt(5, 2); +}); +testVim('delmark_multi', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'd'); + cm.setCursor(5, 2); + helpers.doKeys('m', 'e'); + helpers.doEx('delmarks bcd'); + cm.setCursor(0, 0); + helpers.doKeys('`', 'a'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'b'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'c'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'd'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'e'); + helpers.assertCursorAt(5, 2); +}); +testVim('delmark_multi_space', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'd'); + cm.setCursor(5, 2); + helpers.doKeys('m', 'e'); + helpers.doEx('delmarks b c d'); + cm.setCursor(0, 0); + helpers.doKeys('`', 'a'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'b'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'c'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'd'); + helpers.assertCursorAt(1, 2); + helpers.doKeys('`', 'e'); + helpers.assertCursorAt(5, 2); +}); +testVim('delmark_all', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('m', 'a'); + cm.setCursor(2, 2); + helpers.doKeys('m', 'b'); + cm.setCursor(3, 2); + helpers.doKeys('m', 'c'); + cm.setCursor(4, 2); + helpers.doKeys('m', 'd'); + cm.setCursor(5, 2); + helpers.doKeys('m', 'e'); + helpers.doEx('delmarks a b-de'); + cm.setCursor(0, 0); + helpers.doKeys('`', 'a'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('`', 'b'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('`', 'c'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('`', 'd'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('`', 'e'); + helpers.assertCursorAt(0, 0); +}); +testVim('visual', function(cm, vim, helpers) { + helpers.doKeys('l', 'v', 'l', 'l'); + helpers.assertCursorAt(0, 4); + eqCursorPos(makeCursor(0, 1), cm.getCursor('anchor')); + helpers.doKeys('d'); + eq('15', cm.getValue()); +}, { value: '12345' }); +testVim('visual_yank', function(cm, vim, helpers) { + helpers.doKeys('v', '3', 'l', 'y'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('p'); + eq('aa te test for yank', cm.getValue()); +}, { value: 'a test for yank' }) +testVim('visual_w', function(cm, vim, helpers) { + helpers.doKeys('v', 'w'); + eq(cm.getSelection(), 'motion t'); +}, { value: 'motion test'}); +testVim('visual_initial_selection', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('v'); + cm.getSelection('n'); +}, { value: 'init'}); +testVim('visual_crossover_left', function(cm, vim, helpers) { + cm.setCursor(0, 2); + helpers.doKeys('v', 'l', 'h', 'h'); + cm.getSelection('ro'); +}, { value: 'cross'}); +testVim('visual_crossover_left', function(cm, vim, helpers) { + cm.setCursor(0, 2); + helpers.doKeys('v', 'h', 'l', 'l'); + cm.getSelection('os'); +}, { value: 'cross'}); +testVim('visual_crossover_up', function(cm, vim, helpers) { + cm.setCursor(3, 2); + helpers.doKeys('v', 'j', 'k', 'k'); + eqCursorPos(Pos(2, 2), cm.getCursor('head')); + eqCursorPos(Pos(3, 3), cm.getCursor('anchor')); + helpers.doKeys('k'); + eqCursorPos(Pos(1, 2), cm.getCursor('head')); + eqCursorPos(Pos(3, 3), cm.getCursor('anchor')); +}, { value: 'cross\ncross\ncross\ncross\ncross\n'}); +testVim('visual_crossover_down', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('v', 'k', 'j', 'j'); + eqCursorPos(Pos(2, 3), cm.getCursor('head')); + eqCursorPos(Pos(1, 2), cm.getCursor('anchor')); + helpers.doKeys('j'); + eqCursorPos(Pos(3, 3), cm.getCursor('head')); + eqCursorPos(Pos(1, 2), cm.getCursor('anchor')); +}, { value: 'cross\ncross\ncross\ncross\ncross\n'}); +testVim('visual_exit', function(cm, vim, helpers) { + helpers.doKeys('<C-v>', 'l', 'j', 'j', '<Esc>'); + eqCursorPos(cm.getCursor('anchor'), cm.getCursor('head')); + eq(vim.visualMode, false); +}, { value: 'hello\nworld\nfoo' }); +testVim('visual_line', function(cm, vim, helpers) { + helpers.doKeys('l', 'V', 'l', 'j', 'j', 'd'); + eq(' 4\n 5', cm.getValue()); +}, { value: ' 1\n 2\n 3\n 4\n 5' }); +testVim('visual_block_move_to_eol', function(cm, vim, helpers) { + // moveToEol should move all block cursors to end of line + cm.setCursor(0, 0); + helpers.doKeys('<C-v>', 'G', '$'); + var selections = cm.getSelections().join(); + eq('123,45,6', selections); + // Checks that with cursor at Infinity, finding words backwards still works. + helpers.doKeys('2', 'k', 'b'); + selections = cm.getSelections().join(); + eq('1', selections); +}, {value: '123\n45\n6'}); +testVim('visual_block_different_line_lengths', function(cm, vim, helpers) { + // test the block selection with lines of different length + // i.e. extending the selection + // till the end of the longest line. + helpers.doKeys('<C-v>', 'l', 'j', 'j', '6', 'l', 'd'); + helpers.doKeys('d', 'd', 'd', 'd'); + eq('', cm.getValue()); +}, {value: '1234\n5678\nabcdefg'}); +testVim('visual_block_truncate_on_short_line', function(cm, vim, helpers) { + // check for left side selection in case + // of moving up to a shorter line. + cm.replaceRange('', cm.getCursor()); + cm.setCursor(3, 4); + helpers.doKeys('<C-v>', 'l', 'k', 'k', 'd'); + eq('hello world\n{\ntis\nsa!', cm.getValue()); +}, {value: 'hello world\n{\nthis is\nsparta!'}); +testVim('visual_block_corners', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('<C-v>', '2', 'l', 'k'); + // circle around the anchor + // and check the selections + var selections = cm.getSelections(); + eq('345891', selections.join('')); + helpers.doKeys('4', 'h'); + selections = cm.getSelections(); + eq('123678', selections.join('')); + helpers.doKeys('j', 'j'); + selections = cm.getSelections(); + eq('678abc', selections.join('')); + helpers.doKeys('4', 'l'); + selections = cm.getSelections(); + eq('891cde', selections.join('')); +}, {value: '12345\n67891\nabcde'}); +testVim('visual_block_mode_switch', function(cm, vim, helpers) { + // switch between visual modes + cm.setCursor(1, 1); + // blockwise to characterwise visual + helpers.doKeys('<C-v>', 'j', 'l', 'v'); + var selections = cm.getSelections(); + eq('7891\nabc', selections.join('')); + // characterwise to blockwise + helpers.doKeys('<C-v>'); + selections = cm.getSelections(); + eq('78bc', selections.join('')); + // blockwise to linewise visual + helpers.doKeys('V'); + selections = cm.getSelections(); + eq('67891\nabcde', selections.join('')); +}, {value: '12345\n67891\nabcde'}); +testVim('visual_block_crossing_short_line', function(cm, vim, helpers) { + // visual block with long and short lines + cm.setCursor(0, 3); + helpers.doKeys('<C-v>', 'j', 'j', 'j'); + var selections = cm.getSelections().join(); + eq('4,,d,b', selections); + helpers.doKeys('3', 'k'); + selections = cm.getSelections().join(); + eq('4', selections); + helpers.doKeys('5', 'j', 'k'); + selections = cm.getSelections().join(""); + eq(10, selections.length); +}, {value: '123456\n78\nabcdefg\nfoobar\n}\n'}); +testVim('visual_block_curPos_on_exit', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('<C-v>', '3' , 'l', '<Esc>'); + eqCursorPos(makeCursor(0, 3), cm.getCursor()); + helpers.doKeys('h', '<C-v>', '2' , 'j' ,'3' , 'l'); + eq(cm.getSelections().join(), "3456,,cdef"); + helpers.doKeys('4' , 'h'); + eq(cm.getSelections().join(), "23,8,bc"); + helpers.doKeys('2' , 'l'); + eq(cm.getSelections().join(), "34,,cd"); +}, {value: '123456\n78\nabcdefg\nfoobar'}); + +testVim('visual_marks', function(cm, vim, helpers) { + helpers.doKeys('l', 'v', 'l', 'l', 'j', 'j', 'v'); + // Test visual mode marks + cm.setCursor(2, 1); + helpers.doKeys('\'', '<'); + helpers.assertCursorAt(0, 1); + helpers.doKeys('\'', '>'); + helpers.assertCursorAt(2, 0); +}); +testVim('visual_join', function(cm, vim, helpers) { + helpers.doKeys('l', 'V', 'l', 'j', 'j', 'J'); + eq(' 1 2 3\n 4\n 5', cm.getValue()); + is(!vim.visualMode); +}, { value: ' 1\n 2\n 3\n 4\n 5' }); +testVim('visual_join_2', function(cm, vim, helpers) { + helpers.doKeys('G', 'V', 'g', 'g', 'J'); + eq('1 2 3 4 5 6 ', cm.getValue()); + is(!vim.visualMode); +}, { value: '1\n2\n3\n4\n5\n6\n'}); +testVim('visual_blank', function(cm, vim, helpers) { + helpers.doKeys('v', 'k'); + eq(vim.visualMode, true); +}, { value: '\n' }); +testVim('reselect_visual', function(cm, vim, helpers) { + helpers.doKeys('l', 'v', 'l', 'l', 'l', 'y', 'g', 'v'); + helpers.assertCursorAt(0, 5); + eqCursorPos(makeCursor(0, 1), cm.getCursor('anchor')); + helpers.doKeys('v'); + cm.setCursor(1, 0); + helpers.doKeys('v', 'l', 'l', 'p'); + eq('123456\n2345\nbar', cm.getValue()); + cm.setCursor(0, 0); + helpers.doKeys('g', 'v'); + // here the fake cursor is at (1, 3) + helpers.assertCursorAt(1, 4); + eqCursorPos(makeCursor(1, 0), cm.getCursor('anchor')); + helpers.doKeys('v'); + cm.setCursor(2, 0); + helpers.doKeys('v', 'l', 'l', 'g', 'v'); + helpers.assertCursorAt(1, 4); + eqCursorPos(makeCursor(1, 0), cm.getCursor('anchor')); + helpers.doKeys('g', 'v'); + helpers.assertCursorAt(2, 3); + eqCursorPos(makeCursor(2, 0), cm.getCursor('anchor')); + eq('123456\n2345\nbar', cm.getValue()); +}, { value: '123456\nfoo\nbar' }); +testVim('reselect_visual_line', function(cm, vim, helpers) { + helpers.doKeys('l', 'V', 'j', 'j', 'V', 'g', 'v', 'd'); + eq('foo\nand\nbar', cm.getValue()); + cm.setCursor(1, 0); + helpers.doKeys('V', 'y', 'j'); + helpers.doKeys('V', 'p' , 'g', 'v', 'd'); + eq('foo\nand', cm.getValue()); +}, { value: 'hello\nthis\nis\nfoo\nand\nbar' }); +testVim('reselect_visual_block', function(cm, vim, helpers) { + cm.setCursor(1, 2); + helpers.doKeys('<C-v>', 'k', 'h', '<C-v>'); + cm.setCursor(2, 1); + helpers.doKeys('v', 'l', 'g', 'v'); + eqCursorPos(Pos(1, 2), vim.sel.anchor); + eqCursorPos(Pos(0, 1), vim.sel.head); + // Ensure selection is done with visual block mode rather than one + // continuous range. + eq(cm.getSelections().join(''), '23oo') + helpers.doKeys('g', 'v'); + eqCursorPos(Pos(2, 1), vim.sel.anchor); + eqCursorPos(Pos(2, 2), vim.sel.head); + helpers.doKeys('<Esc>'); + // Ensure selection of deleted range + cm.setCursor(1, 1); + helpers.doKeys('v', '<C-v>', 'j', 'd', 'g', 'v'); + eq(cm.getSelections().join(''), 'or'); +}, { value: '123456\nfoo\nbar' }); +testVim('s_normal', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('s'); + helpers.doKeys('<Esc>'); + eq('ac', cm.getValue()); +}, { value: 'abc'}); +testVim('s_visual', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('v', 's'); + helpers.doKeys('<Esc>'); + helpers.assertCursorAt(0, 0); + eq('ac', cm.getValue()); +}, { value: 'abc'}); +testVim('o_visual', function(cm, vim, helpers) { + cm.setCursor(0,0); + helpers.doKeys('v','l','l','l','o'); + helpers.assertCursorAt(0,0); + helpers.doKeys('v','v','j','j','j','o'); + helpers.assertCursorAt(0,0); + helpers.doKeys('O'); + helpers.doKeys('l','l') + helpers.assertCursorAt(3, 3); + helpers.doKeys('d'); + eq('p',cm.getValue()); +}, { value: 'abcd\nefgh\nijkl\nmnop'}); +testVim('o_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('<C-v>','3','j','l','l', 'o'); + eqCursorPos(Pos(3, 3), vim.sel.anchor); + eqCursorPos(Pos(0, 1), vim.sel.head); + helpers.doKeys('O'); + eqCursorPos(Pos(3, 1), vim.sel.anchor); + eqCursorPos(Pos(0, 3), vim.sel.head); + helpers.doKeys('o'); + eqCursorPos(Pos(0, 3), vim.sel.anchor); + eqCursorPos(Pos(3, 1), vim.sel.head); +}, { value: 'abcd\nefgh\nijkl\nmnop'}); +testVim('changeCase_visual', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('v', 'l', 'l'); + helpers.doKeys('U'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('v', 'l', 'l'); + helpers.doKeys('u'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('l', 'l', 'l', '.'); + helpers.assertCursorAt(0, 3); + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', 'v', 'j', 'U', 'q'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('j', '@', 'a'); + helpers.assertCursorAt(1, 0); + cm.setCursor(3, 0); + helpers.doKeys('V', 'U', 'j', '.'); + eq('ABCDEF\nGHIJKL\nMnopq\nSHORT LINE\nLONG LINE OF TEXT', cm.getValue()); +}, { value: 'abcdef\nghijkl\nmnopq\nshort line\nlong line of text'}); +testVim('changeCase_visual_block', function(cm, vim, helpers) { + cm.setCursor(2, 1); + helpers.doKeys('<C-v>', 'k', 'k', 'h', 'U'); + eq('ABcdef\nGHijkl\nMNopq\nfoo', cm.getValue()); + cm.setCursor(0, 2); + helpers.doKeys('.'); + eq('ABCDef\nGHIJkl\nMNOPq\nfoo', cm.getValue()); + // check when last line is shorter. + cm.setCursor(2, 2); + helpers.doKeys('.'); + eq('ABCDef\nGHIJkl\nMNOPq\nfoO', cm.getValue()); +}, { value: 'abcdef\nghijkl\nmnopq\nfoo'}); +testVim('visual_paste', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('v', 'l', 'l', 'y'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('3', 'l', 'j', 'v', 'l', 'p'); + helpers.assertCursorAt(1, 5); + eq('this is a\nunithitest for visual paste', cm.getValue()); + cm.setCursor(0, 0); + // in case of pasting whole line + helpers.doKeys('y', 'y'); + cm.setCursor(1, 6); + helpers.doKeys('v', 'l', 'l', 'l', 'p'); + helpers.assertCursorAt(2, 0); + eq('this is a\nunithi\nthis is a\n for visual paste', cm.getValue()); +}, { value: 'this is a\nunit test for visual paste'}); + +// This checks the contents of the register used to paste the text +testVim('v_paste_from_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'w'); + cm.setCursor(1, 0); + helpers.doKeys('v', 'p'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+register/.test(text)); + }); +}, { value: 'register contents\nare not erased'}); +testVim('S_normal', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('j', 'S'); + helpers.doKeys('<Esc>'); + helpers.assertCursorAt(1, 1); + eq('aa{\n \ncc', cm.getValue()); + helpers.doKeys('j', 'S'); + eq('aa{\n \n ', cm.getValue()); + helpers.assertCursorAt(2, 2); + helpers.doKeys('<Esc>'); + helpers.doKeys('d', 'd', 'd', 'd'); + helpers.assertCursorAt(0, 0); + helpers.doKeys('S'); + is(vim.insertMode); + eq('', cm.getValue()); +}, { value: 'aa{\nbb\ncc'}); +testVim('blockwise_paste', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('<C-v>', '3', 'j', 'l', 'y'); + cm.setCursor(0, 2); + // paste one char after the current cursor position + helpers.doKeys('p'); + eq('helhelo\nworwold\nfoofo\nbarba', cm.getValue()); + cm.setCursor(0, 0); + helpers.doKeys('v', '4', 'l', 'y'); + cm.setCursor(0, 0); + helpers.doKeys('<C-v>', '3', 'j', 'p'); + eq('helheelhelo\norwold\noofo\narba', cm.getValue()); +}, { value: 'hello\nworld\nfoo\nbar'}); +testVim('blockwise_paste_long/short_line', function(cm, vim, helpers) { + // extend short lines in case of different line lengths. + cm.setCursor(0, 0); + helpers.doKeys('<C-v>', 'j', 'j', 'y'); + cm.setCursor(0, 3); + helpers.doKeys('p'); + eq('hellho\nfoo f\nbar b', cm.getValue()); +}, { value: 'hello\nfoo\nbar'}); +testVim('blockwise_paste_cut_paste', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('<C-v>', '2', 'j', 'x'); + cm.setCursor(0, 0); + helpers.doKeys('P'); + eq('cut\nand\npaste\nme', cm.getValue()); +}, { value: 'cut\nand\npaste\nme'}); +testVim('blockwise_paste_from_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('<C-v>', '2', 'j', '"', 'a', 'y'); + cm.setCursor(0, 3); + helpers.doKeys('"', 'a', 'p'); + eq('foobfar\nhellho\nworlwd', cm.getValue()); +}, { value: 'foobar\nhello\nworld'}); +testVim('blockwise_paste_last_line', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('<C-v>', '2', 'j', 'l', 'y'); + cm.setCursor(3, 0); + helpers.doKeys('p'); + eq('cut\nand\npaste\nmcue\n an\n pa', cm.getValue()); +}, { value: 'cut\nand\npaste\nme'}); + +testVim('S_visual', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('v', 'j', 'S'); + helpers.doKeys('<Esc>'); + helpers.assertCursorAt(0, 0); + eq('\ncc', cm.getValue()); +}, { value: 'aa\nbb\ncc'}); + +testVim('d_/', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('2', 'd', '/'); + helpers.assertCursorAt(0, 0); + eq('match \n next', cm.getValue()); + cm.openDialog = helpers.fakeOpenDialog('2'); + helpers.doKeys('d', ':'); + // TODO eq(' next', cm.getValue()); +}, { value: 'text match match \n next' }); +testVim('/ and n/N', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('/'); + helpers.assertCursorAt(0, 11); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 6); + helpers.doKeys('N'); + helpers.assertCursorAt(0, 11); + + cm.setCursor(0, 0); + helpers.doKeys('2', '/'); + helpers.assertCursorAt(1, 6); +}, { value: 'match nope match \n nope Match' }); +testVim('/_case', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('Match'); + helpers.doKeys('/'); + helpers.assertCursorAt(1, 6); +}, { value: 'match nope match \n nope Match' }); +testVim('/_2_pcre', function(cm, vim, helpers) { + CodeMirror.Vim.setOption('pcre', true); + cm.openDialog = helpers.fakeOpenDialog('(word){2}'); + helpers.doKeys('/'); + helpers.assertCursorAt(1, 9); + helpers.doKeys('n'); + helpers.assertCursorAt(2, 1); +}, { value: 'word\n another wordword\n wordwordword\n' }); +testVim('/_2_nopcre', function(cm, vim, helpers) { + CodeMirror.Vim.setOption('pcre', false); + cm.openDialog = helpers.fakeOpenDialog('\\(word\\)\\{2}'); + helpers.doKeys('/'); + helpers.assertCursorAt(1, 9); + helpers.doKeys('n'); + helpers.assertCursorAt(2, 1); +}, { value: 'word\n another wordword\n wordwordword\n' }); +testVim('/_nongreedy', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('aa'); + helpers.doKeys('/'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa \n a aa'}); +testVim('?_nongreedy', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('aa'); + helpers.doKeys('?'); + helpers.assertCursorAt(1, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa \n a aa'}); +testVim('/_greedy', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('a+'); + helpers.doKeys('/'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 1); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa \n a aa'}); +testVim('?_greedy', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('a+'); + helpers.doKeys('?'); + helpers.assertCursorAt(1, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 1); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa \n a aa'}); +testVim('/_greedy_0_or_more', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('a*'); + helpers.doKeys('/'); + helpers.assertCursorAt(0, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 5); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 0); + helpers.doKeys('n'); + helpers.assertCursorAt(1, 1); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa\n aa'}); +testVim('?_greedy_0_or_more', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('a*'); + helpers.doKeys('?'); + helpers.assertCursorAt(1, 1); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 5); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 3); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 0); +}, { value: 'aaa aa\n aa'}); +testVim('? and n/N', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('?'); + helpers.assertCursorAt(1, 6); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 11); + helpers.doKeys('N'); + helpers.assertCursorAt(1, 6); + + cm.setCursor(0, 0); + helpers.doKeys('2', '?'); + helpers.assertCursorAt(0, 11); +}, { value: 'match nope match \n nope Match' }); +testVim('*', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('*'); + helpers.assertCursorAt(0, 22); + + cm.setCursor(0, 9); + helpers.doKeys('2', '*'); + helpers.assertCursorAt(1, 8); +}, { value: 'nomatch match nomatch match \nnomatch Match' }); +testVim('*_no_word', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('*'); + helpers.assertCursorAt(0, 0); +}, { value: ' \n match \n' }); +testVim('*_symbol', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('*'); + helpers.assertCursorAt(1, 0); +}, { value: ' /}\n/} match \n' }); +testVim('#', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('#'); + helpers.assertCursorAt(1, 8); + + cm.setCursor(0, 9); + helpers.doKeys('2', '#'); + helpers.assertCursorAt(0, 22); +}, { value: 'nomatch match nomatch match \nnomatch Match' }); +testVim('*_seek', function(cm, vim, helpers) { + // Should skip over space and symbols. + cm.setCursor(0, 3); + helpers.doKeys('*'); + helpers.assertCursorAt(0, 22); +}, { value: ' := match nomatch match \nnomatch Match' }); +testVim('#', function(cm, vim, helpers) { + // Should skip over space and symbols. + cm.setCursor(0, 3); + helpers.doKeys('#'); + helpers.assertCursorAt(1, 8); +}, { value: ' := match nomatch match \nnomatch Match' }); +testVim('g*', function(cm, vim, helpers) { + cm.setCursor(0, 8); + helpers.doKeys('g', '*'); + helpers.assertCursorAt(0, 18); + cm.setCursor(0, 8); + helpers.doKeys('3', 'g', '*'); + helpers.assertCursorAt(1, 8); +}, { value: 'matches match alsoMatch\nmatchme matching' }); +testVim('g#', function(cm, vim, helpers) { + cm.setCursor(0, 8); + helpers.doKeys('g', '#'); + helpers.assertCursorAt(0, 0); + cm.setCursor(0, 8); + helpers.doKeys('3', 'g', '#'); + helpers.assertCursorAt(1, 0); +}, { value: 'matches match alsoMatch\nmatchme matching' }); +testVim('macro_insert', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', '0', 'i'); + cm.replaceRange('foo', cm.getCursor()); + helpers.doKeys('<Esc>'); + helpers.doKeys('q', '@', 'a'); + eq('foofoo', cm.getValue()); +}, { value: ''}); +testVim('macro_insert_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', '$', 'a'); + cm.replaceRange('larry.', cm.getCursor()); + helpers.doKeys('<Esc>'); + helpers.doKeys('a'); + cm.replaceRange('curly.', cm.getCursor()); + helpers.doKeys('<Esc>'); + helpers.doKeys('q'); + helpers.doKeys('a'); + cm.replaceRange('moe.', cm.getCursor()); + helpers.doKeys('<Esc>'); + helpers.doKeys('@', 'a'); + // At this point, the most recent edit should be the 2nd insert change + // inside the macro, i.e. "curly.". + helpers.doKeys('.'); + eq('larry.curly.moe.larry.curly.curly.', cm.getValue()); +}, { value: ''}); +testVim('macro_space', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('<Space>', '<Space>'); + helpers.assertCursorAt(0, 2); + helpers.doKeys('q', 'a', '<Space>', '<Space>', 'q'); + helpers.assertCursorAt(0, 4); + helpers.doKeys('@', 'a'); + helpers.assertCursorAt(0, 6); + helpers.doKeys('@', 'a'); + helpers.assertCursorAt(0, 8); +}, { value: 'one line of text.'}); +testVim('macro_t_search', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', 't', 'e', 'q'); + helpers.assertCursorAt(0, 1); + helpers.doKeys('l', '@', 'a'); + helpers.assertCursorAt(0, 6); + helpers.doKeys('l', ';'); + helpers.assertCursorAt(0, 12); +}, { value: 'one line of text.'}); +testVim('macro_f_search', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'b', 'f', 'e', 'q'); + helpers.assertCursorAt(0, 2); + helpers.doKeys('@', 'b'); + helpers.assertCursorAt(0, 7); + helpers.doKeys(';'); + helpers.assertCursorAt(0, 13); +}, { value: 'one line of text.'}); +testVim('macro_slash_search', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'c'); + cm.openDialog = helpers.fakeOpenDialog('e'); + helpers.doKeys('/', 'q'); + helpers.assertCursorAt(0, 2); + helpers.doKeys('@', 'c'); + helpers.assertCursorAt(0, 7); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 13); +}, { value: 'one line of text.'}); +testVim('macro_multislash_search', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'd'); + cm.openDialog = helpers.fakeOpenDialog('e'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('t'); + helpers.doKeys('/', 'q'); + helpers.assertCursorAt(0, 12); + helpers.doKeys('@', 'd'); + helpers.assertCursorAt(0, 15); +}, { value: 'one line of text to rule them all.'}); +testVim('macro_last_ex_command_register', function (cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doEx('s/a/b'); + helpers.doKeys('2', '@', ':'); + eq('bbbaa', cm.getValue()); + helpers.assertCursorAt(0, 2); +}, { value: 'aaaaa'}); +testVim('macro_parens', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'z', 'i'); + cm.replaceRange('(', cm.getCursor()); + helpers.doKeys('<Esc>'); + helpers.doKeys('e', 'a'); + cm.replaceRange(')', cm.getCursor()); + helpers.doKeys('<Esc>'); + helpers.doKeys('q'); + helpers.doKeys('w', '@', 'z'); + helpers.doKeys('w', '@', 'z'); + eq('(see) (spot) (run)', cm.getValue()); +}, { value: 'see spot run'}); +testVim('macro_overwrite', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'z', '0', 'i'); + cm.replaceRange('I ', cm.getCursor()); + helpers.doKeys('<Esc>'); + helpers.doKeys('q'); + helpers.doKeys('e'); + // Now replace the macro with something else. + helpers.doKeys('q', 'z', 'a'); + cm.replaceRange('.', cm.getCursor()); + helpers.doKeys('<Esc>'); + helpers.doKeys('q'); + helpers.doKeys('e', '@', 'z'); + helpers.doKeys('e', '@', 'z'); + eq('I see. spot. run.', cm.getValue()); +}, { value: 'see spot run'}); +testVim('macro_search_f', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', 'f', ' '); + helpers.assertCursorAt(0,3); + helpers.doKeys('q', '0'); + helpers.assertCursorAt(0,0); + helpers.doKeys('@', 'a'); + helpers.assertCursorAt(0,3); +}, { value: 'The quick brown fox jumped over the lazy dog.'}); +testVim('macro_search_2f', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', '2', 'f', ' '); + helpers.assertCursorAt(0,9); + helpers.doKeys('q', '0'); + helpers.assertCursorAt(0,0); + helpers.doKeys('@', 'a'); + helpers.assertCursorAt(0,9); +}, { value: 'The quick brown fox jumped over the lazy dog.'}); +testVim('macro_yank_tick', function(cm, vim, helpers) { + cm.setCursor(0, 0); + // Start recording a macro into the \' register. + helpers.doKeys('q', '\''); + helpers.doKeys('y', '<Right>', '<Right>', '<Right>', '<Right>', 'p'); + helpers.assertCursorAt(0,4); + eq('the tex parrot', cm.getValue()); +}, { value: 'the ex parrot'}); +testVim('yank_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'y'); + helpers.doKeys('j', '"', 'b', 'y', 'y'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+foo/.test(text)); + is(/b\s+bar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('yank_visual_block', function(cm, vim, helpers) { + cm.setCursor(0, 1); + helpers.doKeys('<C-v>', 'l', 'j', '"', 'a', 'y'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+oo\nar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('yank_append_line_to_line_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'y'); + helpers.doKeys('j', '"', 'A', 'y', 'y'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+foo\nbar/.test(text)); + is(/"\s+foo\nbar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('yank_append_word_to_word_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'w'); + helpers.doKeys('j', '"', 'A', 'y', 'w'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+foobar/.test(text)); + is(/"\s+foobar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('yank_append_line_to_word_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'w'); + helpers.doKeys('j', '"', 'A', 'y', 'y'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+foo\nbar/.test(text)); + is(/"\s+foo\nbar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('yank_append_word_to_line_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('"', 'a', 'y', 'y'); + helpers.doKeys('j', '"', 'A', 'y', 'w'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+foo\nbar/.test(text)); + is(/"\s+foo\nbar/.test(text)); + }); + helpers.doKeys(':'); +}, { value: 'foo\nbar'}); +testVim('macro_register', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('q', 'a', 'i'); + cm.replaceRange('gangnam', cm.getCursor()); + helpers.doKeys('<Esc>'); + helpers.doKeys('q'); + helpers.doKeys('q', 'b', 'o'); + cm.replaceRange('style', cm.getCursor()); + helpers.doKeys('<Esc>'); + helpers.doKeys('q'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/a\s+i/.test(text)); + is(/b\s+o/.test(text)); + }); + helpers.doKeys(':'); +}, { value: ''}); +testVim('._register', function(cm,vim,helpers) { + cm.setCursor(0,0); + helpers.doKeys('i'); + cm.replaceRange('foo',cm.getCursor()); + helpers.doKeys('<Esc>'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/\.\s+foo/.test(text)); + }); + helpers.doKeys(':'); +}, {value: ''}); +testVim(':_register', function(cm,vim,helpers) { + helpers.doEx('bar'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/:\s+bar/.test(text)); + }); + helpers.doKeys(':'); +}, {value: ''}); +testVim('search_register_escape', function(cm, vim, helpers) { + // Check that the register is restored if the user escapes rather than confirms. + cm.openDialog = helpers.fakeOpenDialog('waldo'); + helpers.doKeys('/'); + var onKeyDown; + var onKeyUp; + var KEYCODES = { + f: 70, + o: 79, + Esc: 27 + }; + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + onKeyUp = options.onKeyUp; + }; + var close = function() {}; + helpers.doKeys('/'); + // Fake some keyboard events coming in. + onKeyDown({keyCode: KEYCODES.f}, '', close); + onKeyUp({keyCode: KEYCODES.f}, '', close); + onKeyDown({keyCode: KEYCODES.o}, 'f', close); + onKeyUp({keyCode: KEYCODES.o}, 'f', close); + onKeyDown({keyCode: KEYCODES.o}, 'fo', close); + onKeyUp({keyCode: KEYCODES.o}, 'fo', close); + onKeyDown({keyCode: KEYCODES.Esc}, 'foo', close); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/waldo/.test(text)); + is(!/foo/.test(text)); + }); + helpers.doKeys(':'); +}, {value: ''}); +testVim('search_register', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('foo'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('registers'); + cm.openNotification = helpers.fakeOpenNotification(function(text) { + is(/\/\s+foo/.test(text)); + }); + helpers.doKeys(':'); +}, {value: ''}); +testVim('search_history', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('this'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('checks'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('search'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('history'); + helpers.doKeys('/'); + cm.openDialog = helpers.fakeOpenDialog('checks'); + helpers.doKeys('/'); + var onKeyDown; + var onKeyUp; + var query = ''; + var keyCodes = { + Up: 38, + Down: 40 + }; + cm.openDialog = function(template, callback, options) { + onKeyUp = options.onKeyUp; + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') query = newVal; + } + helpers.doKeys('/'); + onKeyDown({keyCode: keyCodes.Up}, query, close); + onKeyUp({keyCode: keyCodes.Up}, query, close); + eq(query, 'checks'); + onKeyDown({keyCode: keyCodes.Up}, query, close); + onKeyUp({keyCode: keyCodes.Up}, query, close); + eq(query, 'history'); + onKeyDown({keyCode: keyCodes.Up}, query, close); + onKeyUp({keyCode: keyCodes.Up}, query, close); + eq(query, 'search'); + onKeyDown({keyCode: keyCodes.Up}, query, close); + onKeyUp({keyCode: keyCodes.Up}, query, close); + eq(query, 'this'); + onKeyDown({keyCode: keyCodes.Down}, query, close); + onKeyUp({keyCode: keyCodes.Down}, query, close); + eq(query, 'search'); +}, {value: ''}); +testVim('exCommand_history', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('registers'); + helpers.doKeys(':'); + cm.openDialog = helpers.fakeOpenDialog('sort'); + helpers.doKeys(':'); + cm.openDialog = helpers.fakeOpenDialog('map'); + helpers.doKeys(':'); + cm.openDialog = helpers.fakeOpenDialog('invalid'); + helpers.doKeys(':'); + var onKeyDown; + var onKeyUp; + var input = ''; + var keyCodes = { + Up: 38, + Down: 40, + s: 115 + }; + cm.openDialog = function(template, callback, options) { + onKeyUp = options.onKeyUp; + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') input = newVal; + } + helpers.doKeys(':'); + onKeyDown({keyCode: keyCodes.Up}, input, close); + eq(input, 'invalid'); + onKeyDown({keyCode: keyCodes.Up}, input, close); + eq(input, 'map'); + onKeyDown({keyCode: keyCodes.Up}, input, close); + eq(input, 'sort'); + onKeyDown({keyCode: keyCodes.Up}, input, close); + eq(input, 'registers'); + onKeyDown({keyCode: keyCodes.s}, '', close); + input = 's'; + onKeyDown({keyCode: keyCodes.Up}, input, close); + eq(input, 'sort'); +}, {value: ''}); +testVim('search_clear', function(cm, vim, helpers) { + var onKeyDown; + var input = ''; + var keyCodes = { + Ctrl: 17, + u: 85 + }; + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') input = newVal; + } + helpers.doKeys('/'); + input = 'foo'; + onKeyDown({keyCode: keyCodes.Ctrl}, input, close); + onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close); + eq(input, ''); +}); +testVim('exCommand_clear', function(cm, vim, helpers) { + var onKeyDown; + var input = ''; + var keyCodes = { + Ctrl: 17, + u: 85 + }; + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + }; + var close = function(newVal) { + if (typeof newVal == 'string') input = newVal; + } + helpers.doKeys(':'); + input = 'foo'; + onKeyDown({keyCode: keyCodes.Ctrl}, input, close); + onKeyDown({keyCode: keyCodes.u, ctrlKey: true}, input, close); + eq(input, ''); +}); +testVim('.', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('2', 'd', 'w'); + helpers.doKeys('.'); + eq('5 6', cm.getValue()); +}, { value: '1 2 3 4 5 6'}); +testVim('._repeat', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('2', 'd', 'w'); + helpers.doKeys('3', '.'); + eq('6', cm.getValue()); +}, { value: '1 2 3 4 5 6'}); +testVim('._insert', function(cm, vim, helpers) { + helpers.doKeys('i'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('<Esc>'); + helpers.doKeys('.'); + eq('testestt', cm.getValue()); + helpers.assertCursorAt(0, 6); + helpers.doKeys('O'); + cm.replaceRange('xyz', cm.getCursor()); + helpers.doInsertModeKeys('Backspace'); + helpers.doInsertModeKeys('Down'); + helpers.doKeys('<Esc>'); + helpers.doKeys('.'); + eq('xy\nxy\ntestestt', cm.getValue()); + helpers.assertCursorAt(1, 1); +}, { value: ''}); +testVim('._insert_repeat', function(cm, vim, helpers) { + helpers.doKeys('i'); + cm.replaceRange('test', cm.getCursor()); + cm.setCursor(0, 4); + helpers.doKeys('<Esc>'); + helpers.doKeys('2', '.'); + eq('testesttestt', cm.getValue()); + helpers.assertCursorAt(0, 10); +}, { value: ''}); +testVim('._repeat_insert', function(cm, vim, helpers) { + helpers.doKeys('3', 'i'); + cm.replaceRange('te', cm.getCursor()); + cm.setCursor(0, 2); + helpers.doKeys('<Esc>'); + helpers.doKeys('.'); + eq('tetettetetee', cm.getValue()); + helpers.assertCursorAt(0, 10); +}, { value: ''}); +testVim('._insert_o', function(cm, vim, helpers) { + helpers.doKeys('o'); + cm.replaceRange('z', cm.getCursor()); + cm.setCursor(1, 1); + helpers.doKeys('<Esc>'); + helpers.doKeys('.'); + eq('\nz\nz', cm.getValue()); + helpers.assertCursorAt(2, 0); +}, { value: ''}); +testVim('._insert_o_repeat', function(cm, vim, helpers) { + helpers.doKeys('o'); + cm.replaceRange('z', cm.getCursor()); + helpers.doKeys('<Esc>'); + cm.setCursor(1, 0); + helpers.doKeys('2', '.'); + eq('\nz\nz\nz', cm.getValue()); + helpers.assertCursorAt(3, 0); +}, { value: ''}); +testVim('._insert_o_indent', function(cm, vim, helpers) { + helpers.doKeys('o'); + cm.replaceRange('z', cm.getCursor()); + helpers.doKeys('<Esc>'); + cm.setCursor(1, 2); + helpers.doKeys('.'); + eq('{\n z\n z', cm.getValue()); + helpers.assertCursorAt(2, 2); +}, { value: '{'}); +testVim('._insert_cw', function(cm, vim, helpers) { + helpers.doKeys('c', 'w'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('<Esc>'); + cm.setCursor(0, 3); + helpers.doKeys('2', 'l'); + helpers.doKeys('.'); + eq('test test word3', cm.getValue()); + helpers.assertCursorAt(0, 8); +}, { value: 'word1 word2 word3' }); +testVim('._insert_cw_repeat', function(cm, vim, helpers) { + // For some reason, repeat cw in desktop VIM will does not repeat insert mode + // changes. Will conform to that behavior. + helpers.doKeys('c', 'w'); + cm.replaceRange('test', cm.getCursor()); + helpers.doKeys('<Esc>'); + cm.setCursor(0, 4); + helpers.doKeys('l'); + helpers.doKeys('2', '.'); + eq('test test', cm.getValue()); + helpers.assertCursorAt(0, 8); +}, { value: 'word1 word2 word3' }); +testVim('._delete', function(cm, vim, helpers) { + cm.setCursor(0, 5); + helpers.doKeys('i'); + helpers.doInsertModeKeys('Backspace'); + helpers.doKeys('<Esc>'); + helpers.doKeys('.'); + eq('zace', cm.getValue()); + helpers.assertCursorAt(0, 1); +}, { value: 'zabcde'}); +testVim('._delete_repeat', function(cm, vim, helpers) { + cm.setCursor(0, 6); + helpers.doKeys('i'); + helpers.doInsertModeKeys('Backspace'); + helpers.doKeys('<Esc>'); + helpers.doKeys('2', '.'); + eq('zzce', cm.getValue()); + helpers.assertCursorAt(0, 1); +}, { value: 'zzabcde'}); +testVim('._visual_>', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('V', 'j', '>'); + cm.setCursor(2, 0) + helpers.doKeys('.'); + eq(' 1\n 2\n 3\n 4', cm.getValue()); + helpers.assertCursorAt(2, 2); +}, { value: '1\n2\n3\n4'}); +testVim('._replace_repeat', function(cm, vim, helpers) { + helpers.doKeys('R'); + cm.replaceRange('123', cm.getCursor(), offsetCursor(cm.getCursor(), 0, 3)); + cm.setCursor(0, 3); + helpers.doKeys('<Esc>'); + helpers.doKeys('2', '.'); + eq('12123123\nabcdefg', cm.getValue()); + helpers.assertCursorAt(0, 7); + cm.setCursor(1, 0); + helpers.doKeys('.'); + eq('12123123\n123123g', cm.getValue()); + helpers.doKeys('l', '"', '.', 'p'); + eq('12123123\n123123g123', cm.getValue()); +}, { value: 'abcdef\nabcdefg'}); +testVim('f;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('f', 'x'); + helpers.doKeys(';'); + helpers.doKeys('2', ';'); + eq(9, cm.getCursor().ch); +}, { value: '01x3xx678x'}); +testVim('F;', function(cm, vim, helpers) { + cm.setCursor(0, 8); + helpers.doKeys('F', 'x'); + helpers.doKeys(';'); + helpers.doKeys('2', ';'); + eq(2, cm.getCursor().ch); +}, { value: '01x3xx6x8x'}); +testVim('t;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('t', 'x'); + helpers.doKeys(';'); + helpers.doKeys('2', ';'); + eq(8, cm.getCursor().ch); +}, { value: '01x3xx678x'}); +testVim('T;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('T', 'x'); + helpers.doKeys(';'); + helpers.doKeys('2', ';'); + eq(2, cm.getCursor().ch); +}, { value: '0xx3xx678x'}); +testVim('f,', function(cm, vim, helpers) { + cm.setCursor(0, 6); + helpers.doKeys('f', 'x'); + helpers.doKeys(','); + helpers.doKeys('2', ','); + eq(2, cm.getCursor().ch); +}, { value: '01x3xx678x'}); +testVim('F,', function(cm, vim, helpers) { + cm.setCursor(0, 3); + helpers.doKeys('F', 'x'); + helpers.doKeys(','); + helpers.doKeys('2', ','); + eq(9, cm.getCursor().ch); +}, { value: '01x3xx678x'}); +testVim('t,', function(cm, vim, helpers) { + cm.setCursor(0, 6); + helpers.doKeys('t', 'x'); + helpers.doKeys(','); + helpers.doKeys('2', ','); + eq(3, cm.getCursor().ch); +}, { value: '01x3xx678x'}); +testVim('T,', function(cm, vim, helpers) { + cm.setCursor(0, 4); + helpers.doKeys('T', 'x'); + helpers.doKeys(','); + helpers.doKeys('2', ','); + eq(8, cm.getCursor().ch); +}, { value: '01x3xx67xx'}); +testVim('fd,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('f', '4'); + cm.setCursor(0, 0); + helpers.doKeys('d', ';'); + eq('56789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('d', ','); + eq('01239', cm.getValue()); +}, { value: '0123456789'}); +testVim('Fd,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('F', '4'); + cm.setCursor(0, 9); + helpers.doKeys('d', ';'); + eq('01239', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('d', ','); + eq('56789', cm.getValue()); +}, { value: '0123456789'}); +testVim('td,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('t', '4'); + cm.setCursor(0, 0); + helpers.doKeys('d', ';'); + eq('456789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('d', ','); + eq('012349', cm.getValue()); +}, { value: '0123456789'}); +testVim('Td,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('T', '4'); + cm.setCursor(0, 9); + helpers.doKeys('d', ';'); + eq('012349', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('d', ','); + eq('456789', cm.getValue()); +}, { value: '0123456789'}); +testVim('fc,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('f', '4'); + cm.setCursor(0, 0); + helpers.doKeys('c', ';', '<Esc>'); + eq('56789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('c', ','); + eq('01239', cm.getValue()); +}, { value: '0123456789'}); +testVim('Fc,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('F', '4'); + cm.setCursor(0, 9); + helpers.doKeys('c', ';', '<Esc>'); + eq('01239', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('c', ','); + eq('56789', cm.getValue()); +}, { value: '0123456789'}); +testVim('tc,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('t', '4'); + cm.setCursor(0, 0); + helpers.doKeys('c', ';', '<Esc>'); + eq('456789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('c', ','); + eq('012349', cm.getValue()); +}, { value: '0123456789'}); +testVim('Tc,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('T', '4'); + cm.setCursor(0, 9); + helpers.doKeys('c', ';', '<Esc>'); + eq('012349', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('c', ','); + eq('456789', cm.getValue()); +}, { value: '0123456789'}); +testVim('fy,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('f', '4'); + cm.setCursor(0, 0); + helpers.doKeys('y', ';', 'P'); + eq('012340123456789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('y', ',', 'P'); + eq('012345678456789', cm.getValue()); +}, { value: '0123456789'}); +testVim('Fy,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('F', '4'); + cm.setCursor(0, 9); + helpers.doKeys('y', ';', 'p'); + eq('012345678945678', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('y', ',', 'P'); + eq('012340123456789', cm.getValue()); +}, { value: '0123456789'}); +testVim('ty,;', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys('t', '4'); + cm.setCursor(0, 0); + helpers.doKeys('y', ';', 'P'); + eq('01230123456789', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 9); + helpers.doKeys('y', ',', 'p'); + eq('01234567895678', cm.getValue()); +}, { value: '0123456789'}); +testVim('Ty,;', function(cm, vim, helpers) { + cm.setCursor(0, 9); + helpers.doKeys('T', '4'); + cm.setCursor(0, 9); + helpers.doKeys('y', ';', 'p'); + eq('01234567895678', cm.getValue()); + helpers.doKeys('u'); + cm.setCursor(0, 0); + helpers.doKeys('y', ',', 'P'); + eq('01230123456789', cm.getValue()); +}, { value: '0123456789'}); +testVim('HML', function(cm, vim, helpers) { + var lines = 35; + var textHeight = cm.defaultTextHeight(); + cm.setSize(600, lines*textHeight); + cm.setCursor(120, 0); + helpers.doKeys('H'); + helpers.assertCursorAt(86, 2); + helpers.doKeys('L'); + helpers.assertCursorAt(120, 4); + helpers.doKeys('M'); + helpers.assertCursorAt(103,4); +}, { value: (function(){ + var lines = new Array(100); + var upper = ' xx\n'; + var lower = ' xx\n'; + upper = lines.join(upper); + lower = lines.join(lower); + return upper + lower; +})()}); + +var zVals = []; +forEach(['zb','zz','zt','z-','z.','z<CR>'], function(e, idx){ + var lineNum = 250; + var lines = 35; + testVim(e, function(cm, vim, helpers) { + var k1 = e[0]; + var k2 = e.substring(1); + var textHeight = cm.defaultTextHeight(); + cm.setSize(600, lines*textHeight); + cm.setCursor(lineNum, 0); + helpers.doKeys(k1, k2); + zVals[idx] = cm.getScrollInfo().top; + }, { value: (function(){ + return new Array(500).join('\n'); + })()}); +}); +testVim('zb_to_bottom', function(cm, vim, helpers){ + var lineNum = 250; + cm.setSize(600, 35*cm.defaultTextHeight()); + cm.setCursor(lineNum, 0); + helpers.doKeys('z', 'b'); + var scrollInfo = cm.getScrollInfo(); + eq(scrollInfo.top + scrollInfo.clientHeight, cm.charCoords(Pos(lineNum, 0), 'local').bottom); +}, { value: (function(){ + return new Array(500).join('\n'); +})()}); +testVim('zt_to_top', function(cm, vim, helpers){ + var lineNum = 250; + cm.setSize(600, 35*cm.defaultTextHeight()); + cm.setCursor(lineNum, 0); + helpers.doKeys('z', 't'); + eq(cm.getScrollInfo().top, cm.charCoords(Pos(lineNum, 0), 'local').top); +}, { value: (function(){ + return new Array(500).join('\n'); +})()}); +testVim('zb<zz', function(cm, vim, helpers){ + eq(zVals[0]<zVals[1], true); +}); +testVim('zz<zt', function(cm, vim, helpers){ + eq(zVals[1]<zVals[2], true); +}); +testVim('zb==z-', function(cm, vim, helpers){ + eq(zVals[0], zVals[3]); +}); +testVim('zz==z.', function(cm, vim, helpers){ + eq(zVals[1], zVals[4]); +}); +testVim('zt==z<CR>', function(cm, vim, helpers){ + eq(zVals[2], zVals[5]); +}); + +var moveTillCharacterSandbox = + 'The quick brown fox \n'; +testVim('moveTillCharacter', function(cm, vim, helpers){ + cm.setCursor(0, 0); + // Search for the 'q'. + cm.openDialog = helpers.fakeOpenDialog('q'); + helpers.doKeys('/'); + eq(4, cm.getCursor().ch); + // Jump to just before the first o in the list. + helpers.doKeys('t'); + helpers.doKeys('o'); + eq('The quick brown fox \n', cm.getValue()); + // Delete that one character. + helpers.doKeys('d'); + helpers.doKeys('t'); + helpers.doKeys('o'); + eq('The quick bown fox \n', cm.getValue()); + // Delete everything until the next 'o'. + helpers.doKeys('.'); + eq('The quick box \n', cm.getValue()); + // An unmatched character should have no effect. + helpers.doKeys('d'); + helpers.doKeys('t'); + helpers.doKeys('q'); + eq('The quick box \n', cm.getValue()); + // Matches should only be possible on single lines. + helpers.doKeys('d'); + helpers.doKeys('t'); + helpers.doKeys('z'); + eq('The quick box \n', cm.getValue()); + // After all that, the search for 'q' should still be active, so the 'N' command + // can run it again in reverse. Use that to delete everything back to the 'q'. + helpers.doKeys('d'); + helpers.doKeys('N'); + eq('The ox \n', cm.getValue()); + eq(4, cm.getCursor().ch); +}, { value: moveTillCharacterSandbox}); +testVim('searchForPipe', function(cm, vim, helpers){ + CodeMirror.Vim.setOption('pcre', false); + cm.setCursor(0, 0); + // Search for the '|'. + cm.openDialog = helpers.fakeOpenDialog('|'); + helpers.doKeys('/'); + eq(4, cm.getCursor().ch); +}, { value: 'this|that'}); + + +var scrollMotionSandbox = + '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'; +testVim('scrollMotion', function(cm, vim, helpers){ + var prevCursor, prevScrollInfo; + cm.setCursor(0, 0); + // ctrl-y at the top of the file should have no effect. + helpers.doKeys('<C-y>'); + eq(0, cm.getCursor().line); + prevScrollInfo = cm.getScrollInfo(); + helpers.doKeys('<C-e>'); + eq(1, cm.getCursor().line); + is(prevScrollInfo.top < cm.getScrollInfo().top); + // Jump to the end of the sandbox. + cm.setCursor(1000, 0); + prevCursor = cm.getCursor(); + // ctrl-e at the bottom of the file should have no effect. + helpers.doKeys('<C-e>'); + eq(prevCursor.line, cm.getCursor().line); + prevScrollInfo = cm.getScrollInfo(); + helpers.doKeys('<C-y>'); + eq(prevCursor.line - 1, cm.getCursor().line, "Y"); + is(prevScrollInfo.top > cm.getScrollInfo().top); +}, { value: scrollMotionSandbox}); + +var squareBracketMotionSandbox = ''+ + '({\n'+//0 + ' ({\n'+//11 + ' /*comment {\n'+//2 + ' */(\n'+//3 + '#else \n'+//4 + ' /* )\n'+//5 + '#if }\n'+//6 + ' )}*/\n'+//7 + ')}\n'+//8 + '{}\n'+//9 + '#else {{\n'+//10 + '{}\n'+//11 + '}\n'+//12 + '{\n'+//13 + '#endif\n'+//14 + '}\n'+//15 + '}\n'+//16 + '#else';//17 +testVim('[[, ]]', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys(']', ']'); + helpers.assertCursorAt(9,0); + helpers.doKeys('2', ']', ']'); + helpers.assertCursorAt(13,0); + helpers.doKeys(']', ']'); + helpers.assertCursorAt(17,0); + helpers.doKeys('[', '['); + helpers.assertCursorAt(13,0); + helpers.doKeys('2', '[', '['); + helpers.assertCursorAt(9,0); + helpers.doKeys('[', '['); + helpers.assertCursorAt(0,0); +}, { value: squareBracketMotionSandbox}); +testVim('[], ][', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doKeys(']', '['); + helpers.assertCursorAt(12,0); + helpers.doKeys('2', ']', '['); + helpers.assertCursorAt(16,0); + helpers.doKeys(']', '['); + helpers.assertCursorAt(17,0); + helpers.doKeys('[', ']'); + helpers.assertCursorAt(16,0); + helpers.doKeys('2', '[', ']'); + helpers.assertCursorAt(12,0); + helpers.doKeys('[', ']'); + helpers.assertCursorAt(0,0); +}, { value: squareBracketMotionSandbox}); +testVim('[{, ]}', function(cm, vim, helpers) { + cm.setCursor(4, 10); + helpers.doKeys('[', '{'); + helpers.assertCursorAt(2,12); + helpers.doKeys('2', '[', '{'); + helpers.assertCursorAt(0,1); + cm.setCursor(4, 10); + helpers.doKeys(']', '}'); + helpers.assertCursorAt(6,11); + helpers.doKeys('2', ']', '}'); + helpers.assertCursorAt(8,1); + cm.setCursor(0,1); + helpers.doKeys(']', '}'); + helpers.assertCursorAt(8,1); + helpers.doKeys('[', '{'); + helpers.assertCursorAt(0,1); +}, { value: squareBracketMotionSandbox}); +testVim('[(, ])', function(cm, vim, helpers) { + cm.setCursor(4, 10); + helpers.doKeys('[', '('); + helpers.assertCursorAt(3,14); + helpers.doKeys('2', '[', '('); + helpers.assertCursorAt(0,0); + cm.setCursor(4, 10); + helpers.doKeys(']', ')'); + helpers.assertCursorAt(5,11); + helpers.doKeys('2', ']', ')'); + helpers.assertCursorAt(8,0); + helpers.doKeys('[', '('); + helpers.assertCursorAt(0,0); + helpers.doKeys(']', ')'); + helpers.assertCursorAt(8,0); +}, { value: squareBracketMotionSandbox}); +testVim('[*, ]*, [/, ]/', function(cm, vim, helpers) { + forEach(['*', '/'], function(key){ + cm.setCursor(7, 0); + helpers.doKeys('2', '[', key); + helpers.assertCursorAt(2,2); + helpers.doKeys('2', ']', key); + helpers.assertCursorAt(7,5); + }); +}, { value: squareBracketMotionSandbox}); +testVim('[#, ]#', function(cm, vim, helpers) { + cm.setCursor(10, 3); + helpers.doKeys('2', '[', '#'); + helpers.assertCursorAt(4,0); + helpers.doKeys('5', ']', '#'); + helpers.assertCursorAt(17,0); + cm.setCursor(10, 3); + helpers.doKeys(']', '#'); + helpers.assertCursorAt(14,0); +}, { value: squareBracketMotionSandbox}); +testVim('[m, ]m, [M, ]M', function(cm, vim, helpers) { + cm.setCursor(11, 0); + helpers.doKeys('[', 'm'); + helpers.assertCursorAt(10,7); + helpers.doKeys('4', '[', 'm'); + helpers.assertCursorAt(1,3); + helpers.doKeys('5', ']', 'm'); + helpers.assertCursorAt(11,0); + helpers.doKeys('[', 'M'); + helpers.assertCursorAt(9,1); + helpers.doKeys('3', ']', 'M'); + helpers.assertCursorAt(15,0); + helpers.doKeys('5', '[', 'M'); + helpers.assertCursorAt(7,3); +}, { value: squareBracketMotionSandbox}); + +testVim('i_indent_right', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedValue = ' word1\nword2\nword3 '; + helpers.doKeys('i', '<C-t>'); + eq(expectedValue, cm.getValue()); + helpers.assertCursorAt(0, 5); +}, { value: ' word1\nword2\nword3 ', indentUnit: 2 }); +testVim('i_indent_left', function(cm, vim, helpers) { + cm.setCursor(0, 3); + var expectedValue = ' word1\nword2\nword3 '; + helpers.doKeys('i', '<C-d>'); + eq(expectedValue, cm.getValue()); + helpers.assertCursorAt(0, 1); +}, { value: ' word1\nword2\nword3 ', indentUnit: 2 }); + +// Ex mode tests +testVim('ex_go_to_line', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doEx('4'); + helpers.assertCursorAt(3, 0); +}, { value: 'a\nb\nc\nd\ne\n'}); +testVim('ex_go_to_mark', function(cm, vim, helpers) { + cm.setCursor(3, 0); + helpers.doKeys('m', 'a'); + cm.setCursor(0, 0); + helpers.doEx('\'a'); + helpers.assertCursorAt(3, 0); +}, { value: 'a\nb\nc\nd\ne\n'}); +testVim('ex_go_to_line_offset', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doEx('+3'); + helpers.assertCursorAt(3, 0); + helpers.doEx('-1'); + helpers.assertCursorAt(2, 0); + helpers.doEx('.2'); + helpers.assertCursorAt(4, 0); + helpers.doEx('.-3'); + helpers.assertCursorAt(1, 0); +}, { value: 'a\nb\nc\nd\ne\n'}); +testVim('ex_go_to_mark_offset', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('m', 'a'); + cm.setCursor(0, 0); + helpers.doEx('\'a1'); + helpers.assertCursorAt(3, 0); + helpers.doEx('\'a-1'); + helpers.assertCursorAt(1, 0); + helpers.doEx('\'a+2'); + helpers.assertCursorAt(4, 0); +}, { value: 'a\nb\nc\nd\ne\n'}); +testVim('ex_write', function(cm, vim, helpers) { + var tmp = CodeMirror.commands.save; + var written; + var actualCm; + CodeMirror.commands.save = function(cm) { + written = true; + actualCm = cm; + }; + // Test that w, wr, wri ... write all trigger :write. + var command = 'write'; + for (var i = 1; i < command.length; i++) { + written = false; + actualCm = null; + helpers.doEx(command.substring(0, i)); + eq(written, true); + eq(actualCm, cm); + } + CodeMirror.commands.save = tmp; +}); +testVim('ex_sort', function(cm, vim, helpers) { + helpers.doEx('sort'); + eq('Z\na\nb\nc\nd', cm.getValue()); +}, { value: 'b\nZ\nd\nc\na'}); +testVim('ex_sort_reverse', function(cm, vim, helpers) { + helpers.doEx('sort!'); + eq('d\nc\nb\na', cm.getValue()); +}, { value: 'b\nd\nc\na'}); +testVim('ex_sort_range', function(cm, vim, helpers) { + helpers.doEx('2,3sort'); + eq('b\nc\nd\na', cm.getValue()); +}, { value: 'b\nd\nc\na'}); +testVim('ex_sort_oneline', function(cm, vim, helpers) { + helpers.doEx('2sort'); + // Expect no change. + eq('b\nd\nc\na', cm.getValue()); +}, { value: 'b\nd\nc\na'}); +testVim('ex_sort_ignoreCase', function(cm, vim, helpers) { + helpers.doEx('sort i'); + eq('a\nb\nc\nd\nZ', cm.getValue()); +}, { value: 'b\nZ\nd\nc\na'}); +testVim('ex_sort_unique', function(cm, vim, helpers) { + helpers.doEx('sort u'); + eq('Z\na\nb\nc\nd', cm.getValue()); +}, { value: 'b\nZ\na\na\nd\na\nc\na'}); +testVim('ex_sort_decimal', function(cm, vim, helpers) { + helpers.doEx('sort d'); + eq('d3\n s5\n6\n.9', cm.getValue()); +}, { value: '6\nd3\n s5\n.9'}); +testVim('ex_sort_decimal_negative', function(cm, vim, helpers) { + helpers.doEx('sort d'); + eq('z-9\nd3\n s5\n6\n.9', cm.getValue()); +}, { value: '6\nd3\n s5\n.9\nz-9'}); +testVim('ex_sort_decimal_reverse', function(cm, vim, helpers) { + helpers.doEx('sort! d'); + eq('.9\n6\n s5\nd3', cm.getValue()); +}, { value: '6\nd3\n s5\n.9'}); +testVim('ex_sort_hex', function(cm, vim, helpers) { + helpers.doEx('sort x'); + eq(' s5\n6\n.9\n&0xB\nd3', cm.getValue()); +}, { value: '6\nd3\n s5\n&0xB\n.9'}); +testVim('ex_sort_octal', function(cm, vim, helpers) { + helpers.doEx('sort o'); + eq('.9\n.8\nd3\n s5\n6', cm.getValue()); +}, { value: '6\nd3\n s5\n.9\n.8'}); +testVim('ex_sort_decimal_mixed', function(cm, vim, helpers) { + helpers.doEx('sort d'); + eq('z\ny\nc1\nb2\na3', cm.getValue()); +}, { value: 'a3\nz\nc1\ny\nb2'}); +testVim('ex_sort_decimal_mixed_reverse', function(cm, vim, helpers) { + helpers.doEx('sort! d'); + eq('a3\nb2\nc1\nz\ny', cm.getValue()); +}, { value: 'a3\nz\nc1\ny\nb2'}); +testVim('ex_sort_pattern_alpha', function(cm, vim, helpers) { + helpers.doEx('sort /[a-z]/'); + eq('a3\nb2\nc1\ny\nz', cm.getValue()); +}, { value: 'z\ny\nc1\nb2\na3'}); +testVim('ex_sort_pattern_alpha_reverse', function(cm, vim, helpers) { + helpers.doEx('sort! /[a-z]/'); + eq('z\ny\nc1\nb2\na3', cm.getValue()); +}, { value: 'z\ny\nc1\nb2\na3'}); +testVim('ex_sort_pattern_alpha_ignoreCase', function(cm, vim, helpers) { + helpers.doEx('sort i/[a-z]/'); + eq('a3\nb2\nC1\nY\nz', cm.getValue()); +}, { value: 'z\nY\nC1\nb2\na3'}); +testVim('ex_sort_pattern_alpha_longer', function(cm, vim, helpers) { + helpers.doEx('sort /[a-z]+/'); + eq('a\naa\nab\nade\nadele\nadelle\nadriana\nalex\nalexandra\nb\nc\ny\nz', cm.getValue()); +}, { value: 'z\nab\naa\nade\nadelle\nalexandra\nalex\nadriana\nadele\ny\nc\nb\na'}); +testVim('ex_sort_pattern_alpha_only', function(cm, vim, helpers) { + helpers.doEx('sort /^[a-z]$/'); + eq('z1\ny2\na3\nb\nc', cm.getValue()); +}, { value: 'z1\ny2\na3\nc\nb'}); +testVim('ex_sort_pattern_alpha_only_reverse', function(cm, vim, helpers) { + helpers.doEx('sort! /^[a-z]$/'); + eq('c\nb\nz1\ny2\na3', cm.getValue()); +}, { value: 'z1\ny2\na3\nc\nb'}); +testVim('ex_sort_pattern_alpha_num', function(cm, vim, helpers) { + helpers.doEx('sort /[a-z][0-9]/'); + eq('c\nb\na3\ny2\nz1', cm.getValue()); +}, { value: 'z1\ny2\na3\nc\nb'}); +// test for :global command +testVim('ex_global', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doEx('g/one/s//two'); + eq('two two\n two two\n two two', cm.getValue()); + helpers.doEx('1,2g/two/s//one'); + eq('one one\n one one\n two two', cm.getValue()); +}, {value: 'one one\n one one\n one one'}); +testVim('ex_global_confirm', function(cm, vim, helpers) { + cm.setCursor(0, 0); + var onKeyDown; + var openDialogSave = cm.openDialog; + var KEYCODES = { + a: 65, + n: 78, + q: 81, + y: 89 + }; + // Intercept the ex command, 'global' + cm.openDialog = function(template, callback, options) { + // Intercept the prompt for the embedded ex command, 'substitute' + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + }; + callback('g/one/s//two/gc'); + }; + helpers.doKeys(':'); + var close = function() {}; + onKeyDown({keyCode: KEYCODES.n}, '', close); + onKeyDown({keyCode: KEYCODES.y}, '', close); + onKeyDown({keyCode: KEYCODES.a}, '', close); + onKeyDown({keyCode: KEYCODES.q}, '', close); + onKeyDown({keyCode: KEYCODES.y}, '', close); + eq('one two\n two two\n one one\n two one\n one one', cm.getValue()); +}, {value: 'one one\n one one\n one one\n one one\n one one'}); +// Basic substitute tests. +testVim('ex_substitute_same_line', function(cm, vim, helpers) { + cm.setCursor(1, 0); + helpers.doEx('s/one/two/g'); + eq('one one\n two two', cm.getValue()); +}, { value: 'one one\n one one'}); +testVim('ex_substitute_full_file', function(cm, vim, helpers) { + cm.setCursor(1, 0); + helpers.doEx('%s/one/two/g'); + eq('two two\n two two', cm.getValue()); +}, { value: 'one one\n one one'}); +testVim('ex_substitute_input_range', function(cm, vim, helpers) { + cm.setCursor(1, 0); + helpers.doEx('1,3s/\\d/0/g'); + eq('0\n0\n0\n4', cm.getValue()); +}, { value: '1\n2\n3\n4' }); +testVim('ex_substitute_range_current_to_input', function(cm, vim, helpers) { + cm.setCursor(1, 0); + helpers.doEx('.,3s/\\d/0/g'); + eq('1\n0\n0\n4', cm.getValue()); +}, { value: '1\n2\n3\n4' }); +testVim('ex_substitute_range_input_to_current', function(cm, vim, helpers) { + cm.setCursor(3, 0); + helpers.doEx('2,.s/\\d/0/g'); + eq('1\n0\n0\n0\n5', cm.getValue()); +}, { value: '1\n2\n3\n4\n5' }); +testVim('ex_substitute_range_offset', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doEx('-1,+1s/\\d/0/g'); + eq('1\n0\n0\n0\n5', cm.getValue()); +}, { value: '1\n2\n3\n4\n5' }); +testVim('ex_substitute_range_implicit_offset', function(cm, vim, helpers) { + cm.setCursor(0, 0); + helpers.doEx('.1,.3s/\\d/0/g'); + eq('1\n0\n0\n0\n5', cm.getValue()); +}, { value: '1\n2\n3\n4\n5' }); +testVim('ex_substitute_to_eof', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doEx('.,$s/\\d/0/g'); + eq('1\n2\n0\n0\n0', cm.getValue()); +}, { value: '1\n2\n3\n4\n5' }); +testVim('ex_substitute_to_relative_eof', function(cm, vim, helpers) { + cm.setCursor(4, 0); + helpers.doEx('2,$-2s/\\d/0/g'); + eq('1\n0\n0\n4\n5', cm.getValue()); +}, { value: '1\n2\n3\n4\n5' }); +testVim('ex_substitute_range_mark', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('ma'); + cm.setCursor(0, 0); + helpers.doEx('.,\'as/\\d/0/g'); + eq('0\n0\n0\n4\n5', cm.getValue()); +}, { value: '1\n2\n3\n4\n5' }); +testVim('ex_substitute_range_mark_offset', function(cm, vim, helpers) { + cm.setCursor(2, 0); + helpers.doKeys('ma'); + cm.setCursor(0, 0); + helpers.doEx('\'a-1,\'a+1s/\\d/0/g'); + eq('1\n0\n0\n0\n5', cm.getValue()); +}, { value: '1\n2\n3\n4\n5' }); +testVim('ex_substitute_visual_range', function(cm, vim, helpers) { + cm.setCursor(1, 0); + // Set last visual mode selection marks '< and '> at lines 2 and 4 + helpers.doKeys('V', '2', 'j', 'v'); + helpers.doEx('\'<,\'>s/\\d/0/g'); + eq('1\n0\n0\n0\n5', cm.getValue()); +}, { value: '1\n2\n3\n4\n5' }); +testVim('ex_substitute_empty_query', function(cm, vim, helpers) { + // If the query is empty, use last query. + cm.setCursor(1, 0); + cm.openDialog = helpers.fakeOpenDialog('1'); + helpers.doKeys('/'); + helpers.doEx('s//b/g'); + eq('abb ab2 ab3', cm.getValue()); +}, { value: 'a11 a12 a13' }); +testVim('ex_substitute_javascript', function(cm, vim, helpers) { + CodeMirror.Vim.setOption('pcre', false); + cm.setCursor(1, 0); + // Throw all the things that javascript likes to treat as special values + // into the replace part. All should be literal (this is VIM). + helpers.doEx('s/\\(\\d+\\)/$$ $\' $` $& \\1/g') + eq('a $$ $\' $` $& 0 b', cm.getValue()); +}, { value: 'a 0 b' }); +testVim('ex_substitute_empty_arguments', function(cm,vim,helpers) { + cm.setCursor(0, 0); + helpers.doEx('s/a/b/g'); + cm.setCursor(1, 0); + helpers.doEx('s'); + eq('b b\nb a', cm.getValue()); +}, {value: 'a a\na a'}); + +// More complex substitute tests that test both pcre and nopcre options. +function testSubstitute(name, options) { + testVim(name + '_pcre', function(cm, vim, helpers) { + cm.setCursor(1, 0); + CodeMirror.Vim.setOption('pcre', true); + helpers.doEx(options.expr); + eq(options.expectedValue, cm.getValue()); + }, options); + // If no noPcreExpr is defined, assume that it's the same as the expr. + var noPcreExpr = options.noPcreExpr ? options.noPcreExpr : options.expr; + testVim(name + '_nopcre', function(cm, vim, helpers) { + cm.setCursor(1, 0); + CodeMirror.Vim.setOption('pcre', false); + helpers.doEx(noPcreExpr); + eq(options.expectedValue, cm.getValue()); + }, options); +} +testSubstitute('ex_substitute_capture', { + value: 'a11 a12 a13', + expectedValue: 'a1111 a1212 a1313', + // $n is a backreference + expr: 's/(\\d+)/$1$1/g', + // \n is a backreference. + noPcreExpr: 's/\\(\\d+\\)/\\1\\1/g'}); +testSubstitute('ex_substitute_capture2', { + value: 'a 0 b', + expectedValue: 'a $00 b', + expr: 's/(\\d+)/$$$1$1/g', + noPcreExpr: 's/\\(\\d+\\)/$\\1\\1/g'}); +testSubstitute('ex_substitute_nocapture', { + value: 'a11 a12 a13', + expectedValue: 'a$1$1 a$1$1 a$1$1', + expr: 's/(\\d+)/$$1$$1/g', + noPcreExpr: 's/\\(\\d+\\)/$1$1/g'}); +testSubstitute('ex_substitute_nocapture2', { + value: 'a 0 b', + expectedValue: 'a $10 b', + expr: 's/(\\d+)/$$1$1/g', + noPcreExpr: 's/\\(\\d+\\)/\\$1\\1/g'}); +testSubstitute('ex_substitute_nocapture', { + value: 'a b c', + expectedValue: 'a $ c', + expr: 's/b/$$/', + noPcreExpr: 's/b/$/'}); +testSubstitute('ex_substitute_slash_regex', { + value: 'one/two \n three/four', + expectedValue: 'one|two \n three|four', + expr: '%s/\\//|'}); +testSubstitute('ex_substitute_pipe_regex', { + value: 'one|two \n three|four', + expectedValue: 'one,two \n three,four', + expr: '%s/\\|/,/', + noPcreExpr: '%s/|/,/'}); +testSubstitute('ex_substitute_or_regex', { + value: 'one|two \n three|four', + expectedValue: 'ana|twa \n thraa|faar', + expr: '%s/o|e|u/a/g', + noPcreExpr: '%s/o\\|e\\|u/a/g'}); +testSubstitute('ex_substitute_or_word_regex', { + value: 'one|two \n three|four', + expectedValue: 'five|five \n three|four', + expr: '%s/(one|two)/five/g', + noPcreExpr: '%s/\\(one\\|two\\)/five/g'}); +testSubstitute('ex_substitute_backslashslash_regex', { + value: 'one\\two \n three\\four', + expectedValue: 'one,two \n three,four', + expr: '%s/\\\\/,'}); +testSubstitute('ex_substitute_slash_replacement', { + value: 'one,two \n three,four', + expectedValue: 'one/two \n three/four', + expr: '%s/,/\\/'}); +testSubstitute('ex_substitute_backslash_replacement', { + value: 'one,two \n three,four', + expectedValue: 'one\\two \n three\\four', + expr: '%s/,/\\\\/g'}); +testSubstitute('ex_substitute_multibackslash_replacement', { + value: 'one,two \n three,four', + expectedValue: 'one\\\\\\\\two \n three\\\\\\\\four', // 2*8 backslashes. + expr: '%s/,/\\\\\\\\\\\\\\\\/g'}); // 16 backslashes. +testSubstitute('ex_substitute_dollar_match', { + value: 'one,two \n three,four', + expectedValue: 'one,two ,\n three,four', + expr: '%s/$/,/g'}); +testSubstitute('ex_substitute_newline_match', { + value: 'one,two \n three,four', + expectedValue: 'one,two , three,four', + expr: '%s/\\n/,/g'}); +testSubstitute('ex_substitute_newline_replacement', { + value: 'one,two \n three,four', + expectedValue: 'one\ntwo \n three\nfour', + expr: '%s/,/\\n/g'}); +testSubstitute('ex_substitute_braces_word', { + value: 'ababab abb ab{2}', + expectedValue: 'ab abb ab{2}', + expr: '%s/(ab){2}//g', + noPcreExpr: '%s/\\(ab\\)\\{2\\}//g'}); +testSubstitute('ex_substitute_braces_range', { + value: 'a aa aaa aaaa', + expectedValue: 'a a', + expr: '%s/a{2,3}//g', + noPcreExpr: '%s/a\\{2,3\\}//g'}); +testSubstitute('ex_substitute_braces_literal', { + value: 'ababab abb ab{2}', + expectedValue: 'ababab abb ', + expr: '%s/ab\\{2\\}//g', + noPcreExpr: '%s/ab{2}//g'}); +testSubstitute('ex_substitute_braces_char', { + value: 'ababab abb ab{2}', + expectedValue: 'ababab ab{2}', + expr: '%s/ab{2}//g', + noPcreExpr: '%s/ab\\{2\\}//g'}); +testSubstitute('ex_substitute_braces_no_escape', { + value: 'ababab abb ab{2}', + expectedValue: 'ababab ab{2}', + expr: '%s/ab{2}//g', + noPcreExpr: '%s/ab\\{2}//g'}); +testSubstitute('ex_substitute_count', { + value: '1\n2\n3\n4', + expectedValue: '1\n0\n0\n4', + expr: 's/\\d/0/i 2'}); +testSubstitute('ex_substitute_count_with_range', { + value: '1\n2\n3\n4', + expectedValue: '1\n2\n0\n0', + expr: '1,3s/\\d/0/ 3'}); +testSubstitute('ex_substitute_not_global', { + value: 'aaa\nbaa\ncaa', + expectedValue: 'xaa\nbxa\ncxa', + expr: '%s/a/x/'}); +function testSubstituteConfirm(name, command, initialValue, expectedValue, keys, finalPos) { + testVim(name, function(cm, vim, helpers) { + var savedOpenDialog = cm.openDialog; + var savedKeyName = CodeMirror.keyName; + var onKeyDown; + var recordedCallback; + var closed = true; // Start out closed, set false on second openDialog. + function close() { + closed = true; + } + // First openDialog should save callback. + cm.openDialog = function(template, callback, options) { + recordedCallback = callback; + } + // Do first openDialog. + helpers.doKeys(':'); + // Second openDialog should save keyDown handler. + cm.openDialog = function(template, callback, options) { + onKeyDown = options.onKeyDown; + closed = false; + }; + // Return the command to Vim and trigger second openDialog. + recordedCallback(command); + // The event should really use keyCode, but here just mock it out and use + // key and replace keyName to just return key. + CodeMirror.keyName = function (e) { return e.key; } + keys = keys.toUpperCase(); + for (var i = 0; i < keys.length; i++) { + is(!closed); + onKeyDown({ key: keys.charAt(i) }, '', close); + } + try { + eq(expectedValue, cm.getValue()); + helpers.assertCursorAt(finalPos); + is(closed); + } catch(e) { + throw e + } finally { + // Restore overridden functions. + CodeMirror.keyName = savedKeyName; + cm.openDialog = savedOpenDialog; + } + }, { value: initialValue }); +}; +testSubstituteConfirm('ex_substitute_confirm_emptydoc', + '%s/x/b/c', '', '', '', makeCursor(0, 0)); +testSubstituteConfirm('ex_substitute_confirm_nomatch', + '%s/x/b/c', 'ba a\nbab', 'ba a\nbab', '', makeCursor(0, 0)); +testSubstituteConfirm('ex_substitute_confirm_accept', + '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'yyy', makeCursor(1, 1)); +testSubstituteConfirm('ex_substitute_confirm_random_keys', + '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'ysdkywerty', makeCursor(1, 1)); +testSubstituteConfirm('ex_substitute_confirm_some', + '%s/a/b/cg', 'ba a\nbab', 'bb a\nbbb', 'yny', makeCursor(1, 1)); +testSubstituteConfirm('ex_substitute_confirm_all', + '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'a', makeCursor(1, 1)); +testSubstituteConfirm('ex_substitute_confirm_accept_then_all', + '%s/a/b/cg', 'ba a\nbab', 'bb b\nbbb', 'ya', makeCursor(1, 1)); +testSubstituteConfirm('ex_substitute_confirm_quit', + '%s/a/b/cg', 'ba a\nbab', 'bb a\nbab', 'yq', makeCursor(0, 3)); +testSubstituteConfirm('ex_substitute_confirm_last', + '%s/a/b/cg', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3)); +testSubstituteConfirm('ex_substitute_confirm_oneline', + '1s/a/b/cg', 'ba a\nbab', 'bb b\nbab', 'yl', makeCursor(0, 3)); +testSubstituteConfirm('ex_substitute_confirm_range_accept', + '1,2s/a/b/cg', 'aa\na \na\na', 'bb\nb \na\na', 'yyy', makeCursor(1, 0)); +testSubstituteConfirm('ex_substitute_confirm_range_some', + '1,3s/a/b/cg', 'aa\na \na\na', 'ba\nb \nb\na', 'ynyy', makeCursor(2, 0)); +testSubstituteConfirm('ex_substitute_confirm_range_all', + '1,3s/a/b/cg', 'aa\na \na\na', 'bb\nb \nb\na', 'a', makeCursor(2, 0)); +testSubstituteConfirm('ex_substitute_confirm_range_last', + '1,3s/a/b/cg', 'aa\na \na\na', 'bb\nb \na\na', 'yyl', makeCursor(1, 0)); +//:noh should clear highlighting of search-results but allow to resume search through n +testVim('ex_noh_clearSearchHighlight', function(cm, vim, helpers) { + cm.openDialog = helpers.fakeOpenDialog('match'); + helpers.doKeys('?'); + helpers.doEx('noh'); + eq(vim.searchState_.getOverlay(),null,'match-highlighting wasn\'t cleared'); + helpers.doKeys('n'); + helpers.assertCursorAt(0, 11,'can\'t resume search after clearing highlighting'); +}, { value: 'match nope match \n nope Match' }); +testVim('ex_yank', function (cm, vim, helpers) { + var curStart = makeCursor(3, 0); + cm.setCursor(curStart); + helpers.doEx('y'); + var register = helpers.getRegisterController().getRegister(); + var line = cm.getLine(3); + eq(line + '\n', register.toString()); +}); +testVim('set_boolean', function(cm, vim, helpers) { + CodeMirror.Vim.defineOption('testoption', true, 'boolean'); + // Test default value is set. + is(CodeMirror.Vim.getOption('testoption')); + try { + // Test fail to set to non-boolean + CodeMirror.Vim.setOption('testoption', '5'); + fail(); + } catch (expected) {} + // Test setOption + CodeMirror.Vim.setOption('testoption', false); + is(!CodeMirror.Vim.getOption('testoption')); +}); +testVim('ex_set_boolean', function(cm, vim, helpers) { + CodeMirror.Vim.defineOption('testoption', true, 'boolean'); + // Test default value is set. + is(CodeMirror.Vim.getOption('testoption')); + try { + // Test fail to set to non-boolean + helpers.doEx('set testoption=22'); + fail(); + } catch (expected) {} + // Test setOption + helpers.doEx('set notestoption'); + is(!CodeMirror.Vim.getOption('testoption')); +}); +testVim('set_string', function(cm, vim, helpers) { + CodeMirror.Vim.defineOption('testoption', 'a', 'string'); + // Test default value is set. + eq('a', CodeMirror.Vim.getOption('testoption')); + try { + // Test fail to set non-string. + CodeMirror.Vim.setOption('testoption', true); + fail(); + } catch (expected) {} + try { + // Test fail to set 'notestoption' + CodeMirror.Vim.setOption('notestoption', 'b'); + fail(); + } catch (expected) {} + // Test setOption + CodeMirror.Vim.setOption('testoption', 'c'); + eq('c', CodeMirror.Vim.getOption('testoption')); +}); +testVim('ex_set_string', function(cm, vim, helpers) { + CodeMirror.Vim.defineOption('testopt', 'a', 'string'); + // Test default value is set. + eq('a', CodeMirror.Vim.getOption('testopt')); + try { + // Test fail to set 'notestopt' + helpers.doEx('set notestopt=b'); + fail(); + } catch (expected) {} + // Test setOption + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt')); + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global + eq('c', CodeMirror.Vim.getOption('testopt')); // global + // Test setOption global + helpers.doEx('setg testopt=d') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); + // Test setOption local + helpers.doEx('setl testopt=e') + eq('e', CodeMirror.Vim.getOption('testopt', cm)); + eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); +}); +testVim('ex_set_callback', function(cm, vim, helpers) { + var global; + + function cb(val, cm, cfg) { + if (val === undefined) { + // Getter + if (cm) { + return cm._local; + } else { + return global; + } + } else { + // Setter + if (cm) { + cm._local = val; + } else { + global = val; + } + } + } + + CodeMirror.Vim.defineOption('testopt', 'a', 'string', cb); + // Test default value is set. + eq('a', CodeMirror.Vim.getOption('testopt')); + try { + // Test fail to set 'notestopt' + helpers.doEx('set notestopt=b'); + fail(); + } catch (expected) {} + // Test setOption (Identical to the string tests, but via callback instead) + helpers.doEx('set testopt=c') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); //local || global + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); // local + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); // global + eq('c', CodeMirror.Vim.getOption('testopt')); // global + // Test setOption global + helpers.doEx('setg testopt=d') + eq('c', CodeMirror.Vim.getOption('testopt', cm)); + eq('c', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); + // Test setOption local + helpers.doEx('setl testopt=e') + eq('e', CodeMirror.Vim.getOption('testopt', cm)); + eq('e', CodeMirror.Vim.getOption('testopt', cm, {scope: 'local'})); + eq('d', CodeMirror.Vim.getOption('testopt', cm, {scope: 'global'})); + eq('d', CodeMirror.Vim.getOption('testopt')); +}) +testVim('ex_set_filetype', function(cm, vim, helpers) { + CodeMirror.defineMode('test_mode', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + CodeMirror.defineMode('test_mode_2', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + // Test mode is set. + helpers.doEx('set filetype=test_mode'); + eq('test_mode', cm.getMode().name); + // Test 'ft' alias also sets mode. + helpers.doEx('set ft=test_mode_2'); + eq('test_mode_2', cm.getMode().name); +}); +testVim('ex_set_filetype_null', function(cm, vim, helpers) { + CodeMirror.defineMode('test_mode', function() { + return {token: function(stream) { + stream.match(/^\s+|^\S+/); + }}; + }); + cm.setOption('mode', 'test_mode'); + // Test mode is set to null. + helpers.doEx('set filetype='); + eq('null', cm.getMode().name); +}); +// TODO: Reset key maps after each test. +testVim('ex_map_key2key', function(cm, vim, helpers) { + helpers.doEx('map a x'); + helpers.doKeys('a'); + helpers.assertCursorAt(0, 0); + eq('bc', cm.getValue()); +}, { value: 'abc' }); +testVim('ex_unmap_key2key', function(cm, vim, helpers) { + helpers.doEx('unmap a'); + helpers.doKeys('a'); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: 'abc' }); +testVim('ex_unmap_key2key_does_not_remove_default', function(cm, vim, helpers) { + try { + helpers.doEx('unmap a'); + fail(); + } catch (expected) {} + helpers.doKeys('a'); + eq('vim-insert', cm.getOption('keyMap')); +}, { value: 'abc' }); +testVim('ex_map_key2key_to_colon', function(cm, vim, helpers) { + helpers.doEx('map ; :'); + var dialogOpened = false; + cm.openDialog = function() { + dialogOpened = true; + } + helpers.doKeys(';'); + eq(dialogOpened, true); +}); +testVim('ex_map_ex2key:', function(cm, vim, helpers) { + helpers.doEx('map :del x'); + helpers.doEx('del'); + helpers.assertCursorAt(0, 0); + eq('bc', cm.getValue()); +}, { value: 'abc' }); +testVim('ex_map_ex2ex', function(cm, vim, helpers) { + helpers.doEx('map :del :w'); + var tmp = CodeMirror.commands.save; + var written = false; + var actualCm; + CodeMirror.commands.save = function(cm) { + written = true; + actualCm = cm; + }; + helpers.doEx('del'); + CodeMirror.commands.save = tmp; + eq(written, true); + eq(actualCm, cm); +}); +testVim('ex_map_key2ex', function(cm, vim, helpers) { + helpers.doEx('map a :w'); + var tmp = CodeMirror.commands.save; + var written = false; + var actualCm; + CodeMirror.commands.save = function(cm) { + written = true; + actualCm = cm; + }; + helpers.doKeys('a'); + CodeMirror.commands.save = tmp; + eq(written, true); + eq(actualCm, cm); +}); +testVim('ex_map_key2key_visual_api', function(cm, vim, helpers) { + CodeMirror.Vim.map('b', ':w', 'visual'); + var tmp = CodeMirror.commands.save; + var written = false; + var actualCm; + CodeMirror.commands.save = function(cm) { + written = true; + actualCm = cm; + }; + // Mapping should not work in normal mode. + helpers.doKeys('b'); + eq(written, false); + // Mapping should work in visual mode. + helpers.doKeys('v', 'b'); + eq(written, true); + eq(actualCm, cm); + + CodeMirror.commands.save = tmp; +}); +testVim('ex_imap', function(cm, vim, helpers) { + CodeMirror.Vim.map('jk', '<Esc>', 'insert'); + helpers.doKeys('i'); + is(vim.insertMode); + helpers.doKeys('j', 'k'); + is(!vim.insertMode); + cm.setCursor(0, 1); + CodeMirror.Vim.map('jj', '<Esc>', 'insert'); + helpers.doKeys('<C-v>', '2', 'j', 'l', 'c'); + var replacement = fillArray('fo', 3); + cm.replaceSelections(replacement); + eq('1fo4\n5fo8\nafodefg', cm.getValue()); + helpers.doKeys('j', 'j'); + cm.setCursor(0, 0); + helpers.doKeys('.'); + eq('foo4\nfoo8\nfoodefg', cm.getValue()); +}, { value: '1234\n5678\nabcdefg' }); +testVim('ex_unmap_api', function(cm, vim, helpers) { + CodeMirror.Vim.map('<Alt-X>', 'gg', 'normal'); + is(CodeMirror.Vim.handleKey(cm, "<Alt-X>", "normal"), "Alt-X key is mapped"); + CodeMirror.Vim.unmap("<Alt-X>", "normal"); + is(!CodeMirror.Vim.handleKey(cm, "<Alt-X>", "normal"), "Alt-X key is unmapped"); +}); + +// Testing registration of functions as ex-commands and mapping to <Key>-keys +testVim('ex_api_test', function(cm, vim, helpers) { + var res=false; + var val='from'; + CodeMirror.Vim.defineEx('extest','ext',function(cm,params){ + if(params.args)val=params.args[0]; + else res=true; + }); + helpers.doEx(':ext to'); + eq(val,'to','Defining ex-command failed'); + CodeMirror.Vim.map('<C-CR><Space>',':ext'); + helpers.doKeys('<C-CR>','<Space>'); + is(res,'Mapping to key failed'); +}); +// For now, this test needs to be last because it messes up : for future tests. +testVim('ex_map_key2key_from_colon', function(cm, vim, helpers) { + helpers.doEx('map : x'); + helpers.doKeys(':'); + helpers.assertCursorAt(0, 0); + eq('bc', cm.getValue()); +}, { value: 'abc' }); + +// Test event handlers +testVim('beforeSelectionChange', function(cm, vim, helpers) { + cm.setCursor(0, 100); + eqCursorPos(cm.getCursor('head'), cm.getCursor('anchor')); +}, { value: 'abc' }); + + diff --git a/modules/editor/codemirror/theme/ambiance.css b/modules/editor/codemirror/theme/ambiance.css @@ -0,0 +1,74 @@ +/* ambiance theme for codemirror */ + +/* Color scheme */ + +.cm-s-ambiance .cm-header { color: blue; } +.cm-s-ambiance .cm-quote { color: #24C2C7; } + +.cm-s-ambiance .cm-keyword { color: #cda869; } +.cm-s-ambiance .cm-atom { color: #CF7EA9; } +.cm-s-ambiance .cm-number { color: #78CF8A; } +.cm-s-ambiance .cm-def { color: #aac6e3; } +.cm-s-ambiance .cm-variable { color: #ffb795; } +.cm-s-ambiance .cm-variable-2 { color: #eed1b3; } +.cm-s-ambiance .cm-variable-3, .cm-s-ambiance .cm-type { color: #faded3; } +.cm-s-ambiance .cm-property { color: #eed1b3; } +.cm-s-ambiance .cm-operator { color: #fa8d6a; } +.cm-s-ambiance .cm-comment { color: #555; font-style:italic; } +.cm-s-ambiance .cm-string { color: #8f9d6a; } +.cm-s-ambiance .cm-string-2 { color: #9d937c; } +.cm-s-ambiance .cm-meta { color: #D2A8A1; } +.cm-s-ambiance .cm-qualifier { color: yellow; } +.cm-s-ambiance .cm-builtin { color: #9999cc; } +.cm-s-ambiance .cm-bracket { color: #24C2C7; } +.cm-s-ambiance .cm-tag { color: #fee4ff; } +.cm-s-ambiance .cm-attribute { color: #9B859D; } +.cm-s-ambiance .cm-hr { color: pink; } +.cm-s-ambiance .cm-link { color: #F4C20B; } +.cm-s-ambiance .cm-special { color: #FF9D00; } +.cm-s-ambiance .cm-error { color: #AF2018; } + +.cm-s-ambiance .CodeMirror-matchingbracket { color: #0f0; } +.cm-s-ambiance .CodeMirror-nonmatchingbracket { color: #f22; } + +.cm-s-ambiance div.CodeMirror-selected { background: rgba(255, 255, 255, 0.15); } +.cm-s-ambiance.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } +.cm-s-ambiance .CodeMirror-line::selection, .cm-s-ambiance .CodeMirror-line > span::selection, .cm-s-ambiance .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-ambiance .CodeMirror-line::-moz-selection, .cm-s-ambiance .CodeMirror-line > span::-moz-selection, .cm-s-ambiance .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } + +/* Editor styling */ + +.cm-s-ambiance.CodeMirror { + line-height: 1.40em; + color: #E6E1DC; + background-color: #202020; + -webkit-box-shadow: inset 0 0 10px black; + -moz-box-shadow: inset 0 0 10px black; + box-shadow: inset 0 0 10px black; +} + +.cm-s-ambiance .CodeMirror-gutters { + background: #3D3D3D; + border-right: 1px solid #4D4D4D; + box-shadow: 0 10px 20px black; +} + +.cm-s-ambiance .CodeMirror-linenumber { + text-shadow: 0px 1px 1px #4d4d4d; + color: #111; + padding: 0 5px; +} + +.cm-s-ambiance .CodeMirror-guttermarker { color: #aaa; } +.cm-s-ambiance .CodeMirror-guttermarker-subtle { color: #111; } + +.cm-s-ambiance .CodeMirror-cursor { border-left: 1px solid #7991E8; } + +.cm-s-ambiance .CodeMirror-activeline-background { + background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031); +} + +.cm-s-ambiance.CodeMirror, +.cm-s-ambiance .CodeMirror-gutters { + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAQAAAAHUWYVAABFFUlEQVQYGbzBCeDVU/74/6fj9HIcx/FRHx9JCFmzMyGRURhLZIkUsoeRfUjS2FNDtr6WkMhO9sm+S8maJfu+Jcsg+/o/c+Z4z/t97/vezy3z+z8ekGlnYICG/o7gdk+wmSHZ1z4pJItqapjoKXWahm8NmV6eOTbWUOp6/6a/XIg6GQqmenJ2lDHyvCFZ2cBDbmtHA043VFhHwXxClWmeYAdLhV00Bd85go8VmaFCkbVkzlQENzfBDZ5gtN7HwF0KDrTwJ0dypSOzpaKCMwQHKTIreYIxlmhXTzTWkVm+LTynZhiSBT3RZQ7aGfjGEd3qyXQ1FDymqbKxpspERQN2MiRjNZlFFQXfCNFm9nM1zpAsoYjmtRTc5ajwuaXc5xrWskT97RaKzAGe5ARHhVUsDbjKklziiX5WROcJwSNCNI+9w1Jwv4Zb2r7lCMZ4oq5C0EdTx+2GzNuKpJ+iFf38JEWkHJn9DNF7mmBDITrWEg0VWL3pHU20tSZnuqWu+R3BtYa8XxV1HO7GyD32UkOpL/yDloINFTmvtId+nmAjxRw40VMwVKiwrKLE4bK5UOVntYwhOcSSXKrJHKPJedocpGjVz/ZMIbnYUPB10/eKCrs5apqpgVmWzBYWpmtKHecJPjaUuEgRDDaU0oZghCJ6zNMQ5ZhDYx05r5v2muQdM0EILtXUsaKiQX9WMEUotagQzFbUNN6NUPC2nm5pxEWGCjMc3GdJHjSU2kORLK/JGSrkfGEIjncU/CYUnOipoYemwj8tST9NsJmB7TUVXtbUtXATJVZXBMvYeTXJfobgJUPmGMP/yFaWonaa6BcFO3nqcIqCozSZoZoSr1g4zJOzuyGnxTEX3lUEJ7WcZgme8ddaWvWJo2AJR9DZU3CUIbhCSG6ybSwN6qtJVnCU2svDTP2ZInOw2cBTrqtQahtNZn9NcJ4l2NaSmSkkP1noZWnVwkLmdUPOwLZEwy2Z3S3R+4rIG9hcbpPXHFVWcQdZkn2FOta3cKWQnNRC5g1LsJah4GCzSVsKnCOY5OAFRTBekyyryeyilhFKva75r4Mc0aWanGEaThcy31s439KKxTzJYY5WTHPU1FtIHjQU3Oip4xlNzj/lBw23dYZVliQa7WAXf4shetcQfatI+jWRDBPmyNeW6A1P5kdDgyYJlba0BIM8BZu1JfrFwItyjcAMR3K0BWOIrtMEXyhyrlVEx3ui5dUBjmB/Q3CXW85R4mBD0s7B+4q5tKUjOlb9qqmhi5AZ6GFIC5HXtOobdYGlVdMVbNJ8toNTFcHxnoL+muBagcctjWnbNMuR00uI7nQESwg5q2qqrKWIfrNUmeQocY6HuyxJV02wj36w00yhpmUFenv4p6fUkZYqLyuinx2RGOjhCXYyJF84oiU00YMOOhhquNdfbOB7gU88pY4xJO8LVdp6/q2voeB4R04vIdhSE40xZObx1HGGJ/ja0LBthFInKaLPPFzuCaYaoj8JjPME8yoyxo6zlBqkiUZYgq00OYMswbWO5NGmq+xhipxHLRW29ARjNKXO0wRnear8XSg4XFPLKEPUS1GqvyLwiuBUoa7zpZ0l5xxFwWmWZC1H5h5FwU8eQ7K+g8UcVY6TMQreVQT/8uQ8Z+ALIXnSEa2pYZQneE9RZbSBNYXfWYJzW/h/4j4Dp1tYVcFIC5019Vyi4ThPqSFCzjGWaHQTBU8q6vrVwgxP9Lkm840imWKpcLCjYTtrKuwvsKSnrvHCXGkSMk9p6lhckfRpIeis+N2PiszT+mFLspyGleUhDwcLrZqmyeylxwjBcKHEapqkmyangyLZRVOijwOtCY5SsG5zL0OwlCJ4y5KznF3EUNDDrinwiyLZRzOXtlBbK5ITHFGLp8Q0R6ab6mS7enI2cFrxOyHvOCFaT1HThS1krjCwqWeurCkk+willhCC+RSZnRXBiZaC5RXRIZYKp2lyfrHwiKPKR0JDzrdU2EFgpidawlFDR6FgXUMNa+g1FY3bUQh2cLCwosRdnuQTS/S+JVrGLeWIvtQUvONJxlqSQYYKpwoN2kaocLjdVsis4Mk80ESF2YpSkzwldjHkjFCUutI/r+EHDU8oCs6yzL3PhWiEooZdFMkymlas4AcI3KmoMMNSQ3tHzjGWCrcJJdYyZC7QFGwjRL9p+MrRkAGWzIaWCn9W0F3TsK01c2ZvQw0byvxuQU0r1lM0qJO7wW0kRIMdDTtXEdzi4VIh+EoIHm0mWtAtpCixlabgn83fKTI7anJe9ST7WIK1DMGpQmYeA58ImV6ezOGOzK2Kgq01pd60cKWiUi9Lievb/0vIDPHQ05Kzt4ddPckQBQtoaurjyHnek/nKzpQLrVgKPjIkh2v4uyezpv+Xoo7fPFXaGFp1vaLKxQ4uUpQQS5VuQs7BCq4xRJv7fwpVvvFEB3j+620haOuocqMhWd6TTPAEx+mdFNGHdranFe95WrWmIvlY4F1Dle2ECgc6cto7SryuqGGGha0tFQ5V53migUKmg6XKAo4qS3mik+0OZpAhOLeZKicacgaYcyx5hypYQE02ZA4xi/pNhOQxR4klNKyqacj+mpxnLTnnGSo85++3ZCZq6lrZkXlGEX3o+C9FieccJbZWVFjC0Yo1FZnJhoYMFoI1hEZ9r6hwg75HwzBNhbZCdJEfJwTPGzJvaKImw1yYX1HDAmpXR+ZJQ/SmgqMNVQb5vgamGwLtt7VwvP7Qk1xpiM5x5Cyv93E06MZmgs0Nya2azIKOYKCGBQQW97RmhKNKF02JZqHEJ4o58qp7X5EcZmc56trXEqzjCBZ1MFGR87Ql2tSTs6CGxS05PTzRQorkbw7aKoKXFDXsYW42VJih/q+FP2BdTzDTwVqOYB13liM50vG7wy28qagyuIXMeQI/Oqq8bcn5wJI50xH00CRntyfpL1T4hydYpoXgNiFzoIUTDZnLNRzh4TBHwbYGDvZkxmlyJloyr6tRihpeUG94GnKtIznREF0tzJG/OOr73JBcrSh1k6WuTprgLU+mnSGnv6Zge0NNz+kTDdH8nuAuTdJDCNb21LCiIuqlYbqGzT3RAoZofQfjFazkqeNWdYaGvYTM001EW2oKPvVk1ldUGSgUtHFwjKM1h9jnFcmy5lChoLNaQMGGDsYbKixlaMBmmsx1QjCfflwTfO/gckW0ruZ3jugKR3R5W9hGUWqCgxuFgsuaCHorotGKzGaeZB9DMsaTnKCpMtwTvOzhYk0rdrArKCqcaWmVk1+F372ur1YkKxgatI8Qfe1gIX9wE9FgS8ESmuABIXnRUbCapcKe+nO7slClSZFzpV/LkLncEb1qiO42fS3R855Su2mCLh62t1SYZZYVmKwIHjREF2uihTzB20JOkz7dkxzYQnK0UOU494wh+VWRc6Un2kpTaVgLDFEkJ/uhzRcI0YKGgpGWOlocBU/a4fKoJ/pEaNV6jip3+Es9VXY078rGnmAdf7t9ylPXS34RBSuYPs1UecZTU78WanhBCHpZ5sAoTz0LGZKjPf9TRypqWEiTvOFglL1fCEY3wY/++rbk7C8bWebA6p6om6PgOL2kp44TFJlVNBXae2rqqdZztOJpT87GQsE9jqCPIe9VReZuQ/CIgacsyZdCpIScSYqcZk8r+nsyCzhyfhOqHGOIvrLknC8wTpFcaYiGC/RU1NRbUeUpocQOnkRpGOrIOcNRx+1uA0UrzhSSt+VyS3SJpnFWkzNDqOFGIWcfR86DnmARTQ1HKIL33ExPiemeOhYSSjzlSUZZuE4TveoJLnBUOFof6KiysCbnAEcZgcUNTDOwkqWu3RWtmGpZwlHhJENdZ3miGz0lJlsKnjbwqSHQjpxnFDlTLLwqJPMZMjd7KrzkSG7VsxXBZE+F8YZkb01Oe00yyRK9psh5SYh29ySPKBo2ylNht7ZkZnsKenjKNJu9PNEyZpaCHv4Kt6RQsLvAVp7M9kIimmCUwGeWqLMmGuIotYMmWNpSahkhZw9FqZsVnKJhsjAHvtHMsTM9fCI06Dx/u3vfUXCqfsKRc4oFY2jMsoo/7DJDwZ1CsIKnJu+J9ldkpmiCxQx1rWjI+T9FwcWWzOuaYH0Hj7klNRVWEQpmaqosakiGNTFHdjS/qnUdmf0NJW5xsL0HhimCCZZSRzmSPTXJQ4aaztAwtZnoabebJ+htCaZ7Cm535ByoqXKbX1WRc4Eh2MkRXWzImVc96Cj4VdOKVxR84VdQsIUM8Psoou2byVHyZFuq7O8otbSQ2UAoeEWTudATLGSpZzVLlXVkPU2Jc+27lsw2jmg5T5VhbeE3BT083K9WsTTkFU/Osi0rC5lRlpwRHUiesNS0sOvmqGML1aRbPAxTJD9ZKtxuob+hhl8cwYGWpJ8nub7t5p6coYbMovZ1BTdaKn1jYD6h4GFDNFyT/Kqe1XCXphXHOKLZmuRSRdBPEfVUXQzJm5YGPGGJdvAEr7hHNdGZnuBvrpciGmopOLf5N0uVMy0FfYToJk90uUCbJupaVpO53UJXR2bVpoU00V2KOo4zMFrBd0Jtz2pa0clT5Q5L8IpQ177mWQejPMEJhuQjS10ref6HHjdEhy1P1EYR7GtO0uSsKJQYLiTnG1rVScj5lyazpqWGl5uBbRWl7m6ixGOOnEsMJR7z8J0n6KMnCdxhiNYQCoZ6CmYLnO8omC3MkW3bktlPmEt/VQQHejL3+dOE5FlPdK/Mq8hZxxJtLyRrepLThYKbLZxkSb5W52vYxNOaOxUF0yxMUPwBTYqCzy01XayYK0sJyWBLqX0MwU5CzoymRzV0EjjeUeLgDpTo6ij42ZAzvD01dHUUTPLU96MdLbBME8nFBn7zJCMtJcZokn8YoqU0FS5WFKyniHobguMcmW8N0XkWZjkyN3hqOMtS08r+/xTBwpZSZ3qiVRX8SzMHHjfUNFjgHEPmY9PL3ykEzxkSre/1ZD6z/NuznuB0RcE1TWTm9zRgfUWVJiG6yrzgmWPXC8EAR4Wxhlad0ZbgQyEz3pG5RVEwwDJH2mgKpjcTiCOzn1lfUWANFbZ2BA8balnEweJC9J0iuaeZoI+ippFCztEKVvckR2iice1JvhVytrQwUAZpgsubCPaU7xUe9vWnaOpaSBEspalykhC9bUlOMpT42ZHca6hyrqKmw/wMR8H5ZmdFoBVJb03O4UL0tSNnvIeRmkrLWqrs78gcrEn2tpcboh0UPOW3UUR9PMk4T4nnNKWmCjlrefhCwxRNztfmIQVdDElvS4m1/WuOujoZCs5XVOjtKPGokJzsYCtFYoWonSPT21DheU/wWhM19FcElwqNGOsp9Q8N/cwXaiND1MmeL1Q5XROtYYgGeFq1aTMsoMmcrKjQrOFQTQ1fmBYhmW6o8Jkjc7iDJRTBIo5kgJD5yMEYA3srCg7VFKwiVJkmRCc5ohGOKhsYMn/XBLdo5taZjlb9YAlGWRimqbCsoY7HFAXLa5I1HPRxMMsQDHFkWtRNniqT9UEeNjcE7RUlrCJ4R2CSJuqlKHWvJXjAUNcITYkenuBRB84TbeepcqTj3zZyFJzgYQdHnqfgI0ddUwS6GqWpsKWhjq9cV0vBAEMN2znq+EBfIWT+pClYw5xsTlJU6GeIBsjGmmANTzJZiIYpgrM0Oa8ZMjd7NP87jxhqGOhJlnQtjuQpB+8aEE00wZFznSJPyHxgH3HkPOsJFvYk8zqCHzTs1BYOa4J3PFU+UVRZxlHDM4YavlNUuMoRveiZA2d7grMNc2g+RbSCEKzmgYsUmWmazFJyoiOZ4KnyhKOGRzWJa0+moyV4TVHDzn51Awtqaphfk/lRQ08FX1iiqxTB/kLwd0VynKfEvI6cd4XMV5bMhZ7gZUWVzYQ6Nm2BYzxJbw3bGthEUUMfgbGeorae6DxHtJoZ6alhZ0+ytiVoK1R4z5PTrOECT/SugseEOlb1MMNR4VRNcJy+V1Hg9ONClSZFZjdHlc6W6FBLdJja2MC5hhpu0DBYEY1TFGwiFAxRRCsYkiM9JRb0JNMVkW6CZYT/2EiTGWmo8k+h4FhDNE7BvppoTSFnmCV5xZKzvcCdDo7VVPnIU+I+Rc68juApC90MwcFCsJ5hDqxgScYKreruyQwTqrzoqDCmhWi4IbhB0Yrt3RGa6GfDv52rKXWhh28dyZaWUvcZeMTBaZoSGyiCtRU5J8iviioHaErs7Jkj61syVzTTgOcUOQ8buFBTYWdL5g3T4qlpe0+wvD63heAXRfCCIed9RbCsp2CiI7raUOYOTU13N8PNHvpaGvayo4a3LLT1lDrVEPT2zLUlheB1R+ZTRfKWJ+dcocLJfi11vyJ51lLqJ0WD7tRwryezjiV5W28uJO9qykzX8JDe2lHl/9oyBwa2UMfOngpXCixvKdXTk3wrsKmiVYdZIqsoWEERjbcUNDuiaQomGoIbFdEHmsyWnuR+IeriKDVLnlawlyNHKwKlSU631PKep8J4Q+ayjkSLKYLhalNHlYvttb6fHm0p6OApsZ4l2VfdqZkjuysy6ysKLlckf1KUutCTs39bmCgEyyoasIWlVaMF7mgmWtBT8Kol5xpH9IGllo8cJdopcvZ2sImlDmMIbtDk3KIpeNiS08lQw11NFPTwVFlPP6pJ2gvRfI7gQUfmNAtf6Gs0wQxDsKGlVBdF8rCa3jzdwMaGHOsItrZk7hAyOzpK9VS06j5F49b0VNGOOfKs3lDToMsMBe9ZWtHFEgxTJLs7qrygKZjUnmCYoeAqeU6jqWuLJup4WghOdvCYJnrSkSzoyRkm5M2StQwVltPkfCAk58tET/CSg+8MUecmotMEnhBKfWBIZsg2ihruMJQaoIm+tkTLKEqspMh00w95gvFCQRtDwTT1gVDDSEVdlwqZfxoQRbK0g+tbiBZxzKlpnpypejdDwTaeOvorMk/IJE10h9CqRe28hhLbe0pMsdSwv4ZbhKivo2BjDWfL8UKJgeavwlwb5KlwhyE4u4XkGE2ytZCznKLCDZZq42VzT8HLCrpruFbIfOIINmh/qCdZ1ZBc65kLHR1Bkyf5zn6pN3SvGKIlFNGplhrO9QSXanLOMQTLCa0YJCRrCZm/CZmrLTm7WzCK4GJDiWUdFeYx1LCFg3NMd0XmCuF3Y5rITLDUsYS9zoHVzwnJoYpSTQoObyEzr4cFBNqYTopoaU/wkyLZ2lPhX/5Y95ulxGTV7KjhWrOZgl8MyUUafjYraNjNU1N3IWcjT5WzWqjwtoarHSUObGYO3GCJZpsBlnJGPd6ZYLyl1GdCA2625IwwJDP8GUKymbzuyPlZlvTUsaUh5zFDhRWFzPKKZLAlWdcQbObgF9tOqOsmB1dqcqYJmWstFbZRRI9poolmqiLnU0POvxScpah2iSL5UJNzgScY5+AuIbpO0YD3NCW+dLMszFSdFCWGqG6eVq2uYVNDdICGD6W7EPRWZEY5gpsE9rUkS3mijzzJnm6UpUFXG1hCUeVoS5WfNcFpblELL2qqrCvMvRfd45oalvKU2tiQ6ePJOVMRXase9iTtLJztPxJKLWpo2CRDcJwn2sWSLKIO1WQWNTCvpVUvOZhgSC40JD0dOctaSqzkCRbXsKlb11Oip6PCJ0IwSJM31j3akRxlP7Rwn6aGaUL0qiLnJkvB3xWZ2+Q1TfCwpQH3G0o92UzmX4o/oJNQMMSQc547wVHhdk+VCw01DFYEnTxzZKAm74QmeNNR1w6WzEhNK15VJzuCdxQ53dRUDws5KvwgBMOEgpcVNe0hZI6RXT1Jd0cyj5nsaEAHgVmGaJIlWdsc5Ui2ElrRR6jrRAttNMEAIWrTDFubkZaok7/AkzfIwfuWVq0jHzuCK4QabtLUMVPB3kJ0oyHTSVFlqMALilJf2Rf8k5aaHtMfayocLBS8L89oKoxpJvnAkDPa0qp5DAUTHKWmCcnthlou8iCKaFFLHWcINd1nyIwXqrSxMNmSs6KmoL2QrKuWtlQ5V0120xQ5vRyZS1rgFkWwhiOwiuQbR0OOVhQM9iS3tiXp4RawRPMp5tDletOOBL95MpM01dZTBM9pkn5qF010rIeHFcFZhmSGpYpTsI6nwhqe5C9ynhlpp5ophuRb6WcJFldkVnVEwwxVfrVkvnWUuNLCg5bgboFHPDlDPDmnK7hUrWiIbjadDclujlZcaokOFup4Ri1kacV6jmrrK1hN9bGwpKEBQ4Q6DvIUXOmo6U5LqQM6EPyiKNjVkPnJkDPNEaxhiFay5ExW1NXVUGqcpYYdPcGiCq7z/TSlbhL4pplWXKd7NZO5QQFrefhRQW/NHOsqcIglc4UhWklR8K0QzbAw08CBDnpbgqXdeD/QUsM4RZXDFBW6WJKe/mFPdH0LtBgiq57wFLzlyQzz82qYx5D5WJP5yVJDW01BfyHnS6HKO/reZqId1WGa4Hkh2kWodJ8i6KoIPlAj2hPt76CzXsVR6koPRzWTfKqIentatYpQw2me4AA3y1Kind3SwoOKZDcFXTwl9tWU6mfgRk9d71sKtlNwrjnYw5tC5n5LdKiGry3JKNlHEd3oaMCFHrazBPMp/uNJ+V7IudcSbeOIdjUEdwl0VHCOZo5t6YluEuaC9mQeMgSfOyKnYGFHcIeQ84yQWbuJYJpZw5CzglDH7gKnWqqM9ZTaXcN0TeYhR84eQtJT76JJ1lREe7WnnvsMmRc9FQ7SBBM9mV3lCUdmHk/S2RAMt0QjFNFqQpWjDPQ01DXWUdDBkXziKPjGEP3VP+zIWU2t7im41FOloyWzn/L6dkUy3VLDaZ6appgDLHPjJEsyvJngWEPUyVBiAaHCTEXwrLvSEbV1e1gKJniicWorC1MUrVjB3uDhJE/wgSOzk1DXpk0k73qCM8xw2UvD5kJmDUfOomqMpWCkJRlvKXGmoeBm18USjVIk04SClxTB6YrgLAPLWYK9HLUt5cmc0vYES8GnTeRc6skZbQkWdxRsIcyBRzx1DbTk9FbU0caTPOgJHhJKnOGIVhQqvKmo0llRw9sabrZkDtdg3PqaKi9oatjY8B+G371paMg6+mZFNNtQ04mWBq3rYLOmtWWQp8KJnpy9DdFensyjdqZ+yY40VJlH8wcdLzC8PZnvHMFUTZUrDTkLyQaGus5X5LzpYAf3i+e/ZlhqGqWhh6Ou6xTR9Z6oi5AZZtp7Mj2EEm8oSpxiYZCHU/1fbGdNNNRRoZMhmilEb2gqHOEJDtXkHK/JnG6IrvbPCwV3NhONVdS1thBMs1T4QOBcTWa2IzhMk2nW5Kyn9tXUtpv9RsG2msxk+ZsQzRQacJncpgke0+T8y5Fzj8BiGo7XlJjaTIlpQs7KFjpqGnKuoyEPeIKnFMkZHvopgh81ySxNFWvJWcKRs70j2FOT012IllEEO1n4pD1513Yg2ssQPOThOkvyrqHUdEXOSEsihmBbTbKX1kLBPWqWkLOqJbjB3GBIZmoa8qWl4CG/iZ7oiA72ZL7TJNeZUY7kFQftDcHHluBzRbCegzMtrRjVQpX2lgoPKKLJAkcbMl01XK2p7yhL8pCBbQ3BN2avJgKvttcrWDK3CiUOVxQ8ZP+pqXKyIxnmBymCg5vJjNfkPK4+c8cIfK8ocVt7kmfd/I5SR1hKvCzUtb+lhgc00ZaO6CyhIQP1Uv4yIZjload72PXX0OIJvnFU+0Zf6MhsJwTfW0r0UwQfW4LNLZl5HK261JCZ4qnBaAreVAS3WrjV0LBnNDUNNDToCEeFfwgcb4gOEqLRhirWkexrCEYKVV711DLYEE1XBEsp5tpTGjorkomKYF9FDXv7fR3BGwbettSxnyL53MBPjsxDZjMh+VUW9NRxq1DhVk+FSxQcaGjV9Pawv6eGByw5qzoy7xk4RsOShqjJwWKe/1pEEfzkobeD/dQJmpqedcyBTy2sr4nGNRH0c0SPWTLrqAc0OQcb/gemKgqucQT7ySWKCn2EUotoCvpZct7RO2sy/QW0IWcXd7pQRQyZVwT2USRO87uhjioTLKV2brpMUcMQRbKH/N2T+UlTpaMls6cmc6CCNy3JdYYSUzzJQ4oSD3oKLncULOiJvjBEC2oqnCJkJluCYy2ZQ5so9YYlZ1VLlQU1mXEW1jZERwj/MUSRc24TdexlqLKfQBtDTScJUV8FszXBEY5ktpD5Ur9hYB4Nb1iikw3JoYpkKX+RodRKFt53MMuRnKSpY31PwYaGaILh3wxJGz9TkTPEETxoCWZrgvOlmyMzxFEwVJE5xZKzvyJ4WxEc16Gd4Xe3Weq4XH2jKRikqOkGQ87hQnC7wBmGYLAnesX3M+S87eFATauuN+Qcrh7xIxXJbUIdMw3JGE3ylCWzrieaqCn4zhGM19TQ3z1oH1AX+pWEqIc7wNGAkULBo/ZxRaV9NNyh4Br3rCHZzbzmSfawBL0dNRwpW1kK9mxPXR9povcdrGSZK9c2k0xwFGzjuniCtRSZCZ6ccZ7gaktmgAOtKbG/JnOkJrjcQTdFMsxRQ2cLY3WTIrlCw1eWKn8R6pvt4GFDso3QoL4a3nLk3G6JrtME3dSenpx7PNFTmga0EaJTLQ061sEeQoWXhSo9LTXsaSjoJQRXeZLtDclbCrYzfzHHeaKjHCVOUkQHO3JeEepr56mhiyaYYKjjNU+Fed1wS5VlhWSqI/hYUdDOkaxiKehoyOnrCV5yBHtbWFqTHCCwtpDcYolesVR5yUzTZBb3RNMd0d6WP+SvhuBmRcGxnuQzT95IC285cr41cLGQ6aJJhmi4TMGempxeimBRQw1tFKV+8jd6KuzoSTqqDxzRtpZkurvKEHxlqXKRIjjfUNNXQsNOsRScoWFLT+YeRZVD3GRN0MdQcKqQjHDMrdGGVu3iYJpQx3WGUvfbmxwFfR20WBq0oYY7LMFhhgYtr8jpaEnaOzjawWWaTP8mMr0t/EPDPoqcnxTBI5o58L7uoWnMrpoqPwgVrlAUWE+V+TQl9rawoyP6QGAlQw2TPRX+YSkxyBC8Z6jhHkXBgQL7WII3DVFnRfCrBfxewv9D6xsyjys4VkhWb9pUU627JllV0YDNHMku/ldNMMXDEo4aFnAkk4U6frNEU4XgZUPmEKHUl44KrzmYamjAbh0JFvGnaTLPu1s9jPCwjFpYiN7z1DTOk/nc07CfDFzmCf7i+bfNHXhDtLeBXzTBT5rkMvWOIxpl4EMh2LGJBu2syDnAEx2naEhHDWMMzPZEhygyS1mS5RTJr5ZkoKbEUoYqr2kqdDUE8ztK7OaIntJkFrIECwv8LJTaVx5XJE86go8dFeZ3FN3rjabCAYpoYEeC9zzJVULBbmZhDyd7ko09ydpNZ3nm2Kee4FPPXHnYEF1nqOFEC08LUVcDvYXkJHW8gTaKCk9YGOeIJhqiE4ToPEepdp7IWFjdwnWaufGMwJJCMtUTTBBK9BGCOy2tGGrJTHIwyEOzp6aPzNMOtlZkDvcEWpP5SVNhfkvDxhmSazTJXYrM9U1E0xwFVwqZQwzJxw6+kGGGUj2FglGGmnb1/G51udRSMNlTw6GGnCcUwVcOpmsqTHa06o72sw1RL02p9z0VbnMLOaIX3QKaYKSCFQzBKEUNHTSc48k53RH9wxGMtpQa5KjjW0W0n6XCCCG4yxNNdhQ4R4l1Ff+2sSd6UFHiIEOyqqFgT01mEUMD+joy75jPhOA+oVVLm309FR4yVOlp4RhLiScNmSmaYF5Pw0STrOIoWMSR2UkRXOMp+M4SHW8o8Zoi6OZgjKOaFar8zZDzkWzvKOjkKBjmCXby8JahhjXULY4KlzgKLvAwxVGhvyd4zxB1d9T0piazmKLCVZY5sKiD0y2ZSYrkUEPUbIk+dlQ4SJHTR50k1DPaUWIdTZW9NJwnJMOECgd7ou/MnppMJ02O1VT4Wsh85MnZzcFTngpXGKo84qmwgKbCL/orR/SzJ2crA+t6Mp94KvxJUeIbT3CQu1uIdlQEOzlKfS3UMcrTiFmOuroocrZrT2AcmamOKg8YomeEKm/rlT2sociMaybaUlFhuqHCM2qIJ+rg4EcDFymiDSxzaHdPcpE62pD5kyM5SBMoA1PaUtfIthS85ig1VPiPPYXgYEMNk4Qq7TXBgo7oT57gPUdwgCHzhIVFPFU6OYJzHAX9m5oNrVjeE61miDrqQ4VSa1oiURTsKHC0IfjNwU2WzK6eqK8jWln4g15TVBnqmDteCJ501PGAocJhhqjZdtBEB6lnhLreFJKxmlKbeGrqLiSThVIbCdGzloasa6lpMQXHCME2boLpJgT7yWaemu6wBONbqGNVRS0PKIL7LckbjmQtR7K8I5qtqel+T/ChJTNIKLjdUMNIRyvOEko9YYl2cwQveBikCNawJKcLBbc7+JM92mysNvd/Fqp8a0k6CNEe7cnZrxlW0wQXaXjaktnRwNOGZKYiONwS7a1JVheq3WgJHlQUGKHKmp4KAxXR/ULURcNgoa4zhKSLpZR3kxRRb0NmD0OFn+UCS7CzI1nbP6+o4x47QZE5xRCt3ZagnYcvmpYQktXdk5YKXTzBC57kKEe0VVuiSYqapssMS3C9p2CKkHOg8B8Pa8p5atrIw3qezIWanMGa5HRDNF6RM9wcacl0N+Q8Z8hsIkSnaIIdHRUOEebAPy1zbCkhM062FCJtif7PU+UtoVXzWKqM1PxXO8cfdruhFQ/a6x3JKYagvVDhQEtNiyiiSQ7OsuRsZUku0CRNDs4Sog6KKjsZgk2bYJqijgsEenoKeniinRXBn/U3lgpPdyDZynQx8IiioMnCep5Ky8mjGs6Wty0l1hUQTcNWswS3WRp2kCNZwJG8omG8JphPUaFbC8lEfabwP7VtM9yoaNCAjpR41VNhrD9LkbN722v0CoZMByFzhaW+MyzRYEWFDQwN2M4/JiT76PuljT3VU/A36eaIThb+R9oZGOAJ9tewkgGvqOMNRWYjT/Cwu99Q8LqDE4TgbLWxJ1jaDDAERsFOFrobgjUsBScaguXU8kKm2RL19tRypSHnHNlHiIZqgufs4opgQdVdwxBNNFBR6kVFqb8ogimOzB6a6HTzrlDHEpYaxjiiA4TMQobkDg2vejjfwJGWmnbVFAw3H3hq2NyQfG7hz4aC+w3BbwbesG0swYayvpAs6++Ri1Vfzx93mFChvyN5xVHTS+0p9aqCAxyZ6ZacZyw5+7uuQkFPR9DDk9NOiE7X1PCYJVjVUqq7JlrHwWALF5nfHNGjApdpqgzx5OwilDhCiDYTgnc9waGW4BdLNNUQvOtpzDOWHDH8D7TR/A/85KljEQu3NREc4Pl/6B1Hhc8Umb5CsKMmGC9EPcxoT2amwHNCmeOEnOPbklnMkbOgIvO5UMOpQrS9UGVdt6iH/fURjhI/WOpaW9OKLYRod6HCUEdOX000wpDZQ6hwg6LgZfOqo1RfT/CrJzjekXOGhpc1VW71ZLbXyyp+93ILbC1kPtIEYx0FIx1VDrLoVzXRKRYWk809yYlC9ImcrinxtabKnzRJk3lAU1OLEN1j2zrYzr2myHRXJFf4h4QKT1qSTzTB5+ZNTzTRkAxX8FcLV2uS8eoQQ2aAkFzvCM72sJIcJET3WPjRk5wi32uSS9rfZajpWEvj9hW42F4o5NytSXYy8IKHay10VYdrcl4SkqscrXpMwyGOgtkajheSxdQqmpxP1L3t4R5PqasFnrQEjytq6qgp9Y09Qx9o4S1FzhUCn1kyHSzBWLemoSGvOqLNhZyBjmCaAUYpMgt4Ck7wBBMMwWKWgjsUwTaGVsxWC1mYoKiyqqeGKYqonSIRQ3KIkHO0pmAxTdBHkbOvfllfr+AA+7gnc50huVKYK393FOyg7rbPO/izI7hE4CnHHHnJ0ogNPRUGeUpsrZZTBJcrovUcJe51BPsr6GkJdhCCsZ6aTtMEb2pqWkqeVtDXE/QVggsU/Nl86d9RMF3DxvZTA58agu810RWawCiSzzXBeU3MMW9oyJUedvNEvQyNu1f10BSMddR1vaLCYpYa/mGocLSiYDcLbQz8aMn5iyF4xBNMs1P0QEOV7o5gaWGuzSeLue4tt3ro7y4Tgm4G/mopdZgl6q0o6KzJWE3mMksNr3r+a6CbT8g5wZNzT9O7fi/zpaOmnz3BRoqos+tv9zMbdpxsqDBOEewtJLt7cg5wtKKbvldpSzRRCD43VFheCI7yZLppggMVBS/KMAdHODJvOwq2NQSbKKKPLdFWQs7Fqo+mpl01JXYRgq8dnGLhTiFzqmWsUMdpllZdbKlyvSdYxhI9YghOtxR8LgSLWHK62mGGVoxzBE8LNWzqH9CUesQzFy5RQzTc56mhi6fgXEWwpKfE5Z7M05ZgZUPmo6auiv8YKzDYwWBLMErIbKHJvOwIrvEdhOBcQ9JdU1NHQ7CXn2XIDFBKU2WAgcX9UAUzDXWd5alwuyJ41Z9rjKLCL4aCp4WarhPm2rH+SaHUYE001JDZ2ZAzXPjdMpZWvC9wmqIB2lLhQ01D5jO06hghWMndbM7yRJMsoCj1vYbnFQVrW9jak3OlEJ3s/96+p33dEPRV5GxiqaGjIthUU6FFEZyqCa5qJrpBdzSw95IUnOPIrCUUjRZQFrbw5PR0R1qiYx3cb6nrWUMrBmmiBQxVHtTew5ICP/ip6g4hed/Akob/32wvBHsIOX83cI8hGeNeNPCIkPmXe8fPKx84OMSRM1MTdXSwjCZ4S30jVGhvqTRak/OVhgGazHuOCud5onEO1lJr6ecVyaOK6H7zqlBlIaHE0oroCgfvGJIdPcmfLNGLjpz7hZwZQpUbFME0A1cIJa7VNORkgfsMBatbKgwwJM9bSvQXeNOvbIjelg6WWvo5kvbKaJJNHexkKNHL9xRyFlH8Ti2riB5wVPhUk7nGkJnoCe428LR/wRGdYIlmWebCyxou1rCk4g/ShugBDX0V0ZQWkh0dOVsagkM0yV6OoLd5ye+pRlsCr0n+KiQrGuq5yJDzrTAXHtLUMduTDBVKrSm3eHL+6ijxhFDX9Z5gVU/wliHYTMiMFpKLNMEywu80wd3meoFmt6VbRMPenhrOc6DVe4pgXU8DnnHakLOIIrlF4FZPIw6R+zxBP0dyq6OOZ4Q5sLKCcz084ok+VsMMyQhNZmmBgX5xIXOEJTmi7VsGTvMTNdHHhpzdbE8Du2oKxgvBqQKdDDnTFOylCFaxR1syz2iqrOI/FEpNc3C6f11/7+ASS6l2inq2ciTrCCzgyemrCL5SVPjQkdPZUmGy2c9Sw9FtR1sS30RmsKPCS4rkIC/2U0MduwucYolGaPjKEyhzmiPYXagyWbYz8LWBDdzRimAXzxx4z8K9hpzlhLq+NiQ97HuKorMUfK/OVvC2JfiHUPCQI/q7J2gjK+tTDNxkCc4TMssqCs4TGtLVwQihyoAWgj9bosU80XGW6Ac9TJGziaUh5+hnFcHOnlaM1iRn29NaqGENTTTSUHCH2tWTeV0osUhH6psuVLjRUmGWhm6OZEshGeNowABHcJ2Bpy2ZszRcKkRXd2QuKVEeXnbfaEq825FguqfgfE2whlChSRMdron+LATTPQ2Z369t4B9C5gs/ylzv+CMmepIDPclFQl13W0rspPd1JOcbghGOEutqCv5qacURQl3dDKyvyJlqKXGPgcM9FfawJAMVmdcspcYKOZc4GjDYkFlK05olNMHyHn4zFNykyOxt99RkHlfwmiHo60l2EKI+mhreEKp080Tbug08BVPcgoqC5zWt+NLDTZ7oNSF51N1qie7Va3uCCwyZbkINf/NED6jzOsBdZjFN8oqG3wxVunqCSYYKf3EdhJyf9YWGf7tRU2oH3VHgPr1fe5J9hOgHd7xQ0y7qBwXr23aGErP0cm64JVjZwsOGqL+mhNgZmhJLW2oY4UhedsyBgzrCKrq7BmcpNVhR6jBPq64Vgi+kn6XE68pp8J5/+0wRHGOpsKenQn9DZntPzjRLZpDAdD2fnSgkG9tmIXnUwQ6WVighs7Yi2MxQ0N3CqYaCXkJ0oyOztMDJjmSSpcpvlrk0RMMOjmArQ04PRV1DO1FwhCVaUVPpKUM03JK5SxPsIWRu8/CGHi8UHChiqGFDTbSRJWeYUDDcH6vJWUxR4k1FXbMUwV6e4AJFXS8oMqsZKqzvYQ9DDQdZckY4aGsIhtlubbd2r3j4QBMoTamdPZk7O/Bf62lacZwneNjQoGcdVU7zJOd7ghsUHOkosagic6cnWc8+4gg285R6zZP5s1/LUbCKIznTwK36PkdwlOrl4U1LwfdCCa+IrvFkmgw1PCAUXKWo0sURXWcI2muKJlgyFzhynCY4RBOsqCjoI1R5zREco0n2Vt09BQtYSizgKNHfUmUrQ5UOCh51BFcLmY7umhYqXKQomOop8bUnWNNQcIiBcYaC6xzMNOS8JQQfeqKBmmglB+97ok/lfk3ygaHSyZaCRTzRxQo6GzLfa2jWBPepw+UmT7SQEJyiyRkhBLMVOfcoMjcK0eZChfUNzFAUzCsEN5vP/X1uP/n/aoMX+K+nw/Hjr/9xOo7j7Pju61tLcgvJpTWXNbfN5jLpi6VfCOviTktKlFusQixdEKWmEBUKNaIpjZRSSOXSgzaaKLdabrm1/9nZ+/f+vd/vz/v9+Xy+zZ7PRorYoZqyLrCwQdEAixxVOEXNNnjX2nUSRlkqGmWowk8lxR50JPy9Bo6qJXaXwNvREBvnThPEPrewryLhcAnj5WE15Fqi8W7R1sAuEu86S4ENikItFN4xkv9Af4nXSnUVcLiA9xzesFpivRRVeFKtsMRaKBhuSbjOELnAUtlSQUpXgdfB4Z1oSbnFEetbQ0IrAe+Y+pqnDcEJFj6S8LDZzZHwY4e3XONNlARraomNEt2bkvGsosA3ioyHm+6jCMbI59wqt4eeara28IzEmyPgoRaUOEDhTVdEJhmCoTWfC0p8aNkCp0oYqih2iqGi4yXeMkOsn4LdLLnmKfh/YogjNsPebeFGR4m9BJHLzB61XQ3BtpISfS2FugsK9FAtLWX1dCRcrCnUp44CNzuCowUZmxSRgYaE6Za0W2u/E7CVXCiI/UOR8aAm1+OSyE3mOUcwyc1zBBeoX1kiKy0Zfxck1Gsyulti11i83QTBF5Kg3pDQThFMVHiPSlK+0cSedng/VaS8bOZbtsBcTcZAR8JP5KeqQ1OYKAi20njdNNRpgnsU//K+JnaXJaGTomr7aYIphoRn9aeShJWKEq9LcozSF7QleEfDI5LYm5bgVkFkRwVDBCVu0DDIkGupo8TZBq+/pMQURYErJQmPKGKjNDkWOLx7Jd5QizdUweIaKrlP7SwJDhZvONjLkOsBBX9UpGxnydhXkfBLQ8IxgojQbLFnJf81JytSljclYYyEFyx0kVBvKWOFJmONpshGAcsduQY5giVNCV51eOdJYo/pLhbvM0uDHSevNKRcrKZIqnCtJeEsO95RoqcgGK4ocZcho1tTYtcZvH41pNQ7vA0WrhIfOSraIIntIAi+NXWCErdbkvrWwjRLrt0NKUdL6KSOscTOdMSOUtBHwL6OLA0vNSdynaWQEnCpIvKaIrJJEbvHkmuNhn6OjM8VkSGSqn1uYJCGHnq9I3aLhNME3t6GjIkO7xrNFumpyTNX/NrwX7CrIRiqqWijI9JO4d1iieykyfiposQIQ8YjjsjlBh6oHWbwRjgYJQn2NgSnNycmJAk3NiXhx44Sxykihxm8ybUwT1OVKySc7vi3OXVkdBJ4AyXBeksDXG0IhgtYY0lY5ahCD0ehborIk5aUWRJviMA7Xt5kyRjonrXENkm8yYqgs8VzgrJmClK20uMM3jRJ0FiQICQF9hdETlLQWRIb5ki6WDfWRPobvO6a4GP5mcOrNzDFELtTkONLh9dXE8xypEg7z8A9jkhrQ6Fhjlg/QVktJXxt4WXzT/03Q8IaQWSqIuEvloQ2mqC9Jfi7wRul4RX3pSPlzpoVlmCtI2jvKHCFhjcM3sN6lqF6HxnKelLjXWbwrpR4xzuCrTUZx2qq9oAh8p6ixCUGr78g8oyjRAtB5CZFwi80VerVpI0h+IeBxa6Zg6kWvpDHaioYYuEsRbDC3eOmC2JvGYLeioxGknL2UATNJN6hmtj1DlpLvDVmocYbrGCVJKOrg4X6DgddLA203BKMFngdJJFtFd7vJLm6KEpc5yjQrkk7M80SGe34X24nSex1Ra5Omgb71JKyg8SrU3i/kARKwWpH0kOGhKkObyfd0ZGjvyXlAkVZ4xRbYJ2irFMkFY1SwyWxr2oo4zlNiV+7zmaweFpT4kR3kaDAFW6xpSqzJay05FtYR4HmZhc9UxKbbfF2V8RG1MBmSaE+kmC6JnaRXK9gsiXhJHl/U0qM0WTcbyhwkYIvFGwjSbjfwhiJt8ZSQU+Bd5+marPMOkVkD0muxYLIfEuhh60x/J92itguihJSEMySVPQnTewnEm+620rTQEMsOfo4/kP/0ARvWjitlpSX7GxBgcMEsd3EEeYWvdytd+Saawi6aCIj1CkGb6Aj9rwhx16Cf3vAwFy5pyLhVonXzy51FDpdEblbkdJbUcEPDEFzQ8qNmhzzLTmmKWKbFCXeEuRabp6rxbvAtLF442QjQ+wEA9eL1xSR7Q0JXzlSHjJ4exq89yR0laScJ/FW6z4a73pFMEfDiRZvuvijIt86RaSFOl01riV2mD1UEvxGk/Geg5aWwGki1zgKPG9J2U8PEg8qYvMsZeytiTRXBMslCU8JSlxi8EabjwUldlDNLfzTUmCgxWsjqWCOHavYAqsknKFIO0yQ61VL5AVFxk6WhEaCAkdJgt9aSkzXlKNX2jEa79waYuc7gq0N3GDJGCBhoiTXUEPsdknCUE1CK0fwsiaylSF2uiDyO4XX3pFhNd7R4itFGc0k/ElBZwWvq+GC6szVeEoS/MZ+qylwpKNKv9Z469UOjqCjwlusicyTxG6VpNxcQ8IncoR4RhLbR+NdpGGmJWOcIzJGUuKPGpQg8rrG21dOMqQssJQ4RxH5jaUqnZuQ0F4Q+cjxLwPtpZbIAk3QTJHQWBE5S1BokoVtDd6lhqr9UpHSUxMcIYl9pojsb8h4SBOsMQcqvOWC2E8EVehqiJ1hrrAEbQxeK0NGZ0Gkq+guSRgniM23bIHVkqwx4hiHd7smaOyglyIyQuM978j4VS08J/A2G1KeMBRo4fBaSNhKUEZfQewVQ/C1I+MgfbEleEzCUw7mKXI0M3hd1EESVji8x5uQ41nxs1q4RMJCCXs7Iq9acpxn22oSDnQ/sJTxsCbHIYZiLyhY05TY0ZLIOQrGaSJDDN4t8pVaIrsqqFdEegtizc1iTew5Q4ayBDMUsQMkXocaYkc0hZua412siZ1rSXlR460zRJ5SlHGe5j801RLMlJTxtaOM3Q1pvxJ45zUlWFD7rsAbpfEm1JHxG0eh8w2R7QQVzBUw28FhFp5QZzq8t2rx2joqulYTWSuJdTYfWwqMFMcovFmSyJPNyLhE4E10pHzYjOC3huArRa571ZsGajQpQx38SBP5pyZB6lMU3khDnp0MBV51BE9o2E+TY5Ml2E8S7C0o6w1xvCZjf0HkVEHCzFoyNmqC+9wdcqN+Tp7jSDheE9ws8Y5V0NJCn2bk2tqSY4okdrEhx1iDN8cSudwepWmAGXKcJXK65H9to8jYQRH7SBF01ESUJdd0TayVInaWhLkOjlXE5irKGOnI6GSWGCJa482zBI9rCr0jyTVcEuzriC1vcr6mwFGSiqy5zMwxBH/TJHwjSPhL8+01kaaSUuMFKTcLEvaUePcrSmwn8DZrgikWb7CGPxkSjhQwrRk57tctmxLsb9sZvL9LSlyuSLlWkqOjwduo8b6Uv1DkmudIeFF2dHCgxVtk8dpIvHpBxhEOdhKk7OLIUSdJ+cSRY57B+0DgGUUlNfpthTfGkauzxrvTsUUaCVhlKeteTXCoJDCa2NOKhOmC4G1H8JBd4OBZReSRGkqcb/CO1PyLJTLB4j1q8JYaIutEjSLX8YKM+a6phdMsdLFUoV5RTm9JSkuDN8WcIon0NZMNZWh1q8C7SJEwV5HxrmnnTrf3KoJBlmCYI2ilSLlfEvlE4011NNgjgthzEua0oKK7JLE7HZHlEl60BLMVFewg4EWNt0ThrVNEVkkiTwpKXSWJzdRENgvKGq4IhjsiezgSFtsfCUq8qki5S1LRQeYQQ4nemmCkImWMw3tFUoUBZk4NOeZYEp4XRKTGa6wJjrWNHBVJR4m3FCnbuD6aak2WsMTh3SZImGCIPKNgsDpVwnsa70K31lCFJZYcwwSMFcQulGTsZuEaSdBXkPGZhu0FsdUO73RHjq8MPGGIfaGIbVTk6iuI3GFgucHrIQkmWSJdBd7BBu+uOryWAhY7+Lki9rK5wtEQzWwvtbqGhIMFwWRJsElsY4m9IIg9L6lCX0VklaPAYkfkZEGDnOWowlBJjtMUkcGK4Lg6EtoZInMUBVYLgn0UsdmCyCz7gIGHFfk+k1QwTh5We7A9x+IdJ6CvIkEagms0hR50eH9UnTQJ+2oiKyVlLFUE+8gBGu8MQ3CppUHesnjTHN4QB/UGPhCTHLFPHMFrCqa73gqObUJGa03wgbhHkrCfpEpzNLE7JDS25FMKhlhKKWKfCgqstLCPu1zBXy0J2ztwjtixBu8UTRn9LVtkmCN2iyFhtME70JHRQ1KVZXqKI/KNIKYMCYs1GUMEKbM1bKOI9LDXC7zbHS+bt+1MTWS9odA9DtrYtpbImQJ2VHh/lisEwaHqUk1kjKTAKknkBEXkbkdMGwq0dnhzLJF3NJH3JVwrqOB4Sca2hti75nmJN0WzxS6UxDYoEpxpa4htVlRjkYE7DZGzJVU72uC9IyhQL4i8YfGWSYLLNcHXloyz7QhNifmKSE9JgfGmuyLhc403Xm9vqcp6gXe3xuuv8F6VJNxkyTHEkHG2g0aKXL0MsXc1bGfgas2//dCONXiNLCX+5mB7eZIl1kHh7ajwpikyzlUUWOVOsjSQlsS+M0R+pPje/dzBXRZGO0rMtgQrLLG9VSu9n6CMXS3BhwYmSoIBhsjNBmZbgusE9BCPCP5triU4VhNbJfE+swSP27aayE8tuTpYYjtrYjMVGZdp2NpS1s6aBnKSHDsbKuplKbHM4a0wMFd/5/DmGyKrJSUaW4IBrqUhx0vyfzTBBLPIUcnZdrAkNsKR0sWRspumSns6Ch0v/qqIbBYUWKvPU/CFoyrDJGwSNFhbA/MlzKqjrO80hRbpKx0Jewsi/STftwGSlKc1JZyAzx05dhLEdnfQvhZOqiHWWEAHC7+30FuRcZUgaO5gpaIK+xsiHRUsqaPElTV40xQZQ107Q9BZE1nryDVGU9ZSQ47bmhBpLcYpUt7S+xuK/FiT8qKjwXYw5ypS2iuCv7q1gtgjhuBuB8LCFY5cUuCNtsQOFcT+4Ih9JX+k8Ea6v0iCIRZOtCT0Et00JW5UeC85Cg0ScK0k411HcG1zKtre3SeITBRk7WfwDhEvaYLTHP9le0m8By0JDwn4TlLW/aJOvGHxdjYUes+ScZigCkYQdNdEOhkiezgShqkx8ueKjI8lDfK2oNiOFvrZH1hS+tk7NV7nOmLHicGWEgubkXKdwdtZknCLJXaCpkrjZBtLZFsDP9CdxWsSr05Sxl6CMmoFbCOgryX40uDtamB7SVmXW4Ihlgpmq+00tBKUUa83WbjLUNkzDmY7cow1JDygyPGlhgGKYKz4vcV7QBNbJIgM11TUqZaMdwTeSguH6rOaw1JRKzaaGyxVm2EJ/uCIrVWUcZUkcp2grMsEjK+DMwS59jQk3Kd6SEq1d0S6uVmO4Bc1lDXTUcHjluCXEq+1OlBDj1pi9zgiXxnKuE0SqTXwhqbETW6RggMEnGl/q49UT2iCzgJvRwVXS2K/d6+ZkyUl7jawSVLit46EwxVljDZwoSQ20sDBihztHfk2yA8NVZghiXwrYHQdfKAOtzsayjhY9bY0yE2CWEeJ9xfzO423xhL5syS2TFJofO2pboHob0nY4GiAgRrvGQEDa/FWSsoaaYl0syRsEt3kWoH3B01shCXhTUWe9w3Bt44SC9QCh3eShQctwbaK2ApLroGCMlZrYqvlY3qYhM0aXpFkPOuoqJ3Dm6fxXrGwVF9gCWZagjPqznfkuMKQ8DPTQRO8ZqG1hPGKEm9IgpGW4DZDgTNriTxvFiq+Lz+0cKfp4wj6OCK9JSnzNSn9LFU7UhKZZMnYwcJ8s8yRsECScK4j5UOB95HFO0CzhY4xJxuCix0lDlEUeMdS6EZBkTsUkZ4K74dugyTXS7aNgL8aqjDfkCE0ZbwkCXpaWCKhl8P7VD5jxykivSyxyZrYERbe168LYu9ZYh86IkscgVLE7tWPKmJv11CgoyJltMEbrohtVAQfO4ImltiHEroYEs7RxAarVpY8AwXMcMReFOTYWe5iiLRQxJ5Q8DtJ8LQhWOhIeFESPGsILhbNDRljNbHzNRlTFbk2S3L0NOS6V1KFJYKUbSTcIIhM0wQ/s2TM0SRMNcQmSap3jCH4yhJZKSkwyRHpYYgsFeQ4U7xoCB7VVOExhXepo9ABBsYbvGWKXPME3lyH95YioZ0gssQRWWbI+FaSMkXijZXwgiTlYdPdkNLaETxlyDVIwqeaEus0aTcYcg0RVOkpR3CSJqIddK+90JCxzsDVloyrFd5ZAr4TBKfaWa6boEA7C7s6EpYaeFPjveooY72mjIccLHJ9HUwVlDhKkmutJDJBwnp1rvulJZggKDRfbXAkvC/4l3ozQOG9a8lxjx0i7nV4jSXc7vhe3OwIxjgSHjdEhhsif9YkPGlus3iLFDnWOFhtCZbJg0UbQcIaR67JjthoCyMEZRwhiXWyxO5QxI6w5NhT4U1WsJvDO60J34fW9hwzwlKij6ZAW9ne4L0s8C6XeBMEkd/LQy1VucBRot6QMlbivaBhoBgjqGiCJNhsqVp/S2SsG6DIONCR0dXhvWbJ+MRRZJkkuEjgDXJjFQW6SSL7GXK8Z2CZg7cVsbWGoKmEpzQ5elpiy8Ryg7dMkLLUEauzeO86CuwlSOlgYLojZWeJ9xM3S1PWfEfKl5ISLQ0MEKR8YOB2QfCxJBjrKPCN4f9MkaSsqoVXJBmP7EpFZ9UQfOoOFwSzBN4MQ8LsGrymlipcJQhmy0GaQjPqCHaXRwuCZwRbqK2Fg9wlClZqYicrIgMdZfxTQ0c7TBIbrChxmuzoKG8XRaSrIhhiyNFJkrC7oIAWMEOQa5aBekPCRknCo4IKPrYkvCDI8aYmY7WFtprgekcJZ3oLIqssCSMtFbQTJKwXYy3BY5oCh2iKPCpJOE+zRdpYgi6O2KmOAgvVCYaU4ySRek1sgyFhJ403QFHiVEmJHwtybO1gs8Hr5+BETQX3War0qZngYGgtVZtoqd6vFSk/UwdZElYqyjrF4HXUeFspIi9IGKf4j92pKGAdCYMVsbcV3kRF0N+R8LUd5PCsIGWoxDtBkCI0nKofdJQxT+LtZflvuc8Q3CjwWkq8KwUpHzkK/NmSsclCL0nseQdj5FRH5CNHSgtLiW80Of5HU9Hhlsga9bnBq3fEVltKfO5IaSTmGjjc4J0otcP7QsJUSQM8pEj5/wCuUuC2DWz8AAAAAElFTkSuQmCC"); +} diff --git a/modules/editor/codemirror/theme/dracula.css b/modules/editor/codemirror/theme/dracula.css @@ -0,0 +1,40 @@ +/* + + Name: dracula + Author: Michael Kaminsky (http://github.com/mkaminsky11) + + Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme) + +*/ + + +.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters { + background-color: #282a36 !important; + color: #f8f8f2 !important; + border: none; +} +.cm-s-dracula .CodeMirror-gutters { color: #282a36; } +.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; } +.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; } +.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } +.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } +.cm-s-dracula span.cm-comment { color: #6272a4; } +.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; } +.cm-s-dracula span.cm-number { color: #bd93f9; } +.cm-s-dracula span.cm-variable { color: #50fa7b; } +.cm-s-dracula span.cm-variable-2 { color: white; } +.cm-s-dracula span.cm-def { color: #50fa7b; } +.cm-s-dracula span.cm-operator { color: #ff79c6; } +.cm-s-dracula span.cm-keyword { color: #ff79c6; } +.cm-s-dracula span.cm-atom { color: #bd93f9; } +.cm-s-dracula span.cm-meta { color: #f8f8f2; } +.cm-s-dracula span.cm-tag { color: #ff79c6; } +.cm-s-dracula span.cm-attribute { color: #50fa7b; } +.cm-s-dracula span.cm-qualifier { color: #50fa7b; } +.cm-s-dracula span.cm-property { color: #66d9ef; } +.cm-s-dracula span.cm-builtin { color: #50fa7b; } +.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; } + +.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); } +.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/modules/editor/codemirror/theme/duotone-dark.css b/modules/editor/codemirror/theme/duotone-dark.css @@ -0,0 +1,35 @@ +/* +Name: DuoTone-Dark +Author: by Bram de Haan, adapted from DuoTone themes by Simurai (http://simurai.com/projects/2016/01/01/duotone-themes) + +CodeMirror template by Jan T. Sott (https://github.com/idleberg), adapted by Bram de Haan (https://github.com/atelierbram/) +*/ + +.cm-s-duotone-dark.CodeMirror { background: #2a2734; color: #6c6783; } +.cm-s-duotone-dark div.CodeMirror-selected { background: #545167!important; } +.cm-s-duotone-dark .CodeMirror-gutters { background: #2a2734; border-right: 0px; } +.cm-s-duotone-dark .CodeMirror-linenumber { color: #545167; } + +/* begin cursor */ +.cm-s-duotone-dark .CodeMirror-cursor { border-left: 1px solid #ffad5c; /* border-left: 1px solid #ffad5c80; */ border-right: .5em solid #ffad5c; /* border-right: .5em solid #ffad5c80; */ opacity: .5; } +.cm-s-duotone-dark .CodeMirror-activeline-background { background: #363342; /* background: #36334280; */ opacity: .5;} +.cm-s-duotone-dark .cm-fat-cursor .CodeMirror-cursor { background: #ffad5c; /* background: #ffad5c80; */ opacity: .5;} +/* end cursor */ + +.cm-s-duotone-dark span.cm-atom, .cm-s-duotone-dark span.cm-number, .cm-s-duotone-dark span.cm-keyword, .cm-s-duotone-dark span.cm-variable, .cm-s-duotone-dark span.cm-attribute, .cm-s-duotone-dark span.cm-quote, .cm-s-duotone-dark span.cm-hr, .cm-s-duotone-dark span.cm-link { color: #ffcc99; } + +.cm-s-duotone-dark span.cm-property { color: #9a86fd; } +.cm-s-duotone-dark span.cm-punctuation, .cm-s-duotone-dark span.cm-unit, .cm-s-duotone-dark span.cm-negative { color: #e09142; } +.cm-s-duotone-dark span.cm-string { color: #ffb870; } +.cm-s-duotone-dark span.cm-operator { color: #ffad5c; } +.cm-s-duotone-dark span.cm-positive { color: #6a51e6; } + +.cm-s-duotone-dark span.cm-variable-2, .cm-s-duotone-dark span.cm-variable-3, .cm-s-duotone-dark span.cm-type, .cm-s-duotone-dark span.cm-string-2, .cm-s-duotone-dark span.cm-url { color: #7a63ee; } +.cm-s-duotone-dark span.cm-def, .cm-s-duotone-dark span.cm-tag, .cm-s-duotone-dark span.cm-builtin, .cm-s-duotone-dark span.cm-qualifier, .cm-s-duotone-dark span.cm-header, .cm-s-duotone-dark span.cm-em { color: #eeebff; } +.cm-s-duotone-dark span.cm-bracket, .cm-s-duotone-dark span.cm-comment { color: #6c6783; } + +/* using #f00 red for errors, don't think any of the colorscheme variables will stand out enough, ... maybe by giving it a background-color ... */ +.cm-s-duotone-dark span.cm-error, .cm-s-duotone-dark span.cm-invalidchar { color: #f00; } + +.cm-s-duotone-dark span.cm-header { font-weight: normal; } +.cm-s-duotone-dark .CodeMirror-matchingbracket { text-decoration: underline; color: #eeebff !important; } diff --git a/modules/editor/codemirror/theme/icecoder.css b/modules/editor/codemirror/theme/icecoder.css @@ -0,0 +1,43 @@ +/* +ICEcoder default theme by Matt Pass, used in code editor available at https://icecoder.net +*/ + +.cm-s-icecoder { color: #666; background: #1d1d1b; } + +.cm-s-icecoder span.cm-keyword { color: #eee; font-weight:bold; } /* off-white 1 */ +.cm-s-icecoder span.cm-atom { color: #e1c76e; } /* yellow */ +.cm-s-icecoder span.cm-number { color: #6cb5d9; } /* blue */ +.cm-s-icecoder span.cm-def { color: #b9ca4a; } /* green */ + +.cm-s-icecoder span.cm-variable { color: #6cb5d9; } /* blue */ +.cm-s-icecoder span.cm-variable-2 { color: #cc1e5c; } /* pink */ +.cm-s-icecoder span.cm-variable-3, .cm-s-icecoder span.cm-type { color: #f9602c; } /* orange */ + +.cm-s-icecoder span.cm-property { color: #eee; } /* off-white 1 */ +.cm-s-icecoder span.cm-operator { color: #9179bb; } /* purple */ +.cm-s-icecoder span.cm-comment { color: #97a3aa; } /* grey-blue */ + +.cm-s-icecoder span.cm-string { color: #b9ca4a; } /* green */ +.cm-s-icecoder span.cm-string-2 { color: #6cb5d9; } /* blue */ + +.cm-s-icecoder span.cm-meta { color: #555; } /* grey */ + +.cm-s-icecoder span.cm-qualifier { color: #555; } /* grey */ +.cm-s-icecoder span.cm-builtin { color: #214e7b; } /* bright blue */ +.cm-s-icecoder span.cm-bracket { color: #cc7; } /* grey-yellow */ + +.cm-s-icecoder span.cm-tag { color: #e8e8e8; } /* off-white 2 */ +.cm-s-icecoder span.cm-attribute { color: #099; } /* teal */ + +.cm-s-icecoder span.cm-header { color: #6a0d6a; } /* purple-pink */ +.cm-s-icecoder span.cm-quote { color: #186718; } /* dark green */ +.cm-s-icecoder span.cm-hr { color: #888; } /* mid-grey */ +.cm-s-icecoder span.cm-link { color: #e1c76e; } /* yellow */ +.cm-s-icecoder span.cm-error { color: #d00; } /* red */ + +.cm-s-icecoder .CodeMirror-cursor { border-left: 1px solid white; } +.cm-s-icecoder div.CodeMirror-selected { color: #fff; background: #037; } +.cm-s-icecoder .CodeMirror-gutters { background: #1d1d1b; min-width: 41px; border-right: 0; } +.cm-s-icecoder .CodeMirror-linenumber { color: #555; cursor: default; } +.cm-s-icecoder .CodeMirror-matchingbracket { color: #fff !important; background: #555 !important; } +.cm-s-icecoder .CodeMirror-activeline-background { background: #000; } diff --git a/modules/editor/codemirror/theme/panda-syntax.css b/modules/editor/codemirror/theme/panda-syntax.css @@ -0,0 +1,85 @@ +/* + Name: Panda Syntax + Author: Siamak Mokhtari (http://github.com/siamak/) + CodeMirror template by Siamak Mokhtari (https://github.com/siamak/atom-panda-syntax) +*/ +.cm-s-panda-syntax { + background: #292A2B; + color: #E6E6E6; + line-height: 1.5; + font-family: 'Operator Mono', 'Source Sans Pro', Menlo, Monaco, Consolas, Courier New, monospace; +} +.cm-s-panda-syntax .CodeMirror-cursor { border-color: #ff2c6d; } +.cm-s-panda-syntax .CodeMirror-activeline-background { + background: rgba(99, 123, 156, 0.1); +} +.cm-s-panda-syntax .CodeMirror-selected { + background: #FFF; +} +.cm-s-panda-syntax .cm-comment { + font-style: italic; + color: #676B79; +} +.cm-s-panda-syntax .cm-operator { + color: #f3f3f3; +} +.cm-s-panda-syntax .cm-string { + color: #19F9D8; +} +.cm-s-panda-syntax .cm-string-2 { + color: #FFB86C; +} + +.cm-s-panda-syntax .cm-tag { + color: #ff2c6d; +} +.cm-s-panda-syntax .cm-meta { + color: #b084eb; +} + +.cm-s-panda-syntax .cm-number { + color: #FFB86C; +} +.cm-s-panda-syntax .cm-atom { + color: #ff2c6d; +} +.cm-s-panda-syntax .cm-keyword { + color: #FF75B5; +} +.cm-s-panda-syntax .cm-variable { + color: #ffb86c; +} +.cm-s-panda-syntax .cm-variable-2 { + color: #ff9ac1; +} +.cm-s-panda-syntax .cm-variable-3, .cm-s-panda-syntax .cm-type { + color: #ff9ac1; +} + +.cm-s-panda-syntax .cm-def { + color: #e6e6e6; +} +.cm-s-panda-syntax .cm-property { + color: #f3f3f3; +} +.cm-s-panda-syntax .cm-unit { + color: #ffb86c; +} + +.cm-s-panda-syntax .cm-attribute { + color: #ffb86c; +} + +.cm-s-panda-syntax .CodeMirror-matchingbracket { + border-bottom: 1px dotted #19F9D8; + padding-bottom: 2px; + color: #e6e6e6; +} +.cm-s-panda-syntax .CodeMirror-gutters { + background: #292a2b; + border-right-color: rgba(255, 255, 255, 0.1); +} +.cm-s-panda-syntax .CodeMirror-linenumber { + color: #e6e6e6; + opacity: 0.6; +} diff --git a/modules/editor/codemirror/theme/pastel-on-dark.css b/modules/editor/codemirror/theme/pastel-on-dark.css @@ -0,0 +1,52 @@ +/** + * Pastel On Dark theme ported from ACE editor + * @license MIT + * @copyright AtomicPages LLC 2014 + * @author Dennis Thompson, AtomicPages LLC + * @version 1.1 + * @source https://github.com/atomicpages/codemirror-pastel-on-dark-theme + */ + +.cm-s-pastel-on-dark.CodeMirror { + background: #2c2827; + color: #8F938F; + line-height: 1.5; +} +.cm-s-pastel-on-dark div.CodeMirror-selected { background: rgba(221,240,255,0.2); } +.cm-s-pastel-on-dark .CodeMirror-line::selection, .cm-s-pastel-on-dark .CodeMirror-line > span::selection, .cm-s-pastel-on-dark .CodeMirror-line > span > span::selection { background: rgba(221,240,255,0.2); } +.cm-s-pastel-on-dark .CodeMirror-line::-moz-selection, .cm-s-pastel-on-dark .CodeMirror-line > span::-moz-selection, .cm-s-pastel-on-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(221,240,255,0.2); } + +.cm-s-pastel-on-dark .CodeMirror-gutters { + background: #34302f; + border-right: 0px; + padding: 0 3px; +} +.cm-s-pastel-on-dark .CodeMirror-guttermarker { color: white; } +.cm-s-pastel-on-dark .CodeMirror-guttermarker-subtle { color: #8F938F; } +.cm-s-pastel-on-dark .CodeMirror-linenumber { color: #8F938F; } +.cm-s-pastel-on-dark .CodeMirror-cursor { border-left: 1px solid #A7A7A7; } +.cm-s-pastel-on-dark span.cm-comment { color: #A6C6FF; } +.cm-s-pastel-on-dark span.cm-atom { color: #DE8E30; } +.cm-s-pastel-on-dark span.cm-number { color: #CCCCCC; } +.cm-s-pastel-on-dark span.cm-property { color: #8F938F; } +.cm-s-pastel-on-dark span.cm-attribute { color: #a6e22e; } +.cm-s-pastel-on-dark span.cm-keyword { color: #AEB2F8; } +.cm-s-pastel-on-dark span.cm-string { color: #66A968; } +.cm-s-pastel-on-dark span.cm-variable { color: #AEB2F8; } +.cm-s-pastel-on-dark span.cm-variable-2 { color: #BEBF55; } +.cm-s-pastel-on-dark span.cm-variable-3, .cm-s-pastel-on-dark span.cm-type { color: #DE8E30; } +.cm-s-pastel-on-dark span.cm-def { color: #757aD8; } +.cm-s-pastel-on-dark span.cm-bracket { color: #f8f8f2; } +.cm-s-pastel-on-dark span.cm-tag { color: #C1C144; } +.cm-s-pastel-on-dark span.cm-link { color: #ae81ff; } +.cm-s-pastel-on-dark span.cm-qualifier,.cm-s-pastel-on-dark span.cm-builtin { color: #C1C144; } +.cm-s-pastel-on-dark span.cm-error { + background: #757aD8; + color: #f8f8f0; +} +.cm-s-pastel-on-dark .CodeMirror-activeline-background { background: rgba(255, 255, 255, 0.031); } +.cm-s-pastel-on-dark .CodeMirror-matchingbracket { + border: 1px solid rgba(255,255,255,0.25); + color: #8F938F !important; + margin: -1px -1px 0 -1px; +} diff --git a/modules/editor/codemirror/theme/the-matrix.css b/modules/editor/codemirror/theme/the-matrix.css @@ -0,0 +1,30 @@ +.cm-s-the-matrix.CodeMirror { background: #000000; color: #00FF00; } +.cm-s-the-matrix div.CodeMirror-selected { background: #2D2D2D; } +.cm-s-the-matrix .CodeMirror-line::selection, .cm-s-the-matrix .CodeMirror-line > span::selection, .cm-s-the-matrix .CodeMirror-line > span > span::selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-the-matrix .CodeMirror-line::-moz-selection, .cm-s-the-matrix .CodeMirror-line > span::-moz-selection, .cm-s-the-matrix .CodeMirror-line > span > span::-moz-selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-the-matrix .CodeMirror-gutters { background: #060; border-right: 2px solid #00FF00; } +.cm-s-the-matrix .CodeMirror-guttermarker { color: #0f0; } +.cm-s-the-matrix .CodeMirror-guttermarker-subtle { color: white; } +.cm-s-the-matrix .CodeMirror-linenumber { color: #FFFFFF; } +.cm-s-the-matrix .CodeMirror-cursor { border-left: 1px solid #00FF00; } + +.cm-s-the-matrix span.cm-keyword { color: #008803; font-weight: bold; } +.cm-s-the-matrix span.cm-atom { color: #3FF; } +.cm-s-the-matrix span.cm-number { color: #FFB94F; } +.cm-s-the-matrix span.cm-def { color: #99C; } +.cm-s-the-matrix span.cm-variable { color: #F6C; } +.cm-s-the-matrix span.cm-variable-2 { color: #C6F; } +.cm-s-the-matrix span.cm-variable-3, .cm-s-the-matrix span.cm-type { color: #96F; } +.cm-s-the-matrix span.cm-property { color: #62FFA0; } +.cm-s-the-matrix span.cm-operator { color: #999; } +.cm-s-the-matrix span.cm-comment { color: #CCCCCC; } +.cm-s-the-matrix span.cm-string { color: #39C; } +.cm-s-the-matrix span.cm-meta { color: #C9F; } +.cm-s-the-matrix span.cm-qualifier { color: #FFF700; } +.cm-s-the-matrix span.cm-builtin { color: #30a; } +.cm-s-the-matrix span.cm-bracket { color: #cc7; } +.cm-s-the-matrix span.cm-tag { color: #FFBD40; } +.cm-s-the-matrix span.cm-attribute { color: #FFF700; } +.cm-s-the-matrix span.cm-error { color: #FF0000; } + +.cm-s-the-matrix .CodeMirror-activeline-background { background: #040; } diff --git a/modules/editor/codemirror/theme/tomorrow-night-eighties.css b/modules/editor/codemirror/theme/tomorrow-night-eighties.css @@ -0,0 +1,38 @@ +/* + + Name: Tomorrow Night - Eighties + Author: Chris Kempson + + CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror) + Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) + +*/ + +.cm-s-tomorrow-night-eighties.CodeMirror { background: #000000; color: #CCCCCC; } +.cm-s-tomorrow-night-eighties div.CodeMirror-selected { background: #2D2D2D; } +.cm-s-tomorrow-night-eighties .CodeMirror-line::selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span::selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span > span::selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-tomorrow-night-eighties .CodeMirror-line::-moz-selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span::-moz-selection, .cm-s-tomorrow-night-eighties .CodeMirror-line > span > span::-moz-selection { background: rgba(45, 45, 45, 0.99); } +.cm-s-tomorrow-night-eighties .CodeMirror-gutters { background: #000000; border-right: 0px; } +.cm-s-tomorrow-night-eighties .CodeMirror-guttermarker { color: #f2777a; } +.cm-s-tomorrow-night-eighties .CodeMirror-guttermarker-subtle { color: #777; } +.cm-s-tomorrow-night-eighties .CodeMirror-linenumber { color: #515151; } +.cm-s-tomorrow-night-eighties .CodeMirror-cursor { border-left: 1px solid #6A6A6A; } + +.cm-s-tomorrow-night-eighties span.cm-comment { color: #d27b53; } +.cm-s-tomorrow-night-eighties span.cm-atom { color: #a16a94; } +.cm-s-tomorrow-night-eighties span.cm-number { color: #a16a94; } + +.cm-s-tomorrow-night-eighties span.cm-property, .cm-s-tomorrow-night-eighties span.cm-attribute { color: #99cc99; } +.cm-s-tomorrow-night-eighties span.cm-keyword { color: #f2777a; } +.cm-s-tomorrow-night-eighties span.cm-string { color: #ffcc66; } + +.cm-s-tomorrow-night-eighties span.cm-variable { color: #99cc99; } +.cm-s-tomorrow-night-eighties span.cm-variable-2 { color: #6699cc; } +.cm-s-tomorrow-night-eighties span.cm-def { color: #f99157; } +.cm-s-tomorrow-night-eighties span.cm-bracket { color: #CCCCCC; } +.cm-s-tomorrow-night-eighties span.cm-tag { color: #f2777a; } +.cm-s-tomorrow-night-eighties span.cm-link { color: #a16a94; } +.cm-s-tomorrow-night-eighties span.cm-error { background: #f2777a; color: #6A6A6A; } + +.cm-s-tomorrow-night-eighties .CodeMirror-activeline-background { background: #343600; } +.cm-s-tomorrow-night-eighties .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } diff --git a/modules/editor/codemirror/theme/twilight.css b/modules/editor/codemirror/theme/twilight.css @@ -0,0 +1,32 @@ +.cm-s-twilight.CodeMirror { background: #141414; color: #f7f7f7; } /**/ +.cm-s-twilight div.CodeMirror-selected { background: #323232; } /**/ +.cm-s-twilight .CodeMirror-line::selection, .cm-s-twilight .CodeMirror-line > span::selection, .cm-s-twilight .CodeMirror-line > span > span::selection { background: rgba(50, 50, 50, 0.99); } +.cm-s-twilight .CodeMirror-line::-moz-selection, .cm-s-twilight .CodeMirror-line > span::-moz-selection, .cm-s-twilight .CodeMirror-line > span > span::-moz-selection { background: rgba(50, 50, 50, 0.99); } + +.cm-s-twilight .CodeMirror-gutters { background: #222; border-right: 1px solid #aaa; } +.cm-s-twilight .CodeMirror-guttermarker { color: white; } +.cm-s-twilight .CodeMirror-guttermarker-subtle { color: #aaa; } +.cm-s-twilight .CodeMirror-linenumber { color: #aaa; } +.cm-s-twilight .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-twilight .cm-keyword { color: #f9ee98; } /**/ +.cm-s-twilight .cm-atom { color: #FC0; } +.cm-s-twilight .cm-number { color: #ca7841; } /**/ +.cm-s-twilight .cm-def { color: #8DA6CE; } +.cm-s-twilight span.cm-variable-2, .cm-s-twilight span.cm-tag { color: #607392; } /**/ +.cm-s-twilight span.cm-variable-3, .cm-s-twilight span.cm-def, .cm-s-twilight span.cm-type { color: #607392; } /**/ +.cm-s-twilight .cm-operator { color: #cda869; } /**/ +.cm-s-twilight .cm-comment { color:#777; font-style:italic; font-weight:normal; } /**/ +.cm-s-twilight .cm-string { color:#8f9d6a; font-style:italic; } /**/ +.cm-s-twilight .cm-string-2 { color:#bd6b18; } /*?*/ +.cm-s-twilight .cm-meta { background-color:#141414; color:#f7f7f7; } /*?*/ +.cm-s-twilight .cm-builtin { color: #cda869; } /*?*/ +.cm-s-twilight .cm-tag { color: #997643; } /**/ +.cm-s-twilight .cm-attribute { color: #d6bb6d; } /*?*/ +.cm-s-twilight .cm-header { color: #FF6400; } +.cm-s-twilight .cm-hr { color: #AEAEAE; } +.cm-s-twilight .cm-link { color:#ad9361; font-style:italic; text-decoration:none; } /**/ +.cm-s-twilight .cm-error { border-bottom: 1px solid red; } + +.cm-s-twilight .CodeMirror-activeline-background { background: #27282E; } +.cm-s-twilight .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/modules/editor/codemirror/theme/vibrant-ink.css b/modules/editor/codemirror/theme/vibrant-ink.css @@ -0,0 +1,34 @@ +/* Taken from the popular Visual Studio Vibrant Ink Schema */ + +.cm-s-vibrant-ink.CodeMirror { background: black; color: white; } +.cm-s-vibrant-ink div.CodeMirror-selected { background: #35493c; } +.cm-s-vibrant-ink .CodeMirror-line::selection, .cm-s-vibrant-ink .CodeMirror-line > span::selection, .cm-s-vibrant-ink .CodeMirror-line > span > span::selection { background: rgba(53, 73, 60, 0.99); } +.cm-s-vibrant-ink .CodeMirror-line::-moz-selection, .cm-s-vibrant-ink .CodeMirror-line > span::-moz-selection, .cm-s-vibrant-ink .CodeMirror-line > span > span::-moz-selection { background: rgba(53, 73, 60, 0.99); } + +.cm-s-vibrant-ink .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; } +.cm-s-vibrant-ink .CodeMirror-guttermarker { color: white; } +.cm-s-vibrant-ink .CodeMirror-guttermarker-subtle { color: #d0d0d0; } +.cm-s-vibrant-ink .CodeMirror-linenumber { color: #d0d0d0; } +.cm-s-vibrant-ink .CodeMirror-cursor { border-left: 1px solid white; } + +.cm-s-vibrant-ink .cm-keyword { color: #CC7832; } +.cm-s-vibrant-ink .cm-atom { color: #FC0; } +.cm-s-vibrant-ink .cm-number { color: #FFEE98; } +.cm-s-vibrant-ink .cm-def { color: #8DA6CE; } +.cm-s-vibrant-ink span.cm-variable-2, .cm-s-vibrant span.cm-tag { color: #FFC66D; } +.cm-s-vibrant-ink span.cm-variable-3, .cm-s-vibrant span.cm-def, .cm-s-vibrant span.cm-type { color: #FFC66D; } +.cm-s-vibrant-ink .cm-operator { color: #888; } +.cm-s-vibrant-ink .cm-comment { color: gray; font-weight: bold; } +.cm-s-vibrant-ink .cm-string { color: #A5C25C; } +.cm-s-vibrant-ink .cm-string-2 { color: red; } +.cm-s-vibrant-ink .cm-meta { color: #D8FA3C; } +.cm-s-vibrant-ink .cm-builtin { color: #8DA6CE; } +.cm-s-vibrant-ink .cm-tag { color: #8DA6CE; } +.cm-s-vibrant-ink .cm-attribute { color: #8DA6CE; } +.cm-s-vibrant-ink .cm-header { color: #FF6400; } +.cm-s-vibrant-ink .cm-hr { color: #AEAEAE; } +.cm-s-vibrant-ink .cm-link { color: blue; } +.cm-s-vibrant-ink .cm-error { border-bottom: 1px solid red; } + +.cm-s-vibrant-ink .CodeMirror-activeline-background { background: #27282E; } +.cm-s-vibrant-ink .CodeMirror-matchingbracket { outline:1px solid grey; color:white !important; } diff --git a/modules/editor/codemirror/theme/xq-light.css b/modules/editor/codemirror/theme/xq-light.css @@ -0,0 +1,43 @@ +/* +Copyright (C) 2011 by MarkLogic Corporation +Author: Mike Brevoort <mike@brevoort.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +.cm-s-xq-light span.cm-keyword { line-height: 1em; font-weight: bold; color: #5A5CAD; } +.cm-s-xq-light span.cm-atom { color: #6C8CD5; } +.cm-s-xq-light span.cm-number { color: #164; } +.cm-s-xq-light span.cm-def { text-decoration:underline; } +.cm-s-xq-light span.cm-variable { color: black; } +.cm-s-xq-light span.cm-variable-2 { color:black; } +.cm-s-xq-light span.cm-variable-3, .cm-s-xq-light span.cm-type { color: black; } +.cm-s-xq-light span.cm-property {} +.cm-s-xq-light span.cm-operator {} +.cm-s-xq-light span.cm-comment { color: #0080FF; font-style: italic; } +.cm-s-xq-light span.cm-string { color: red; } +.cm-s-xq-light span.cm-meta { color: yellow; } +.cm-s-xq-light span.cm-qualifier { color: grey; } +.cm-s-xq-light span.cm-builtin { color: #7EA656; } +.cm-s-xq-light span.cm-bracket { color: #cc7; } +.cm-s-xq-light span.cm-tag { color: #3F7F7F; } +.cm-s-xq-light span.cm-attribute { color: #7F007F; } +.cm-s-xq-light span.cm-error { color: #f00; } + +.cm-s-xq-light .CodeMirror-activeline-background { background: #e8f2ff; } +.cm-s-xq-light .CodeMirror-matchingbracket { outline:1px solid grey;color:black !important;background:yellow; } diff --git a/modules/editor/codemirror/theme/zenburn.css b/modules/editor/codemirror/theme/zenburn.css @@ -0,0 +1,37 @@ +/** + * " + * Using Zenburn color palette from the Emacs Zenburn Theme + * https://github.com/bbatsov/zenburn-emacs/blob/master/zenburn-theme.el + * + * Also using parts of https://github.com/xavi/coderay-lighttable-theme + * " + * From: https://github.com/wisenomad/zenburn-lighttable-theme/blob/master/zenburn.css + */ + +.cm-s-zenburn .CodeMirror-gutters { background: #3f3f3f !important; } +.cm-s-zenburn .CodeMirror-foldgutter-open, .CodeMirror-foldgutter-folded { color: #999; } +.cm-s-zenburn .CodeMirror-cursor { border-left: 1px solid white; } +.cm-s-zenburn { background-color: #3f3f3f; color: #dcdccc; } +.cm-s-zenburn span.cm-builtin { color: #dcdccc; font-weight: bold; } +.cm-s-zenburn span.cm-comment { color: #7f9f7f; } +.cm-s-zenburn span.cm-keyword { color: #f0dfaf; font-weight: bold; } +.cm-s-zenburn span.cm-atom { color: #bfebbf; } +.cm-s-zenburn span.cm-def { color: #dcdccc; } +.cm-s-zenburn span.cm-variable { color: #dfaf8f; } +.cm-s-zenburn span.cm-variable-2 { color: #dcdccc; } +.cm-s-zenburn span.cm-string { color: #cc9393; } +.cm-s-zenburn span.cm-string-2 { color: #cc9393; } +.cm-s-zenburn span.cm-number { color: #dcdccc; } +.cm-s-zenburn span.cm-tag { color: #93e0e3; } +.cm-s-zenburn span.cm-property { color: #dfaf8f; } +.cm-s-zenburn span.cm-attribute { color: #dfaf8f; } +.cm-s-zenburn span.cm-qualifier { color: #7cb8bb; } +.cm-s-zenburn span.cm-meta { color: #f0dfaf; } +.cm-s-zenburn span.cm-header { color: #f0efd0; } +.cm-s-zenburn span.cm-operator { color: #f0efd0; } +.cm-s-zenburn span.CodeMirror-matchingbracket { box-sizing: border-box; background: transparent; border-bottom: 1px solid; } +.cm-s-zenburn span.CodeMirror-nonmatchingbracket { border-bottom: 1px solid; background: none; } +.cm-s-zenburn .CodeMirror-activeline { background: #000000; } +.cm-s-zenburn .CodeMirror-activeline-background { background: #000000; } +.cm-s-zenburn div.CodeMirror-selected { background: #545454; } +.cm-s-zenburn .CodeMirror-focused div.CodeMirror-selected { background: #4f4f4f; }