* Various Bugfixes for t3editor
authorIngo Renner <ingo.renner@typo3.org>
Wed, 16 Apr 2008 18:39:43 +0000 (18:39 +0000)
committerIngo Renner <ingo.renner@typo3.org>
Wed, 16 Apr 2008 18:39:43 +0000 (18:39 +0000)
* Fixed bug #6812: Editor does not respect charset settings while saving with AJAX
* Fixed bug #8049: TypoScript templates can't be saved at all after editing them using IE7/WinXP
* Fixed bug #7314: In IE 6.0.2900 syntax highlighting, save indicator, keyword suggestion and undo isn't working
* Moved CodeMirror into own directory
* Switched from gluecode to hooks

git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@3588 709f56b5-9817-0410-a4d7-c38de5d9e867

25 files changed:
ChangeLog
typo3/sysext/t3editor/LICENSE [new file with mode: 0644]
typo3/sysext/t3editor/class.tx_t3editor.php
typo3/sysext/t3editor/css/t3editor.css
typo3/sysext/t3editor/css/t3editor_inner.css [new file with mode: 0644]
typo3/sysext/t3editor/ext_emconf.php
typo3/sysext/t3editor/ext_localconf.php [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/LICENSE [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/README [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/codemirror.js [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/editor.js [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/parsejavascript.js [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/parsetyposcript.js [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/parsexml.js [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/patch.codemirror055.diff [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/select.js [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/stringstream.js [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/tokenizejavascript.js [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/tokenizetyposcript.js [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/undo.js [new file with mode: 0644]
typo3/sysext/t3editor/jslib/codemirror/util.js [new file with mode: 0644]
typo3/sysext/t3editor/jslib/t3editor.js
typo3/sysext/tstemplate/ts/index.php
typo3/sysext/tstemplate_info/class.tx_tstemplateinfo.php
typo3/template.php

index 85f622a..1f7371b 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
        * Added several other new icons in t3skin
        * Replaced icon for Web->Functions in t3skin
        * Fixed bug #8108: Extension configuration options are not always shown, credits Jeff Segars
+       * Various Bugfixes for t3editor
+               * Fixed bug #6812: Editor does not respect charset settings while saving with AJAX
+               * Fixed bug #8049: TypoScript templates can't be saved at all after editing them using IE7/WinXP
+               * Fixed bug #7314: In IE 6.0.2900 syntax highlighting, save indicator, keyword suggestion and undo isn't working
+               * Moved CodeMirror into own directory
+               * Switched from gluecode to hooks
 
 2008-04-15  Benjamin Mack  <benni@typo3.org>
 
diff --git a/typo3/sysext/t3editor/LICENSE b/typo3/sysext/t3editor/LICENSE
new file mode 100644 (file)
index 0000000..1c52b6c
--- /dev/null
@@ -0,0 +1,11 @@
+
+t3editor is written by Tobias Liebig (mail(at)etobi.de)
+
+Many thanks for help on the conceptual work, testing, feedback and providing several patches 
+to Vitaly Dutchak, Thomas Hempel and all the other people.
+
+t3editor uses the CodeMirror by Marijn Haverbeke (http://marijn.haverbeke.de/codemirror/)
+Thanks Marjin! 
+
+See jslib/codemirror/README and jslib/codemirror/LICENSE for more informations on CodeMirror
+
index a561b40..80039d0 100755 (executable)
@@ -93,6 +93,9 @@ class tx_t3editor {
                        //TODO give $state a more descriptive name / state of/for what?
                $state = t3lib_div::_GP('t3editor_disableEditor') == 'true' ? true : $GLOBALS['BE_USER']->uc['disableT3Editor'];
                $this->setBEUCdisableT3Editor($state);
+
+                       // disable pmktextarea to avoid conflicts (thanks Peter Klein for this suggestion)
+               $GLOBALS["BE_USER"]->uc['disablePMKTextarea'] = 1;
        }
 
        /**
@@ -112,35 +115,31 @@ class tx_t3editor {
        /**
         * Retrieves JavaScript code for editor
         *
+        * @param       template        $doc
         * @return      string          JavaScript code
         */
-       public function getJavascriptCode()     {
+       public function getJavascriptCode($doc) {
                $code = ''; // TODO find a more descriptive name (low prio)
 
                if ($this->isEnabled) {
 
-                       $path_t3e = $GLOBALS['BACK_PATH'].t3lib_extmgm::extRelPath('t3editor');
-
-                       $code.= '<script type="text/javascript">'.
-                               'var PATH_t3e = "'.$GLOBALS['BACK_PATH']. t3lib_extmgm::extRelPath('t3editor').'"; '.
-                               '</script>';
-
-                       $code.= '<script src="'.$path_t3e.'/jslib/util.js" type="text/javascript"></script>'.
-                               '<script src="'.$path_t3e.'/jslib/select.js" type="text/javascript"></script>'.
-                               '<script src="'.$path_t3e.'/jslib/stringstream.js" type="text/javascript"></script>'.
-                               '<script src="'.$path_t3e.'/jslib/parsetyposcript.js" type="text/javascript"></script>'.
-                               '<script src="'.$path_t3e.'/jslib/tokenizetyposcript.js" type="text/javascript"></script>';
+                       $path_t3e = t3lib_extmgm::extRelPath('t3editor');
 
-                               // include prototype and scriptacolous
-                               // TODO: should use the new loadJavascriptLib
-                       $code.= '<script src="'.$GLOBALS['BACK_PATH'].'contrib/prototype/prototype.js" type="text/javascript" id="prototype-script"></script>';
-                       $code.= '<script src="'.$GLOBALS['BACK_PATH'].'contrib/scriptaculous/scriptaculous.js" type="text/javascript" id="scriptaculous-script"></script>';
+                               // include needed javascript-frameworks
+                       $doc->loadJavascriptLib('contrib/prototype/prototype.js');
+                       $doc->loadJavascriptLib('contrib/scriptaculous/scriptaculous.js');
 
                                // include editor-css
                        $code.= '<link href="'.$GLOBALS['BACK_PATH'].t3lib_extmgm::extRelPath('t3editor').$this->filepathEditorcss.'" type="text/css" rel="stylesheet" />';
-
                                // include editor-js-lib
-                       $code.= '<script src="'.$GLOBALS['BACK_PATH'].t3lib_extmgm::extRelPath('t3editor').$this->filepathEditorlib.'" type="text/javascript" id="t3editor-script"></script>';
+                       $doc->loadJavascriptLib($path_t3e.'jslib/codemirror/codemirror.js');
+                       $doc->loadJavascriptLib($path_t3e.'jslib/t3editor.js');
+
+                       // set correct path to the editor
+                       $code.= '<script type="text/javascript">'.
+                               'PATH_t3e = "'.$GLOBALS['BACK_PATH']. t3lib_extmgm::extRelPath('t3editor').'"; '.
+                               '</script>';
+
                }
 
                return $code;
@@ -191,6 +190,97 @@ class tx_t3editor {
                return $code;
        }
 
+
+       public function makeGlobalEditorInstance() {
+               if (!is_object($GLOBALS['T3_VAR']['t3editorObj'])) {
+                       $GLOBALS['T3_VAR']['t3editorObj'] = t3lib_div::getUserObj('EXT:t3editor/class.tx_t3editor.php:&tx_t3editor');
+               }
+       }
+
+       /**
+        * Hook-function: inject t3editor JavaScript code before the page is compiled
+        * called in typo3/template.php:startPage
+        *
+        * @param array $parameters
+        * @param template $pObj
+        */
+       public function preStartPageHook($parameters, $pObj) {
+                       // enable editor in Template-Modul
+               if (preg_match('/sysext\/tstemplate\/ts\/index\.php/', $_SERVER['SCRIPT_NAME'])) {
+
+                       tx_t3editor::makeGlobalEditorInstance();
+
+                       // insert javascript code in document header
+                       $pObj->JScode .= $GLOBALS['T3_VAR']['t3editorObj']->getJavascriptCode($pObj);
+               }
+       }
+
+
+       /**
+        * Hook-function:
+        * called in typo3/sysext/tstemplate_info/class.tx_tstemplateinfo.php
+        *
+        * @param array $parameters
+        * @param tx_tstemplateinfo $pObj
+        */
+       public function postTCEProcessingHook($parameters, $pObj) {
+               if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
+                       tx_t3editor::makeGlobalEditorInstance();
+                       $GLOBALS['T3_VAR']['t3editorObj']->setBEUCdisableT3Editor(false);
+
+                       header('X-JSON: ('.json_encode(array('result' => $pObj->tce_processed)).')');
+                       // Stop further processing here!
+                       exit();
+               }
+       }
+
+       /**
+        * Hook-function:
+        * called in typo3/sysext/tstemplate_info/class.tx_tstemplateinfo.php
+        *
+        * @param array $parameters
+        * @param tx_tstemplateinfo $pObj
+        */
+       public function postOutputProcessingHook($parameters, $pObj) {
+               tx_t3editor::makeGlobalEditorInstance();
+               if (!$GLOBALS['T3_VAR']['t3editorObj']->isEnabled) {
+                       return;
+               }
+
+               // Template Constants
+               if ($parameters['e']['constants']) {
+                       $outCode = $GLOBALS['T3_VAR']['t3editorObj']->getCodeEditor(
+                                               'data[constants]',      // name
+                                               'fixed-font enable-tab',        // class
+                                               t3lib_div::formatForTextarea($parameters['tplRow']['constants']),       // content
+                                               'rows="'.$parameters['numberOfRows'].'" wrap="off" '.$pObj->pObj->doc->formWidthText(48, 'width:98%;height:60%', 'off'),
+                                               'Template: '.htmlspecialchars($parameters['tplRow']['title']).': Constants' // title
+                                       );
+                       $parameters['theOutput'] = preg_replace(
+                               '/\<textarea name="data\[constants\]".*\>([^\<]*)\<\/textarea\>/mi',
+                               $outCode,
+                               $parameters['theOutput']
+                               );
+               }
+
+               // Template Setup
+               if ($parameters['e']['config']) {
+                       $outCode = $GLOBALS['T3_VAR']['t3editorObj']->getCodeEditor(
+                                               'data[config]', // name
+                                               'fixed-font enable-tab',        // class
+                                               t3lib_div::formatForTextarea($parameters['tplRow']['config']),  // content
+                                               'rows="'.$parameters['numberOfRows'].'" wrap="off" '.$pObj->pObj->doc->formWidthText(48, 'width:98%;height:60%', 'off'),
+                                               'Template: '.htmlspecialchars($parameters['tplRow']['title']).': Setup' // title
+                                       );
+                       $parameters['theOutput'] = preg_replace(
+                               '/\<textarea name="data\[config\]".*\>([^\<]*)\<\/textarea\>/mi',
+                               $outCode,
+                               $parameters['theOutput']
+                               );
+               }
+       }
+
+
 }
 
 
index f7bc92e..d0b271d 100755 (executable)
@@ -1,58 +1,20 @@
 
-.editbox {
-  border-width: 0;
-  margin: .0em;
-  padding: 0;
-  font-family: monospace;
-  font-size: 12px;
-  color: black;
-  background-color: #fff;
-}
-
-.editbox p {
-  margin: 0;
-}
-
-
-/*********************************************
- * Syntax highlighting
- *********************************************/
-
-span.other { color: black; }
-.ts-operator { color: #0000cc; font-weight: bold; }
-.ts-value { color: #cc0000; }
-.ts-objstr, .keyword, .keyword2, .keyword3, .reserved { color: #0000cc; }
-.ts-value_copy { color: #006600; }
-.ts-value_unset { background-color: #66cc66; }
-.ts-ignored { background-color: #66cc66; }
-.ts-default { background-color: #66cc66; }
-.ts-comment { color: #666; font-style: italic; }
-.ts-condition { background-color: maroon; color: #fff; font-weight: bold; }
-.ts-error { background-color: yellow; border: 1px red dashed; font-weight: bold; }
-.highlight-bracket {background-color: #0c0; color: #fff; }
-.error-bracket {background-color: #d00; color: #fff; }
-
-/* unparsed code */
-pre.code, .editbox {
-  color: #666;
-}
-
-
-
 /* around the editor */
-.t3e_outerdiv {
+.t3e_wrap {
        border: 1px solid gray;
        position: relative;
     background-color: #EFEFF4;
-       background-image: url('../icons/loader_eeeeee.gif');
+       background-image: url('../../../gfx/spinner.gif');
        background-position: 49% 50%;
        background-repeat: no-repeat;
+       margin-right: 10px;
 }
+
 .t3e_modalOverlay {
        position: absolute;
        top: 0; left: 0;
     background-color: #EFEFF4;
-       background-image: url('../icons/loader_eeeeee.gif');
+       background-image: url('../../../gfx/spinner.gif');
        background-position: 49% 50%;
        background-repeat: no-repeat;
        z-index:200;
@@ -123,14 +85,33 @@ pre.code, .editbox {
        padding: 0;
 }
 
-.t3e_footer_wrap {
+.t3e_toolbar_wrap {
        clear: both;
        width: 100%;
        font-size: 0.9em;
        padding-right: 20px;
 }
 
-.t3e_footer_item {
+
+
+
+
+.t3e_statusbar_wrap {
+       clear: both;
+       font-size: 0.9em;
+       padding-left: 3px;
+       padding-top: 3px;
+       background-color:#B8BEC9;
+       background-image: url(../../t3skin/icons/gfx/alt_menu_mainitem_bg.gif);
+       background-position:left top;
+       background-repeat:repeat-x;
+       height: 20px;
+}
+.t3e_statusbar_wrap span {
+       color:#FFFFFF;
+       margin: 3px;
+}
+.t3e_statusbar_item {
        float: right;
        height: 16px;
        border-left: 1px solid gray;
@@ -143,7 +124,7 @@ pre.code, .editbox {
 }
 
 
-.t3e_footer_overlay {
+.t3e_statusbar_overlay {
        position: absolute; bottom: 20px; right: 17px; 
        opacity: 0.85;
        background-color: #EFEFF4;
@@ -155,23 +136,23 @@ pre.code, .editbox {
        border-top: 1px solid gray;
        z-index: 100;
 }
-.t3e_footer_overlay#t3e_footer_overlay_options {
+.t3e_statusbar_overlay#t3e_statusbar_overlay_options {
        height: 8em;
 }
-.t3e_footer_overlay ul {
+.t3e_statusbar_overlay ul {
        list-style-type: none;
        padding: 0;
        margin: 0;
 }
-.t3e_footer_overlay ul li {
+.t3e_statusbar_overlay ul li {
        color: #212121;
        padding: 2px 6px 2px 6px;
        cursor:pointer;cursor:hand;
 }
-.t3e_footer_overlay ul li label {
+.t3e_statusbar_overlay ul li label {
        cursor:pointer;cursor:hand;
        }
-.t3e_footer_overlay ul li:hover {
+.t3e_statusbar_overlay ul li:hover {
        background-color: #cfcfcf;
 }
 
diff --git a/typo3/sysext/t3editor/css/t3editor_inner.css b/typo3/sysext/t3editor/css/t3editor_inner.css
new file mode 100644 (file)
index 0000000..0d498cb
--- /dev/null
@@ -0,0 +1,39 @@
+
+.editbox {
+  border-width: 0;
+  margin: .0em;
+  padding: 0;
+  font-family: monospace;
+  font-size: 12px;
+  color: black;
+  background-color: #fff;
+}
+
+.editbox p {
+  margin: 0;
+}
+
+
+/*********************************************
+ * Syntax highlighting
+ *********************************************/
+
+span.other { color: black; }
+.ts-operator { color: #0000cc; font-weight: bold; }
+.ts-value { color: #cc0000; }
+.ts-objstr, .keyword, .keyword2, .keyword3, .reserved { color: #0000cc; }
+.ts-value_copy { color: #006600; }
+.ts-value_unset { background-color: #66cc66; }
+.ts-ignored { background-color: #66cc66; }
+.ts-default { background-color: #66cc66; }
+.ts-comment { color: #666; font-style: italic; }
+.ts-condition { background-color: maroon; color: #fff; font-weight: bold; }
+.ts-error { background-color: yellow; border: 1px red dashed; font-weight: bold; }
+.highlight-bracket {background-color: #0c0; color: #fff; }
+.error-bracket {background-color: #d00; color: #fff; }
+
+/* unparsed code */
+pre.code, .editbox {
+  color: #666;
+}
+
index ec47129..e215c7b 100755 (executable)
@@ -32,7 +32,7 @@ $EM_CONF[$_EXTKEY] = array(
        'author_company' => '',
        'CGLcompliance' => '',
        'CGLcompliance_note' => '',
-       'version' => '0.0.9',
+       'version' => '0.0.10',
        '_md5_values_when_last_written' => 'a:14:{s:21:"class.tx_t3editor.php";s:4:"bb07";s:12:"ext_icon.gif";s:4:"4cef";s:16:"css/t3editor.css";s:4:"d4f1";s:23:"icons/loader_eeeeee.gif";s:4:"83a4";s:13:"jslib/LICENSE";s:4:"d835";s:14:"jslib/Mochi.js";s:4:"872d";s:24:"jslib/parsejavascript.js";s:4:"5377";s:24:"jslib/parsetyposcript.js";s:4:"7815";s:15:"jslib/select.js";s:4:"6725";s:21:"jslib/stringstream.js";s:4:"e6a5";s:17:"jslib/t3editor.js";s:4:"656c";s:27:"jslib/tokenizejavascript.js";s:4:"1c7a";s:27:"jslib/tokenizetyposcript.js";s:4:"c232";s:13:"jslib/util.js";s:4:"7620";}',
        'constraints' => array(
                'depends' => array(
@@ -40,7 +40,6 @@ $EM_CONF[$_EXTKEY] = array(
                        'typo3' => '4.2-0.0.0',
                ),
                'conflicts' => array(
-                       'pmktextarea' => '',
                ),
                'suggests' => array(
                ),
diff --git a/typo3/sysext/t3editor/ext_localconf.php b/typo3/sysext/t3editor/ext_localconf.php
new file mode 100644 (file)
index 0000000..89ea1a3
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+if (!defined ('TYPO3_MODE'))   die ('Access denied.');
+
+
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['preStartPageHook'][] = 'EXT:t3editor/class.tx_t3editor.php:&tx_t3editor->preStartPageHook';
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/tstemplate_info/class.tx_tstemplateinfo.php']['postTCEProcessingHook'][] = 'EXT:t3editor/class.tx_t3editor.php:&tx_t3editor->postTCEProcessingHook';
+$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/tstemplate_info/class.tx_tstemplateinfo.php']['postOutputProcessingHook'][] = 'EXT:t3editor/class.tx_t3editor.php:&tx_t3editor->postOutputProcessingHook';
+
+ ?>
\ No newline at end of file
diff --git a/typo3/sysext/t3editor/jslib/codemirror/LICENSE b/typo3/sysext/t3editor/jslib/codemirror/LICENSE
new file mode 100644 (file)
index 0000000..12134c0
--- /dev/null
@@ -0,0 +1,23 @@
+ Copyright (c) 2007 Marijn Haverbeke
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any
+ damages arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any
+ purpose, including commercial applications, and to alter it and
+ redistribute it freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must
+    not claim that you wrote the original software. If you use this
+    software in a product, an acknowledgment in the product
+    documentation would be appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must
+    not be misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+    distribution.
+
+ Marijn Haverbeke
+ marijnh at gmail
diff --git a/typo3/sysext/t3editor/jslib/codemirror/README b/typo3/sysext/t3editor/jslib/codemirror/README
new file mode 100644 (file)
index 0000000..1b223dc
--- /dev/null
@@ -0,0 +1,16 @@
+The t3editor is based on the CodeMirror-project by Marijn Haverbeke (http://marijn.haverbeke.nl/codemirror/)
+
+Currently the release 0.55 is used. 
+To use the CodeMirror for the t3editor some changes were needed.  The file "patch.codemirror055.diff" contains a patch to apply the needed changes in the codemirror source.
+
+See the LICENSE file for the CodeMirror license.
+All files in this folder (typo3/sysext/t3editor/jslib/codemirror/) are part of the CodeMirror and therefore licened under the CodeMirror license.
+
+t3editor itself is part of TYPO3 and therefore licened under GPL (as TYPO3 is).
+
+
+thanks,
+
+  Tobias Liebig
+  mail(at)etobi.de
+
diff --git a/typo3/sysext/t3editor/jslib/codemirror/codemirror.js b/typo3/sysext/t3editor/jslib/codemirror/codemirror.js
new file mode 100644 (file)
index 0000000..f92cc75
--- /dev/null
@@ -0,0 +1,165 @@
+/* CodeMirror main module
+ *
+ * Implements the CodeMirror constructor and prototype, which take care
+ * of initializing the editor frame, and providing the outside interface.
+ */
+
+// The CodeMirrorConfig object is used to specify a default
+// configuration. If you specify such an object before loading this
+// file, the values you put into it will override the defaults given
+// below. You can also assign to it after loading.
+var CodeMirrorConfig = window.CodeMirrorConfig || {};
+
+var CodeMirror = (function(){
+  function setDefaults(object, defaults) {
+    for (var option in defaults) {
+      if (!object.hasOwnProperty(option))
+        object[option] = defaults[option];
+    }
+  }
+  function forEach(array, action) {
+    for (var i = 0; i < array.length; i++)
+      action(array[i]);
+  }
+
+  // These default options can be overridden by passing a set of
+  // options to a specific CodeMirror constructor. See manual.html for
+  // their meaning.
+  setDefaults(CodeMirrorConfig, {
+    stylesheet: "",
+    path: "",
+    parserfile: [],
+    basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js"],
+    linesPerPass: 15,
+    passDelay: 200,
+    continuousScanning: false,
+    saveFunction: null,
+    undoDepth: 20,
+    undoDelay: 800,
+    disableSpellcheck: true,
+    textWrapping: true,
+    readOnly: false,
+    width: "100%",
+    height: "300px",
+    parserConfig: null,
+       outerEditor: null
+  });
+
+  function CodeMirror(place, options) {
+    // Use passed options, if any, to override defaults.
+    this.options = options = options || {};
+    setDefaults(options, CodeMirrorConfig);
+
+    frame = document.createElement("IFRAME");
+    frame.style.border = "0";
+    frame.style.width = options.width;
+    frame.style.height = options.height;
+    // display: block occasionally suppresses some Firefox bugs, so we
+    // always add it, redundant as it sounds.
+    frame.style.display = "block";
+
+    if (place.appendChild)
+      place.appendChild(frame);
+    else
+      place(frame);
+
+    // Link back to this object, so that the editor can fetch options
+    // and add a reference to itself.
+    frame.CodeMirror = this;
+    this.win = frame.contentWindow;
+
+    if (typeof options.parserfile == "string")
+      options.parserfile = [options.parserfile];
+    var html = ["<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"" + options.stylesheet + "\"/>"];
+    forEach(options.basefiles.concat(options.parserfile), function(file) {
+      html.push("<script type=\"text/javascript\" src=\"" + options.path + file + "\"></script>");
+    });
+    html.push("</head><body style=\"border-width: 0;\" class=\"editbox\" spellcheck=\"" +
+              (options.disableSpellcheck ? "false" : "true") + "\"></body></html>");
+
+    var doc = this.win.document;
+    doc.open();
+    doc.write(html.join(""));
+    doc.close();
+  }
+
+  CodeMirror.prototype = {
+    getCode: function() {
+      return this.editor.getCode();
+    },
+    setCode: function(code) {
+      this.editor.importCode(code);
+    },
+    focus: function() {
+      this.win.focus();
+    },
+    jumpToChar: function(start, end) {
+      this.editor.jumpToChar(start, end);
+      this.focus();
+    },
+    jumpToLine: function(line) {
+      this.editor.jumpToLine(line);
+      this.focus();
+    },
+    currentLine: function() {
+      return this.editor.currentLine();
+    },
+    selection: function() {
+      return this.editor.selectedText();
+    },
+    reindent: function() {
+      this.editor.reindent();
+    },
+    replaceSelection: function(text, focus) {
+      this.editor.replaceSelection(text);
+      if (focus) this.focus();
+    },
+    replaceChars: function(text, start, end) {
+      this.editor.replaceChars(text, start, end);
+    },
+    getSearchCursor: function(string, fromCursor) {
+      return this.editor.getSearchCursor(string, fromCursor);
+    }
+  };
+
+  CodeMirror.replace = function(element) {
+    if (typeof element == "string")
+      element = document.getElementById(element);
+    return function(newElement) {
+      element.parentNode.replaceChild(newElement, element);
+    };
+  };
+
+  CodeMirror.fromTextArea = function(area, options) {
+    if (typeof area == "string")
+      area = document.getElementById(area);
+
+    options = options || {};
+    if (area.style.width) options.width = area.style.width;
+    if (area.style.height) options.height = area.style.height;
+    if (options.content == null) options.content = area.value;
+
+    if (area.form) {
+      function updateField() {
+        area.value = mirror.getCode();
+      }
+      if (typeof area.form.addEventListener == "function")
+        area.form.addEventListener("submit", updateField, false);
+      else
+        area.form.attachEvent("onsubmit", updateField);
+    }
+
+    function insert(frame) {
+      if (area.nextSibling)
+        area.parentNode.insertBefore(frame, area.nextSibling);
+      else
+        area.parentNode.appendChild(frame);
+    }
+
+    area.style.display = "none";
+    var mirror = new CodeMirror(insert, options);
+    return mirror;
+  };
+
+  return CodeMirror;
+})();
diff --git a/typo3/sysext/t3editor/jslib/codemirror/editor.js b/typo3/sysext/t3editor/jslib/codemirror/editor.js
new file mode 100644 (file)
index 0000000..0ed5901
--- /dev/null
@@ -0,0 +1,976 @@
+/* The Editor object manages the content of the editable frame. It
+ * catches events, colours nodes, and indents lines. This file also
+ * holds some functions for transforming arbitrary DOM structures into
+ * plain sequences of <span> and <br> elements
+ */
+
+var Editor = (function(){
+  // The HTML elements whose content should be suffixed by a newline
+  // when converting them to flat text.
+  var newlineElements = {"P": true, "DIV": true, "LI": true};
+
+  // Create a set of white-space characters that will not be collapsed
+  // by the browser, but will not break text-wrapping either.
+  function safeWhiteSpace(n) {
+    var buffer = [], nb = true;
+    for (; n > 0; n--) {
+      buffer.push((nb || n == 1) ? nbsp : " ");
+      nb = !nb;
+    }
+    return buffer.join("");
+  }
+
+  function splitSpaces(string) {
+    return string.replace(/[\t \u00a0]{2,}/g, function(s) {return safeWhiteSpace(s.length);});
+  }
+  function asEditorLines(string) {
+    return splitSpaces(string.replace(/\u00a0/g, " ")).replace(/\r\n?/g, "\n").split("\n");
+  }
+
+  // Helper function for traverseDOM. Flattens an arbitrary DOM node
+  // into an array of textnodes and <br> tags.
+  function simplifyDOM(root) {
+    var doc = root.ownerDocument;
+    var result = [];
+    var leaving = false;
+
+    function simplifyNode(node) {
+      if (node.nodeType == 3) {
+        var text = node.nodeValue = splitSpaces(node.nodeValue.replace(/[\n\r]/g, ""));
+        if (text.length) leaving = false;
+        result.push(node);
+      }
+      else if (node.nodeName == "BR" && node.childNodes.length == 0) {
+        leaving = false;
+        result.push(node);
+      }
+      else {
+        forEach(node.childNodes, simplifyNode);
+        if (!leaving && newlineElements.hasOwnProperty(node.nodeName)) {
+          leaving = true;
+          result.push(doc.createElement("BR"));
+        }
+      }
+    }
+
+    simplifyNode(root);
+    return result;
+  }
+
+  // Creates a MochiKit-style iterator that goes over a series of DOM
+  // nodes. The values it yields are strings, the textual content of
+  // the nodes. It makes sure that all nodes up to and including the
+  // one whose text is being yielded have been 'normalized' to be just
+  // <span> and <br> elements.
+  // See the story.html file for some short remarks about the use of
+  // continuation-passing style in this iterator.
+  function traverseDOM(start){
+    function yield(value, c){cc = c; return value;}
+    function push(fun, arg, c){return function(){return fun(arg, c);};}
+    function stop(){cc = stop; throw StopIteration;};
+    var cc = push(scanNode, start, stop);
+    var owner = start.ownerDocument;
+    var nodeQueue = [];
+
+    // Create a function that can be used to insert nodes after the
+    // one given as argument.
+    function pointAt(node){
+      var parent = node.parentNode;
+      var next = node.nextSibling;
+      if (next)
+        return function(newnode){parent.insertBefore(newnode, next);};
+      else
+        return function(newnode){parent.appendChild(newnode);};
+    }
+    var point = null;
+
+    // Insert a normalized node at the current point. If it is a text
+    // node, wrap it in a <span>, and give that span a currentText
+    // property -- this is used to cache the nodeValue, because
+    // directly accessing nodeValue is horribly slow on some browsers.
+    // The dirty property is used by the highlighter to determine
+    // which parts of the document have to be re-highlighted.
+    function insertPart(part){
+      var text = "\n";
+      if (part.nodeType == 3) {
+        text = part.nodeValue;
+        var span = owner.createElement("SPAN");
+        span.className = "part";
+        span.appendChild(part);
+        part = span;
+        part.currentText = text;
+      }
+      part.dirty = true;
+      nodeQueue.push(part);
+      point(part);
+      return text;
+    }
+
+    // Extract the text and newlines from a DOM node, insert them into
+    // the document, and yield the textual content. Used to replace
+    // non-normalized nodes.
+    function writeNode(node, c){
+      var toYield = [];
+      forEach(simplifyDOM(node), function(part) {
+        toYield.push(insertPart(part));
+      });
+      return yield(toYield.join(""), c);
+    }
+
+    // Check whether a node is a normalized <span> element.
+    function partNode(node){
+      if (node.nodeName == "SPAN" && node.childNodes.length == 1 && node.firstChild.nodeType == 3){
+        node.currentText = node.firstChild.nodeValue;
+        return true;
+      }
+      return false;
+    }
+
+    // Handle a node. Add its successor to the continuation if there
+    // is one, find out whether the node is normalized. If it is,
+    // yield its content, otherwise, normalize it (writeNode will take
+    // care of yielding).
+    function scanNode(node, c){
+      if (node.nextSibling)
+        c = push(scanNode, node.nextSibling, c);
+
+      if (partNode(node)){
+        nodeQueue.push(node);
+        return yield(node.currentText, c);
+      }
+      else if (node.nodeName == "BR") {
+        nodeQueue.push(node);
+        return yield("\n", c);
+      }
+      else {
+        point = pointAt(node);
+        removeElement(node);
+        return writeNode(node, c);
+      }
+    }
+
+    // MochiKit iterators are objects with a next function that
+    // returns the next value or throws StopIteration when there are
+    // no more values.
+    return {next: function(){return cc();}, nodes: nodeQueue};
+  }
+
+  // Determine the text size of a processed node.
+  function nodeSize(node) {
+    if (node.nodeName == "BR")
+      return 1;
+    else
+      return node.currentText.length;
+  }
+
+  // Search backwards through the top-level nodes until the next BR or
+  // the start of the frame.
+  function startOfLine(node) {
+    while (node && node.nodeName != "BR")
+      node = node.previousSibling;
+    return node;
+  }
+
+  function cleanText(text) {
+    return text.replace(/\u00a0/g, " ");
+  }
+
+  // Client interface for searching the content of the editor. Create
+  // these by calling CodeMirror.getSearchCursor. To use, call
+  // findNext on the resulting object -- this returns a boolean
+  // indicating whether anything was found, and can be called again to
+  // skip to the next find. Use the select and replace methods to
+  // actually do something with the found locations.
+  function SearchCursor(editor, string, fromCursor) {
+    this.editor = editor;
+    this.history = editor.history;
+    this.history.commit();
+
+    // Are we currently at an occurrence of the search string?
+    this.atOccurrence = false;
+    // The object stores a set of nodes coming after its current
+    // position, so that when the current point is taken out of the
+    // DOM tree, we can still try to continue.
+    this.fallbackSize = 15;
+    var cursor;
+    // Start from the cursor when specified and a cursor can be found.
+    if (fromCursor && (cursor = select.cursorPos(this.editor.container))) {
+      this.line = cursor.node;
+      this.offset = cursor.offset;
+    }
+    else {
+      this.line = null;
+      this.offset = 0;
+    }
+    this.valid = !!string;
+
+    // Create a matcher function based on the kind of string we have.
+    var target = string.split("\n"), self = this;;
+    this.matches = (target.length == 1) ?
+      // For one-line strings, searching can be done simply by calling
+      // indexOf on the current line.
+      function() {
+        var match = cleanText(self.history.textAfter(self.line).slice(self.offset)).indexOf(string);
+        if (match > -1)
+          return {from: {node: self.line, offset: self.offset + match},
+                  to: {node: self.line, offset: self.offset + match + string.length}};
+      } :
+      // Multi-line strings require internal iteration over lines, and
+      // some clunky checks to make sure the first match ends at the
+      // end of the line and the last match starts at the start.
+      function() {
+        var firstLine = cleanText(self.history.textAfter(self.line).slice(self.offset));
+        var match = firstLine.lastIndexOf(target[0]);
+        if (match == -1 || match != firstLine.length - target[0].length)
+          return false;
+        var startOffset = self.offset + match;
+
+        var line = self.history.nodeAfter(self.line);
+        for (var i = 1; i < target.length - 1; i++) {
+          if (cleanText(self.history.textAfter(line)) != target[i])
+            return false;
+          line = self.history.nodeAfter(line);
+        }
+
+        if (cleanText(self.history.textAfter(line)).indexOf(target[target.length - 1]) != 0)
+          return false;
+
+        return {from: {node: self.line, offset: startOffset},
+                to: {node: line, offset: target[target.length - 1].length}};
+      };
+  }
+
+  SearchCursor.prototype = {
+    findNext: function() {
+      if (!this.valid) return false;
+      this.atOccurrence = false;
+      var self = this;
+
+      // Go back to the start of the document if the current line is
+      // no longer in the DOM tree.
+      if (this.line && !this.line.parentNode) {
+        this.line = null;
+        this.offset = 0;
+      }
+
+      // Set the cursor's position one character after the given
+      // position.
+      function saveAfter(pos) {
+        if (self.history.textAfter(pos.node).length < pos.offset) {
+          self.line = pos.node;
+          self.offset = pos.offset + 1;
+        }
+        else {
+          self.line = self.history.nodeAfter(pos.node);
+          self.offset = 0;
+        }
+      }
+
+      while (true) {
+        var match = this.matches();
+        // Found the search string.
+        if (match) {
+          this.atOccurrence = match;
+          saveAfter(match.from);
+          return true;
+        }
+        this.line = this.history.nodeAfter(this.line);
+        this.offset = 0;
+        // End of document.
+        if (!this.line) {
+          this.valid = false;
+          return false;
+        }
+      }
+    },
+
+    select: function() {
+      if (this.atOccurrence) {
+        select.setCursorPos(this.editor.container, this.atOccurrence.from, this.atOccurrence.to);
+        select.scrollToCursor(this.editor.container);
+      }
+    },
+
+    replace: function(string) {
+      if (this.atOccurrence) {
+        this.editor.replaceRange(this.atOccurrence.from, this.atOccurrence.to, string);
+        this.line = this.atOccurrence.from.node;
+        this.offset = this.atOccurrence.from.offset;
+        this.atOccurrence = false;
+      }
+    }
+  };
+
+  // The Editor object is the main inside-the-iframe interface.
+  function Editor(options) {
+    this.options = options;
+    this.parent = parent;
+    this.doc = document;
+    this.container = this.doc.body;
+    this.win = window;
+    this.history = new History(this.container, this.options.undoDepth, this.options.undoDelay, this);
+
+    if (!Editor.Parser)
+      throw "No parser loaded.";
+    if (options.parserConfig && Editor.Parser.configure)
+      Editor.Parser.configure(options.parserConfig);
+
+    if (!options.textWrapping)
+      this.doc.body.style.whiteSpace = "pre";
+
+    this.dirty = [];
+    if (options.content)
+      this.importCode(options.content);
+    else // FF acts weird when the editable document is completely empty
+      this.container.appendChild(this.doc.createElement("SPAN"));
+
+    if (!options.readOnly) {
+      if (options.continuousScanning !== false) {
+        this.scanner = this.documentScanner(options.linesPerPass);
+        this.delayScanning();
+      }
+
+      // In IE, designMode frames can not run any scripts, so we use
+      // contentEditable instead. Random ActiveX check is there because
+      // Opera apparently also supports some kind of perverted form of
+      // contentEditable.
+      if (document.body.contentEditable != undefined && window.ActiveXObject)
+        document.body.contentEditable = "true";
+      else
+        document.designMode = "on";
+
+      addEventHandler(document, "keydown", method(this, "keyDown"));
+      addEventHandler(document, "keypress", method(this, "keyPress"));
+      addEventHandler(document, "keyup", method(this, "keyUp"));
+      addEventHandler(document.body, "paste", method(this, "markCursorDirty"));
+         if (options.outerEditor && options.outerEditor.scroll) {
+               addEventHandler(document, "scroll", method(options.outerEditor, "scroll"));
+               // addEventHandler(window, "scroll", method(options.outerEditor, "scroll"));
+         }
+         if (options.outerEditor && options.outerEditor.click) {
+               addEventHandler(document, "click", method(options.outerEditor, "click"));
+         }
+    }
+  }
+
+  function isSafeKey(code) {
+    return (code >= 16 && code <= 18) || // shift, control, alt
+           (code >= 33 && code <= 40); // arrows, home, end
+  }
+
+  Editor.prototype = {
+    // Import a piece of code into the editor.
+    importCode: function(code) {
+      this.history.push(null, null, asEditorLines(code));
+      this.history.reset();
+         if (this.options.outerEditor && this.options.outerEditor.updateLinenum) {
+               this.options.outerEditor.updateLinenum(code);
+         }
+    },
+
+    // Extract the code from the editor.
+    getCode: function() {
+      if (!this.container.firstChild)
+        return "";
+
+      var accum = [];
+      forEach(traverseDOM(this.container.firstChild), method(accum, "push"));
+      return cleanText(accum.join(""));
+    },
+
+    // Move the cursor to the start of a specific line (counting from 1).
+    jumpToLine: function(line) {
+      if (line <= 1 || !this.container.firstChild) {
+        select.focusAfterNode(null, this.container);
+      }
+      else {
+        var pos = this.container.firstChild;
+        while (true) {
+          if (pos.nodeName == "BR") line--;
+          if (line <= 1 || !pos.nextSibling) break;
+          pos = pos.nextSibling;
+        }
+        select.focusAfterNode(pos, this.container);
+      }
+      select.scrollToCursor(this.container);
+    },
+
+    // Find the line that the cursor is currently on.
+    currentLine: function() {
+      var pos = select.cursorPos(this.container, true), line = 1;
+      if (!pos) return 1;
+      for (cursor = pos.node; cursor; cursor = cursor.previousSibling)
+        if (cursor.nodeName == "BR") line++;
+      return line;
+    },
+
+    // Retrieve the selected text.
+    selectedText: function() {
+      var h = this.history;
+      h.commit();
+
+      var start = select.cursorPos(this.container, true),
+          end = select.cursorPos(this.container, false);
+      if (!start || !end) return "";
+
+      if (start.node == end.node)
+        return h.textAfter(start.node).slice(start.offset, end.offset);
+
+      var text = [h.textAfter(start.node).slice(start.offset)];
+      for (pos = h.nodeAfter(start.node); pos != end.node; pos = h.nodeAfter(pos))
+        text.push(h.textAfter(pos));
+      text.push(h.textAfter(end.node).slice(0, end.offset));
+      return cleanText(text.join("\n"));
+    },
+
+    // Replace the selection with another piece of text.
+    replaceSelection: function(text) {
+      this.history.commit();
+      var start = select.cursorPos(this.container, true),
+          end = select.cursorPos(this.container, false);
+      if (!start || !end) return;
+
+      end = this.replaceRange(start, end, text);
+      select.setCursorPos(this.container, start, end);
+    },
+
+    replaceRange: function(from, to, text) {
+      var lines = asEditorLines(text);
+      lines[0] = this.history.textAfter(from.node).slice(0, from.offset) + lines[0];
+      var lastLine = lines[lines.length - 1];
+      lines[lines.length - 1] = lastLine + this.history.textAfter(to.node).slice(to.offset);
+      var end = this.history.nodeAfter(to.node)
+      this.history.push(from.node, end, lines);
+      return {node: this.history.nodeBefore(end),
+              offset: lastLine.length};
+    },
+
+    getSearchCursor: function(string, fromCursor) {
+      return new SearchCursor(this, string, fromCursor);
+    },
+
+    // Re-indent the whole buffer
+    reindent: function() {
+      if (this.container.firstChild)
+        this.indentRegion(null, this.container.lastChild);
+    },
+
+    // Intercept enter and tab, and assign their new functions.
+    keyDown: function(event) {
+
+         // Don't scan when the user is typing.
+      this.delayScanning();
+
+         if (
+               this.options.outerEditor 
+               && this.options.outerEditor.checkTextModified
+               && !isSafeKey(event.keyCode)
+               && !event.ctrlKey ) {
+                       this.options.outerEditor.checkTextModified();
+         }
+
+      if (event.keyCode == 13) { // enter
+        if (event.ctrlKey) {
+          this.reparseBuffer();
+        }
+        else {
+          select.insertNewlineAtCursor(this.win);
+          this.indentAtCursor();
+          select.scrollToCursor(this.container);
+                 if (this.options.outerEditor && this.options.outerEditor.updateLinenum) {
+                       this.options.outerEditor.updateLinenum();
+                 }
+        }
+        event.stop();
+      }
+      else if (event.keyCode == 9) { // tab
+        this.handleTab();
+        event.stop();
+      }
+      else if (event.ctrlKey) {
+        if (event.keyCode == 90 || event.keyCode == 8) { // Z, backspace
+          this.history.undo();
+          event.stop();
+        }
+        else if (event.keyCode == 89) { // Y
+          this.history.redo();
+          event.stop();
+        }
+        else if (event.keyCode == 83 && this.options.saveFunction) { // S
+          this.options.saveFunction();
+          event.stop();
+        } 
+               else if (event.keyCode == 122 ) { // F11 toogle fullscreen mode
+                 if (this.options.outerEditor && this.options.outerEditor.toggleFullscreen) {
+                       this.options.outerEditor.toggleFullscreen();
+                       event.stop();
+                 }
+               }
+      }
+    },
+
+    // Check for characters that should re-indent the current line,
+    // and prevent Opera from handling enter and tab anyway.
+    keyPress: function(event) {
+         var electric = Editor.Parser.electricChars;
+      // Hack for Opera, and Firefox on OS X, in which stopping a
+      // keydown event does not prevent the associated keypress event
+      // from happening, so we have to cancel enter and tab again
+      // here.
+      if (event.code == 13 || event.code == 9)
+        event.stop();
+      else if (electric && electric.indexOf(event.character) != -1) {
+               this.parent.setTimeout(method(this, "indentAtCursor"), 0);
+         }
+               
+    },
+
+    // Mark the node at the cursor dirty when a non-safe key is
+    // released.
+    keyUp: function(event) {
+      if (!isSafeKey(event.keyCode))
+        this.markCursorDirty();
+
+         if (event.keyCode == 46       // Delete
+               || event.keyCode == 13  // Return
+               || event.keyCode == 8) {        // Backspace
+                if (this.options.outerEditor && this.options.outerEditor.updateLinenum) {
+                       this.options.outerEditor.updateLinenum();
+                }
+         }
+         if (this.options.outerEditor && this.options.outerEditor.checkBracketAtCursor) {
+               this.options.outerEditor.checkBracketAtCursor();
+         }
+         
+    },
+
+    // Indent the line following a given <br>, or null for the first
+    // line. If given a <br> element, this must have been highlighted
+    // so that it has an indentation method. Returns the whitespace
+    // element that has been modified or created (if any).
+    indentLineAfter: function(start) {
+      // whiteSpace is the whitespace span at the start of the line,
+      // or null if there is no such node.
+      var whiteSpace = start ? start.nextSibling : this.container.firstChild;
+      if (whiteSpace && !hasClass(whiteSpace, "whitespace"))
+        whiteSpace = null;
+
+      // Sometimes the start of the line can influence the correct
+      // indentation, so we retrieve it.
+      var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild);
+      var nextChars = (start && firstText && firstText.currentText) ? firstText.currentText : "";
+
+      // Ask the lexical context for the correct indentation, and
+      // compute how much this differs from the current indentation.
+      var indent = start ? start.indentation(nextChars) : 0;
+      var indentDiff = indent - (whiteSpace ? whiteSpace.currentText.length : 0);
+
+      // If there is too much, this is just a matter of shrinking a span.
+      if (indentDiff < 0) {
+        if (indent == 0) {
+          removeElement(whiteSpace);
+          whiteSpace = null;
+        }
+        else {
+          whiteSpace.currentText = safeWhiteSpace(indent);
+          whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
+        }
+      }
+      // Not enough...
+      else if (indentDiff > 0) {
+        // If there is whitespace, we grow it.
+        if (whiteSpace) {
+          whiteSpace.currentText = safeWhiteSpace(indent);
+          whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
+        }
+        // Otherwise, we have to add a new whitespace node.
+        else {
+          whiteSpace = this.doc.createElement("SPAN");
+          whiteSpace.className = "part whitespace";
+          whiteSpace.appendChild(this.doc.createTextNode(safeWhiteSpace(indent)));
+          if (start)
+            insertAfter(whiteSpace, start);
+          else
+            insertAtStart(whiteSpace, this.containter);
+        }
+      }
+      return whiteSpace;
+    },
+
+    // Re-highlight the selected part of the document.
+    highlightAtCursor: function() {
+      var pos = select.selectionTopNode(this.container, true);
+      var to = select.selectionTopNode(this.container, false);
+      if (pos === false || !to) return;
+      // Skip one node ahead to make sure the cursor itself is
+      // *inside* a highlighted line.
+      if (to.nextSibling) to = to.nextSibling;
+
+      var sel = select.markSelection(this.win);
+      var toIsText = to.nodeType == 3;
+      if (!toIsText) to.dirty = true;
+
+      // Highlight lines as long as to is in the document and dirty.
+      while (to.parentNode == this.container && (toIsText || to.dirty)) {
+        var result = this.highlight(pos, 1, true);
+        if (result) pos = result.node;
+        if (!result || result.left) break;
+      }
+      select.selectMarked(sel);
+    },
+
+    // When tab is pressed with text selected, the whole selection is
+    // re-indented, when nothing is selected, the line with the cursor
+    // is re-indented.
+    handleTab: function() {
+      var start = select.selectionTopNode(this.container, true),
+        end = select.selectionTopNode(this.container, false);
+      if (start === false || end === false) return;
+
+      if (start == end)
+        this.indentAtCursor();
+      else
+        this.indentRegion(start, end);
+    },
+
+    // Adjust the amount of whitespace at the start of the line that
+    // the cursor is on so that it is indented properly.
+    indentAtCursor: function() {
+      if (!this.container.firstChild) return;
+      // The line has to have up-to-date lexical information, so we
+      // highlight it first.
+      this.highlightAtCursor();
+      var cursor = select.selectionTopNode(this.container, false);
+      // If we couldn't determine the place of the cursor,
+      // there's nothing to indent.
+      if (cursor === false)
+        return;
+      var lineStart = startOfLine(cursor);
+         
+         if (this.options.outerEditor && this.options.outerEditor.autoCloseBracket) {
+           this.options.outerEditor.autoCloseBracket(lineStart.previousSibling);
+         }
+                       
+      var whiteSpace = this.indentLineAfter(lineStart);
+      if (cursor == lineStart && whiteSpace)
+          cursor = whiteSpace;
+      // This means the indentation has probably messed up the cursor.
+      if (cursor == whiteSpace)
+        select.focusAfterNode(cursor, this.container);
+    },
+
+    // Indent all lines whose start falls inside of the current
+    // selection.
+    indentRegion: function(current, end) {
+      var sel = select.markSelection(this.win);
+      if (!current)
+        this.indentLineAfter(current);
+      else
+        current = startOfLine(current.previousSibling);
+      end = startOfLine(end);
+
+      while (true) {
+        var result = this.highlight(current, 1);
+        var next = result ? result.node : null;
+
+        while (current != next)
+          current = current ? current.nextSibling : this.container.firstChild;
+        if (next)
+          this.indentLineAfter(next);
+        if (current == end)
+          break;
+      }
+      select.selectMarked(sel);
+    },
+
+    // Find the node that the cursor is in, mark it as dirty, and make
+    // sure a highlight pass is scheduled.
+    markCursorDirty: function() {
+      var cursor = select.selectionTopNode(this.container, false);
+      if (cursor !== false && this.container.firstChild) {
+        this.scheduleHighlight();
+        this.addDirtyNode(cursor || this.container.firstChild);
+      }
+    },
+
+    reparseBuffer: function() {
+      forEach(this.container.childNodes, function(node) {node.dirty = true;});
+      if (this.container.firstChild)
+        this.addDirtyNode(this.container.firstChild);
+    },
+
+    // Add a node to the set of dirty nodes, if it isn't already in
+    // there.
+    addDirtyNode: function(node) {
+      node = node || this.container.firstChild;
+      if (!node) return;
+
+      for (var i = 0; i < this.dirty.length; i++)
+        if (this.dirty[i] == node) return;
+
+      if (node.nodeType != 3)
+        node.dirty = true;
+      this.dirty.push(node);
+    },
+
+    // Cause a highlight pass to happen in options.passDelay
+    // milliseconds. Clear the existing timeout, if one exists. This
+    // way, the passes do not happen while the user is typing, and
+    // should as unobtrusive as possible.
+    scheduleHighlight: function() {
+      // Timeouts are routed through the parent window, because on
+      // some browsers designMode windows do not fire timeouts.
+      this.parent.clearTimeout(this.highlightTimeout);
+      this.highlightTimeout = this.parent.setTimeout(method(this, "highlightDirty"), this.options.passDelay);
+    },
+
+    // Fetch one dirty node, and remove it from the dirty set.
+    getDirtyNode: function() {
+      while (this.dirty.length > 0) {
+        var found = this.dirty.pop();
+        // If the node has been coloured in the meantime, or is no
+        // longer in the document, it should not be returned.
+        if ((found.dirty || found.nodeType == 3) && found.parentNode)
+          return found;
+      }
+      return null;
+    },
+
+    // Pick dirty nodes, and highlight them, until
+    // options.linesPerPass lines have been highlighted. The highlight
+    // method will continue to next lines as long as it finds dirty
+    // nodes. It returns an object indicating the amount of lines
+    // left, and information about the place where it stopped. If
+    // there are dirty nodes left after this function has spent all
+    // its lines, it shedules another highlight to finish the job.
+    highlightDirty: function(all) {
+      var lines = all ? Infinity : this.options.linesPerPass;
+      var sel = select.markSelection(this.win);
+      var start;
+      while (lines > 0 && (start = this.getDirtyNode())){
+        var result = this.highlight(start, lines);
+        if (result) {
+          lines = result.left;
+          if (result.node && result.dirty)
+            this.addDirtyNode(result.node);
+        }
+      }
+      select.selectMarked(sel);
+      if (start)
+        this.scheduleHighlight();
+    },
+
+    // Creates a function that, when called through a timeout, will
+    // continuously re-parse the document.
+    documentScanner: function(linesPer) {
+      var self = this, pos = null;
+      return function() {
+        // If the current node is no longer in the document... oh
+        // well, we start over.
+        if (pos && pos.parentNode != self.container)
+          pos = null;
+        var sel = select.markSelection(self.win);
+        var result = self.highlight(pos, linesPer, true);
+        select.selectMarked(sel);
+        pos = result ? result.node : null;
+        self.delayScanning();
+      }
+    },
+
+    // Starts the continuous scanning process for this document after
+    // a given interval.
+    delayScanning: function() {
+      if (this.scanner) {
+        this.parent.clearTimeout(this.documentScan);
+        this.documentScan = this.parent.setTimeout(this.scanner, this.options.continuousScanning);
+      }
+    },
+
+    // The function that does the actual highlighting/colouring (with
+    // help from the parser and the DOM normalizer). Its interface is
+    // rather overcomplicated, because it is used in different
+    // situations: ensuring that a certain line is highlighted, or
+    // highlighting up to X lines starting from a certain point. The
+    // 'from' argument gives the node at which it should start. If
+    // this is null, it will start at the beginning of the frame. When
+    // a number of lines is given with the 'lines' argument, it will
+    // colour no more than that amount. If at any time it comes across
+    // a 'clean' line (no dirty nodes), it will stop, except when
+    // 'cleanLines' is true.
+    highlight: function(from, lines, cleanLines){
+      var container = this.container, self = this;
+
+      if (!container.firstChild)
+        return;
+      // Backtrack to the first node before from that has a partial
+      // parse stored.
+      while (from && (!from.parserFromHere || from.dirty))
+        from = from.previousSibling;
+      // If we are at the end of the document, do nothing.
+      if (from && !from.nextSibling)
+        return;
+
+      // Check whether a part (<span> node) and the corresponding token
+      // match.
+      function correctPart(token, part){
+        return !part.reduced && part.currentText == token.value && hasClass(part, token.style);
+      }
+      // Shorten the text associated with a part by chopping off
+      // characters from the front. Note that only the currentText
+      // property gets changed. For efficiency reasons, we leave the
+      // nodeValue alone -- we set the reduced flag to indicate that
+      // this part must be replaced.
+      function shortenPart(part, minus){
+        part.currentText = part.currentText.substring(minus);
+        part.reduced = true;
+      }
+      // Create a part corresponding to a given token.
+      function tokenPart(token){
+        var part = self.doc.createElement("SPAN");
+        part.className = "part " + token.style;
+        part.appendChild(self.doc.createTextNode(token.value));
+        part.currentText = token.value;
+        return part;
+      }
+
+      // Get the token stream. If from is null, we start with a new
+      // parser from the start of the frame, otherwise a partial parse
+      // is resumed.
+      var traversal = traverseDOM(from ? from.nextSibling : container.firstChild),
+          stream = multiStringStream(traversal),
+          parsed = from ? from.parserFromHere(stream) : Editor.Parser.make(stream);
+
+      // parts is an interface to make it possible to 'delay' fetching
+      // the next DOM node until we are completely done with the one
+      // before it. This is necessary because often the next node is
+      // not yet available when we want to proceed past the current
+      // one.
+      var parts = {
+        current: null,
+        // Fetch current node.
+        get: function(){
+          if (!this.current)
+            this.current = traversal.nodes.shift();
+          return this.current;
+        },
+        // Advance to the next part (do not fetch it yet).
+        next: function(){
+          this.current = null;
+        },
+        // Remove the current part from the DOM tree, and move to the
+        // next.
+        remove: function(){
+          container.removeChild(this.get());
+          this.current = null;
+        },
+        // Advance to the next part that is not empty, discarding empty
+        // parts.
+        getNonEmpty: function(){
+          var part = this.get();
+          while (part.nodeName == "SPAN" && part.currentText == ""){
+            var old = part;
+            this.remove();
+            part = this.get();
+            // Adjust selection information, if any. See select.js for
+            // details.
+            select.replaceSelection(old.firstChild, part.firstChild || part, 0, 0);
+          }
+          return part;
+        }
+      };
+
+      var lineDirty = false, lineHasNodes = false;;
+      this.history.touch(from);
+
+      // This forEach loops over the tokens from the parsed stream, and
+      // at the same time uses the parts object to proceed through the
+      // corresponding DOM nodes.
+      forEach(parsed, function(token){
+        var part = parts.getNonEmpty();
+
+        if (token.value == "\n"){
+          // The idea of the two streams actually staying synchronized
+          // is such a long shot that we explicitly check.
+          if (part.nodeName != "BR")
+            throw "Parser out of sync. Expected BR.";
+
+          if (part.dirty || !part.indentation)
+            lineDirty = true;
+          self.history.touch(part);
+
+          // Every <br> gets a copy of the parser state and a lexical
+          // context assigned to it. The first is used to be able to
+          // later resume parsing from this point, the second is used
+          // for indentation.
+          part.parserFromHere = parsed.copy();
+          part.indentation = token.indentation;
+          part.dirty = false;
+          // A clean line means we are done. Throwing a StopIteration is
+          // the way to break out of a MochiKit forEach loop.
+          if ((lines !== undefined && --lines <= 0) || (!lineDirty && lineHasNodes && !cleanLines))
+            throw StopIteration;
+          lineDirty = false; lineHasNodes = false;
+          parts.next();
+        }
+        else {
+          if (part.nodeName != "SPAN")
+            throw "Parser out of sync. Expected SPAN.";
+          if (part.dirty)
+            lineDirty = true;
+          lineHasNodes = true;
+
+          // If the part matches the token, we can leave it alone.
+          if (correctPart(token, part)){
+            part.dirty = false;
+            parts.next();
+          }
+          // Otherwise, we have to fix it.
+          else {
+            lineDirty = true;
+            // Insert the correct part.
+            var newPart = tokenPart(token);
+            container.insertBefore(newPart, part);
+            var tokensize = token.value.length;
+            var offset = 0;
+            // Eat up parts until the text for this token has been
+            // removed, adjusting the stored selection info (see
+            // select.js) in the process.
+            while (tokensize > 0) {
+              part = parts.get();
+              var partsize = part.currentText.length;
+              select.replaceSelection(part.firstChild, newPart.firstChild, tokensize, offset);
+              if (partsize > tokensize){
+                shortenPart(part, tokensize);
+                tokensize = 0;
+              }
+              else {
+                tokensize -= partsize;
+                offset += partsize;
+                parts.remove();
+              }
+            }
+          }
+        }
+      });
+
+      // The function returns some status information that is used by
+      // hightlightDirty to determine whether and where it has to
+      // continue.
+      return {left: lines,
+              node: parts.get(),
+              dirty: lineDirty};
+    }
+  };
+
+  return Editor;
+})();
+
+addEventHandler(window, "load", function() {
+  var CodeMirror = window.frameElement.CodeMirror;
+  CodeMirror.editor = new Editor(CodeMirror.options);
+  if (CodeMirror.options.initCallback) {
+    this.parent.setTimeout(function(){
+      CodeMirror.options.initCallback(CodeMirror);
+    }, 0);
+  }
+});
diff --git a/typo3/sysext/t3editor/jslib/codemirror/parsejavascript.js b/typo3/sysext/t3editor/jslib/codemirror/parsejavascript.js
new file mode 100644 (file)
index 0000000..8544cd9
--- /dev/null
@@ -0,0 +1,323 @@
+/* Parse function for JavaScript. Makes use of the tokenizer from
+ * tokenizejavascript.js. Note that your parsers do not have to be
+ * this complicated -- if you don't want to recognize local variables,
+ * in many languages it is enough to just look for braces, semicolons,
+ * parentheses, etc, and know when you are inside a string or comment.
+ *
+ * See manual.html for more info about the parser interface.
+ */
+
+Editor.Parser = (function() {
+  // Token types that can be considered to be atoms.
+  var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
+  // Constructor for the lexical context objects.
+  function JSLexical(indented, column, type, align, prev) {
+    // indentation at start of this line
+    this.indented = indented;
+    // column at which this scope was opened
+    this.column = column;
+    // type of scope ('vardef', 'stat' (statement), 'form' (special form), '[', '{', or '(')
+    this.type = type;
+    // '[', '{', or '(' blocks that have any text after their opening
+    // character are said to be 'aligned' -- any lines below are
+    // indented all the way to the opening character.
+    if (align != null)
+      this.align = align;
+    // Parent scope, if any.
+    this.prev = prev;
+  }
+  // My favourite JavaScript indentation rules.
+  function indentJS(lexical) {
+    return function(firstChars) {
+      var firstChar = firstChars && firstChars.charAt(0);
+      var closing = firstChar == lexical.type;
+      if (lexical.type == "vardef")
+        return lexical.indented + 4;
+      else if (lexical.type == "form" && firstChar == "{")
+        return lexical.indented;
+      else if (lexical.type == "stat" || lexical.type == "form")
+        return lexical.indented + 2;
+      else if (lexical.align)
+        return lexical.column - (closing ? 1 : 0);
+      else
+        return lexical.indented + (closing ? 0 : 2);
+    };
+  }
+
+  // The parser-iterator-producing function itself.
+  function parseJS(input) {
+    // Wrap the input in a token stream
+    var tokens = tokenizeJavaScript(input);
+    // The parser state. cc is a stack of actions that have to be
+    // performed to finish the current statement. For example we might
+    // know that we still need to find a closing parenthesis and a
+    // semicolon. Actions at the end of the stack go first. It is
+    // initialized with an infinitely looping action that consumes
+    // whole statements.
+    var cc = [statements];
+    // Context contains information about the current local scope, the
+    // variables defined in that, and the scopes above it.
+    var context = null;
+    // The lexical scope, used mostly for indentation.
+    var lexical = new JSLexical(-2, 0, "block", false);
+    // Current column, and the indentation at the start of the current
+    // line. Used to create lexical scope objects.
+    var column = 0;
+    var indented = 0;
+    // Variables which are used by the mark, cont, and pass functions
+    // below to communicate with the driver loop in the 'next'
+    // function.
+    var consume, marked;
+  
+    // The iterator object.
+    var parser = {next: next, copy: copy};
+
+    function next(){
+      // Start by performing any 'lexical' actions (adjusting the
+      // lexical variable), or the operations below will be working
+      // with the wrong lexical state.
+      while(cc[cc.length - 1].lex)
+        cc.pop()();
+
+      // Fetch a token.
+      var token = tokens.next();
+      // Adjust column and indented.
+      if (token.type == "whitespace" && column == 0)
+        indented = token.value.length;
+      column += token.value.length;
+      if (token.type == "newline"){
+        indented = column = 0;
+        // If the lexical scope's align property is still undefined at
+        // the end of the line, it is an un-aligned scope.
+        if (!("align" in lexical))
+          lexical.align = false;
+        // Newline tokens get a lexical context associated with them,
+        // which is used for indentation.
+        token.indentation = indentJS(lexical);
+      }
+      // No more processing for meaningless tokens.
+      if (token.type == "whitespace" || token.type == "newline" || token.type == "comment")
+        return token;
+      // When a meaningful token is found and the lexical scope's
+      // align is undefined, it is an aligned scope.
+      if (!("align" in lexical))
+        lexical.align = true;
+
+      // Execute actions until one 'consumes' the token and we can
+      // return it. Marked is used to 
+      while(true){
+        consume = marked = false;
+        // Take and execute the topmost action.
+        cc.pop()(token.type, token.name);
+        if (consume){
+          // Marked is used to change the style of the current token.
+          if (marked)
+            token.style = marked;
+          // Here we differentiate between local and global variables.
+          else if (token.type == "variable" && inScope(token.name))
+            token.style = "localvariable";
+          return token;
+        }
+      }
+    }
+
+    // This makes a copy of the parser state. It stores all the
+    // stateful variables in a closure, and returns a function that
+    // will restore them when called with a new input stream. Note
+    // that the cc array has to be copied, because it is contantly
+    // being modified. Lexical objects are not mutated, and context
+    // objects are not mutated in a harmful way, so they can be shared
+    // between runs of the parser.
+    function copy(){
+      var _context = context, _lexical = lexical, _cc = cc.concat([]), _regexp = tokens.regexp, _comment = tokens.inComment;
+  
+      return function(input){
+        context = _context;
+        lexical = _lexical;
+        cc = _cc.concat([]); // copies the array
+        column = indented = 0;
+        tokens = tokenizeJavaScript(input);
+        tokens.regexp = _regexp;
+        tokens.inComment = _comment;
+        return parser;
+      };
+    }
+
+    // Helper function for pushing a number of actions onto the cc
+    // stack in reverse order.
+    function push(fs){
+      for (var i = fs.length - 1; i >= 0; i--)
+        cc.push(fs[i]);
+    }
+    // cont and pass are used by the action functions to add other
+    // actions to the stack. cont will cause the current token to be
+    // consumed, pass will leave it for the next action.
+    function cont(){
+      push(arguments);
+      consume = true;
+    }
+    function pass(){
+      push(arguments);
+      consume = false;
+    }
+    // Used to change the style of the current token.
+    function mark(style){
+      marked = style;
+    }
+
+    // Push a new scope. Will automatically link the the current
+    // scope.
+    function pushcontext(){
+      context = {prev: context, vars: {"this": true, "arguments": true}};
+    }
+    // Pop off the current scope.
+    function popcontext(){
+      context = context.prev;
+    }
+    // Register a variable in the current scope.
+    function register(varname){
+      if (context){
+        mark("variabledef");
+        context.vars[varname] = true;
+      }
+    }
+    // Check whether a variable is defined in the current scope.
+    function inScope(varname){
+      var cursor = context;
+      while (cursor) {
+        if (cursor.vars[varname])
+          return true;
+        cursor = cursor.prev;
+      }
+      return false;
+    }
+  
+    // Push a new lexical context of the given type.
+    function pushlex(type){
+      var result = function(){
+        lexical = new JSLexical(indented, column, type, null, lexical)
+      };
+      result.lex = true;
+      return result;
+    }
+    // Pop off the current lexical context.
+    function poplex(){
+      lexical = lexical.prev;
+    }
+    poplex.lex = true;
+    // The 'lex' flag on these actions is used by the 'next' function
+    // to know they can (and have to) be ran before moving on to the
+    // next token.
+  
+    // Creates an action that discards tokens until it finds one of
+    // the given type.
+    function expect(wanted){
+      return function(type){
+        if (type == wanted) cont();
+        else cont(arguments.callee);
+      };
+    }
+
+    // Looks for a statement, and then calls itself.
+    function statements(type){
+      return pass(statement, statements);
+    }
+    // Dispatches various types of statements based on the type of the
+    // current token.
+    function statement(type){
+      if (type == "var") cont(pushlex("vardef"), vardef1, expect(";"), poplex);
+      else if (type == "keyword a") cont(pushlex("form"), expression, statement, poplex);
+      else if (type == "keyword b") cont(pushlex("form"), statement, poplex);
+      else if (type == "{") cont(pushlex("}"), block, poplex);
+      else if (type == "function") cont(functiondef);
+      else if (type == "for") cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), poplex, statement, poplex);
+      else if (type == "variable") cont(pushlex("stat"), maybelabel);
+      else if (type == "case") cont(expression, expect(":"));
+      else if (type == "catch") cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext);
+      else pass(pushlex("stat"), expression, expect(";"), poplex);
+    }
+    // Dispatch expression types.
+    function expression(type){
+      if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator);
+      else if (type == "function") cont(functiondef);
+      else if (type == "keyword c") cont(expression);
+      else if (type == "(") cont(pushlex(")"), expression, expect(")"), poplex);
+      else if (type == "operator") cont(expression);
+      else if (type == "[") cont(pushlex("]"), commasep(expression), expect("]"), poplex);
+      else if (type == "{") cont(pushlex("}"), commasep(objprop), expect("}"), poplex);
+    }
+    // Called for places where operators, function calls, or
+    // subscripts are valid. Will skip on to the next action if none
+    // is found.
+    function maybeoperator(type){
+      if (type == "operator") cont(expression);
+      else if (type == "(") cont(pushlex(")"), expression, commasep(expression), expect(")"), poplex);
+      else if (type == ".") cont(property, maybeoperator);
+      else if (type == "[") cont(pushlex("]"), expression, expect("]"), poplex);
+    }
+    // When a statement starts with a variable name, it might be a
+    // label. If no colon follows, it's a regular statement.
+    function maybelabel(type){
+      if (type == ":") cont(poplex, statement);
+      else pass(maybeoperator, expect(";"), poplex);
+    }
+    // Property names need to have their style adjusted -- the
+    // tokenizer think they are variables.
+    function property(type){
+      if (type == "variable") {mark("property"); cont();}
+    }
+    // This parses a property and its value in an object literal.
+    function objprop(type){
+      if (type == "variable") mark("property");
+      if (atomicTypes.hasOwnProperty(type)) cont(expect(":"), expression);
+    }
+    // Parses a comma-separated list of the things that are recognized
+    // by the 'what' argument.
+    function commasep(what){
+      function proceed(type) {
+        if (type == ",") cont(what, proceed);
+      };
+      return function() {
+        pass(what, proceed);
+      };
+    }
+    // Look for statements until a closing brace is found.
+    function block(type){
+      if (type == "}") cont();
+      else pass(statement, block);
+    }
+    // Variable definitions are split into two actions -- 1 looks for
+    // a name or the end of the definition, 2 looks for an '=' sign or
+    // a comma.
+    function vardef1(type, value){
+      if (type == "variable"){register(value); cont(vardef2);}
+      else cont();
+    }
+    function vardef2(type){
+      if (type == "operator") cont(expression, vardef2);
+      else if (type == ",") cont(vardef1);
+    }
+    // For loops.
+    function forspec1(type, value){
+      if (type == "var") cont(vardef1, forspec2);
+      else cont(expression, forspec2);
+    }
+    function forspec2(type){
+      if (type == ",") cont(forspec1);
+      if (type == ";") cont(expression, expect(";"), expression);
+    }
+    // A function definition creates a new context, and the variables
+    // in its argument list have to be added to this context.
+    function functiondef(type, value){
+      if (type == "variable"){register(value); cont(functiondef);}
+      else if (type == "(") cont(pushcontext, commasep(funarg), expect(")"), statement, popcontext);
+    }
+    function funarg(type, value){
+      if (type == "variable"){register(value); cont();}
+    }
+  
+    return parser;
+  }
+
+  return {make: parseJS, electricChars: "{}"};
+})();
diff --git a/typo3/sysext/t3editor/jslib/codemirror/parsetyposcript.js b/typo3/sysext/t3editor/jslib/codemirror/parsetyposcript.js
new file mode 100644 (file)
index 0000000..80f9d4e
--- /dev/null
@@ -0,0 +1,457 @@
+/* TypoScript parser
+ *
+ * based on parsejavascript.js by Marijn Haverbeke
+ *
+ * A parser that can be plugged into the CodeMirror system has to
+ * implement the following interface: It is a function that, when
+ * called with a string stream (stringstream.js) as an argument,
+ * returns a MochiKit-style iterator (object with a 'next' method).
+ * This iterator, when called, consumes some input from the string
+ * stream, and returns a token object. Token objects must have a
+ * 'value' property (the text they represent), a 'style' property (the
+ * CSS style that should be used to colour them). Tokens for newline
+ * characters must also have a 'lexicalContext' property, which has an
+ * 'indentation' method that can be used to determine the proper
+ * indentation level for the next line. This method optionally takes
+ * the first character of the next line as an argument, which it can
+ * use to adjust the indentation level.
+ *
+ * So far this should be easy. The hard part is that the iterator
+ * produced by the parse function must also have a 'copy' method. This
+ * method, called without arguments, returns a function representing
+ * the current state of the parser. When this function is later called
+ * with a string stream as its argument, it returns a parser iterator
+ * object that resumes parsing using the old state and the new input
+ * stream. It may assume that only one parser is active at a time, and
+ * clobber the state of the old parser (the implementation below
+ * certianly does).
+ */
+
+// Parse function for TypoScript. Makes use of the tokenizer from
+// tokenizetyposcript.js. Note that your parsers do not have to be
+// this complicated -- if you don't want to recognize local variables,
+// in many languages it is enough to just look for braces, semicolons,
+// parentheses, etc, and know when you are inside a string or comment.
+Editor.Parser = (function() {
+       // Token types that can be considered to be atoms.
+       var atomicTypes = {
+               "atom": true,
+               "number": true,
+               "variable": true,
+               "string": true,
+               "regexp": true
+       };
+
+       // Constructor for the lexical context objects.
+       function TSLexical(indented, column, type, align, prev) {
+               // indentation at start of this line
+               this.indented = indented;
+               // column at which this scope was opened
+               this.column = column;
+               // type of scope ('vardef', 'stat' (statement), '[', '{', or '(')
+               this.type = type;
+               // '[', '{', or '(' blocks that have any text after their opening
+               // character are said to be 'aligned' -- any lines below are
+               // indented all the way to the opening character.
+               if (align != null) {
+                       this.align = align;
+               }
+               // Parent scope, if any.
+               this.prev = prev;
+
+       }
+       
+       
+       // My favourite TypoScript indentation rules.
+       function indentTS(lexical) {
+               return function(firstChars) {
+                       var firstChar = firstChars && firstChars.charAt(0);
+                       var closing = firstChar == lexical.type;
+
+                       if (lexical.type == "{" && firstChar != "}") {
+                               return lexical.indented + 2;
+                       }
+
+                       if (firstChar == "}" && lexical.prev) {
+                               lexical = lexical.prev;
+                       }
+                       
+                       if (lexical.align) {
+                               return lexical.column - (closing ? 1: 0);
+                       } else {
+                               return lexical.indented + (closing ? 0: 2);
+                       }
+
+               };
+       }
+
+       // The parser-iterator-producing function itself.
+       function parseTS(input) {
+               // Wrap the input in a token stream
+               var tokens = tokenizeTypoScript(input);
+               // The parser state. cc is a stack of actions that have to be
+               // performed to finish the current statement. For example we might
+               // know that we still need to find a closing parenthesis and a
+               // semicolon. Actions at the end of the stack go first. It is
+               // initialized with an infinitely looping action that consumes
+               // whole statements.
+               var cc = [statements];
+               // Context contains information about the current local scope, the
+               // variables defined in that, and the scopes above it.
+               var context = null;
+               // The lexical scope, used mostly for indentation.
+               var lexical = new TSLexical( -2, 0, "block", false);
+               // Current column, and the indentation at the start of the current
+               // line. Used to create lexical scope objects.
+               var column = 0;
+               var indented = 0;
+               // Variables which are used by the mark, cont, and pass functions
+               // below to communicate with the driver loop in the 'next'
+               // function.
+               var consume,
+               marked;
+
+               // The iterator object.
+               var parser = {
+                       next: next,
+                       copy: copy
+               };
+
+               function next() {
+                       // Start by performing any 'lexical' actions (adjusting the
+                       // lexical variable), or the operations below will be working
+                       // with the wrong lexical state.
+                       while (cc[cc.length - 1].lex) {
+                               cc.pop()();
+                       }
+
+                       // Fetch a token.
+                       var token = tokens.next();
+                       // Adjust column and indented.
+                       if (token.type == "whitespace" && column == 0) {
+                               indented = token.value.length;
+                       }
+                       column += token.value.length;
+                       if (token.type == "newline") {
+                               indented = column = 0;
+                               // If the lexical scope's align property is still undefined at
+                               // the end of the line, it is an un-aligned scope.
+                               if (! ("align" in lexical)) {
+                                       lexical.align = false;
+                               }
+                       // Newline tokens get a lexical context associated with them,
+                               // which is used for indentation.
+                               token.indentation = indentTS(lexical);
+                       }
+                       // No more processing for meaningless tokens.
+                       if (token.type == "whitespace" || token.type == "newline" || token.type == "comment") {
+                               return token;
+                       }
+                       // When a meaningful token is found and the lexical scope's
+                       // align is undefined, it is an aligned scope.
+                       if (! ("align" in lexical)) {
+                               lexical.align = true;
+                       }
+                       // Execute actions until one 'consumes' the token and we can
+                       // return it. Marked is used to
+                       while (true) {
+                               consume = marked = false;
+                               // Take and execute the topmost action.
+                               cc.pop()(token.type, token.name);
+                               if (consume) {
+                                       // Marked is used to change the style of the current token.
+                                       if (marked) {
+                                               token.style = marked;
+                                       }
+                                       return token;
+                               }
+                       }
+               }
+
+               // This makes a copy of the parser state. It stores all the
+               // stateful variables in a closure, and returns a function that
+               // will restore them when called with a new input stream. Note
+               // that the cc array has to be copied, because it is contantly
+               // being modified. Lexical objects are not mutated, and context
+               // objects are not mutated in a harmful way, so they can be shared
+               // between runs of the parser.
+               function copy() {
+                       var _context = context,
+                       _lexical = lexical,
+                       _cc = cc.concat([]),
+                       _regexp = tokens.regexp,
+                       _comment = tokens.inComment;
+
+                       return function(input) {
+                               context = _context;
+                               lexical = _lexical;
+                               cc = _cc.concat([]);
+                               // copies the array
+                               column = indented = 0;
+                               tokens = tokenizeTypoScript(input);
+                               tokens.regexp = _regexp;
+                               tokens.inComment = _comment;
+                               return parser;
+                       };
+               }
+
+               // Helper function for pushing a number of actions onto the cc
+               // stack in reverse order.
+               function push(fs) {
+                       for (var i = fs.length - 1; i >= 0; i--) {
+                               cc.push(fs[i]);
+                       }
+               }
+
+               // cont and pass are used by the action functions to add other
+               // actions to the stack. cont will cause the current token to be
+               // consumed, pass will leave it for the next action.
+               function cont() {
+                       push(arguments);
+                       consume = true;
+               }
+
+               function pass() {
+                       push(arguments);
+                       consume = false;
+               }
+
+               // Used to change the style of the current token.
+               function mark(style) {
+                       marked = style;
+               }
+
+               // Push a new scope. Will automatically link the the current
+               // scope.
+               function pushcontext() {
+                       context = {
+                               prev: context,
+                               vars: {
+                                       "this": true,
+                                       "arguments": true
+                               }
+                       };
+               }
+
+               // Pop off the current scope.
+               function popcontext() {
+                       context = context.prev;
+               }
+
+               // Register a variable in the current scope.
+               function register(varname) {
+                       if (context) {
+                               mark("variabledef");
+                               context.vars[varname] = true;
+                       }
+               }
+
+               // Push a new lexical context of the given type.
+               function pushlex(type) {
+                       var result = function() {
+                               lexical = new TSLexical(indented, column, type, null, lexical)
+                       };
+                       result.lex = true;
+                       return result;
+               }
+
+               // Pop off the current lexical context.
+               function poplex() {
+                       lexical = lexical.prev;
+               }
+
+               poplex.lex = true;
+               // The 'lex' flag on these actions is used by the 'next' function
+               // to know they can (and have to) be ran before moving on to the
+               // next token.
+
+               // Creates an action that discards tokens until it finds one of
+               // the given type.
+               function expect(wanted) {
+                       return function(type) {
+                               if (type == wanted) {
+                                       cont();
+                               } else {
+                                       cont(arguments.callee);
+                               }
+                       };
+               }
+
+               // Looks for a statement, and then calls itself.
+               function statements(type) {
+                       return pass(statement, statements);
+               }
+               // Dispatches various types of statements based on the type of the
+               // current token.
+               function statement(type) {
+                       if (type == "{") {
+                               cont(pushlex("{"), block, poplex);
+                       } else {
+                               cont();
+                       }
+               }
+
+               // Dispatch expression types.
+               function expression(type) {
+                       if (atomicTypes.hasOwnProperty(type)) {
+                               cont(maybeoperator);
+
+                       } else if (type == "function") {
+                               cont(functiondef);
+
+                       } else if (type == "keyword c") {
+                               cont(expression);
+
+                       } else if (type == "(") {
+                               cont(pushlex(")"), expression, expect(")"), poplex);
+
+                       } else if (type == "operator") {
+                               cont(expression);
+
+                       } else if (type == "[") {
+                               cont(pushlex("]"), commasep(expression), expect("]"), poplex);
+
+                       } else if (type == "{") {
+                               cont(pushlex("}"), commasep(objprop), expect("}"), poplex);
+                       }
+               }
+
+               // Called for places where operators, function calls, or
+               // subscripts are valid. Will skip on to the next action if none
+               // is found.
+               function maybeoperator(type) {
+                       if (type == "operator") {
+                               cont(expression);
+
+                       } else if (type == "(") {
+                               cont(pushlex(")"), expression, commasep(expression), expect(")"), poplex);
+
+                       } else if (type == ".") {
+                               cont(property, maybeoperator);
+
+                       } else if (type == "[") {
+                               cont(pushlex("]"), expression, expect("]"), poplex);
+                       }
+               }
+
+               // When a statement starts with a variable name, it might be a
+               // label. If no colon follows, it's a regular statement.
+               function maybelabel(type) {
+                       if (type == ":") {
+                               cont(poplex, statement);
+                       } else {
+                               pass(maybeoperator, expect(";"), poplex);
+                       }
+               }
+
+               // Property names need to have their style adjusted -- the
+               // tokenizer think they are variables.
+               function property(type) {
+                       if (type == "variable") {
+                               mark("property");
+                               cont();
+                       }
+               }
+
+               // This parses a property and its value in an object literal.
+               function objprop(type) {
+                       if (type == "variable") {
+                               mark("property");
+                       }
+                       if (atomicTypes.hasOwnProperty(type)) {
+                               cont(expect(":"), expression);
+                       }
+               }
+
+               // Parses a comma-separated list of the things that are recognized
+               // by the 'what' argument.
+               function commasep(what) {
+                       function proceed(type) {
+                               if (type == ",") {
+                                       cont(what, proceed);
+                               }
+                       };
+                       return function() {
+                               pass(what, proceed);
+                       };
+               }
+
+               // Look for statements until a closing brace is found.
+               function block(type) {
+                       if (type == "}") {
+                               cont();
+                       } else {
+                               pass(statement, block);
+                       }
+               }
+
+               // Look for statements until a closing brace is found.
+               function condition(type) {
+                       if (type == "]") {
+                               cont();
+                       } else {
+                               pass(statement, block);
+                       }
+               }
+
+               // Variable definitions are split into two actions -- 1 looks for
+               // a name or the end of the definition, 2 looks for an '=' sign or
+               // a comma.
+               function vardef1(type, value) {
+                       if (type == "variable") {
+                               register(value);
+                               cont(vardef2);
+                       } else {
+                               cont();
+                       }
+               }
+
+               function vardef2(type) {
+                       if (type == "operator") {
+                               cont(expression, vardef2);
+                       } else if (type == ",") {
+                               cont(vardef1);
+                       }
+               }
+
+               // For loops.
+               function forspec1(type, value) {
+                       if (type == "var") {
+                               cont(vardef1, forspec2);
+                       } else {
+                               cont(expression, forspec2);
+                       }
+               }
+
+               function forspec2(type) {
+                       if (type == ",") {
+                               cont(forspec1);
+                       }
+                       if (type == ";") {
+                               cont(expression, expect(";"), expression);
+                       }
+               }
+
+               // A function definition creates a new context, and the variables
+               // in its argument list have to be added to this context.
+               function functiondef(type, value) {
+                       if (type == "variable") {
+                               register(value);
+                               cont(functiondef);
+                       } else if (type == "(") {
+                               cont(pushcontext, commasep(funarg), expect(")"), statement, popcontext);
+                       }
+               }
+
+               function funarg(type, value) {
+                       if (type == "variable") {
+                               register(value);
+                               cont();
+                       }
+               }
+
+               return parser;
+       }
+       
+       return {make: parseTS, electricChars: "{}"};
+})();
\ No newline at end of file
diff --git a/typo3/sysext/t3editor/jslib/codemirror/parsexml.js b/typo3/sysext/t3editor/jslib/codemirror/parsexml.js
new file mode 100644 (file)
index 0000000..e9bcf8a
--- /dev/null
@@ -0,0 +1,310 @@
+/* This file defines an XML parser, with a few kludges to make it
+ * useable for HTML. autoSelfClosers defines a set of tag names that
+ * are expected to not have a closing tag, and doNotIndent specifies
+ * the tags inside of which no indentation should happen (see Config
+ * object). These can be disabled by passing the editor an object like
+ * {useHTMLKludges: false} as parserConfig option.
+ */
+
+Editor.Parser = (function() {
+  var Kludges = {
+    autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
+                      "meta": true, "col": true, "frame": true, "base": true, "area": true},
+    doNotIndent: {"pre": true}
+  };
+  var NoKludges = {autoSelfClosers: {}, doNotIndent: {}};
+  var UseKludges = Kludges;
+
+  // Simple stateful tokenizer for XML documents. Returns a
+  // MochiKit-style iterator, with a state property that contains a
+  // function encapsulating the current state.
+  function tokenizeXML(source, startState) {
+    function isWhiteSpace(ch) {
+      return ch != "\n" && realWhiteSpace.test(ch);
+    }
+    // The following functions are all state functions -- they 'consume'
+    // and label the next token based on the current parser state.
+    function inText() {
+      var ch = this.source.next();
+      if (ch == "<") {
+        if (this.source.equals("!")) {
+          this.source.next();
+          if (this.source.equals("[")) {
+            this.source.next();
+            this.state = inBlock("cdata", "]]>");
+            return this.state();
+          }
+          else if (this.source.equals("-")) {
+            this.source.next();
+            this.state = inBlock("comment", "-->");
+            return this.state();
+          }
+          else {
+            return "text";
+          }
+        }
+        else {
+          if (this.source.applies(matcher(/[?\/]/))) this.source.next();
+          this.state = inTag;
+          return "punctuation";
+        }
+      }
+      else if (ch == "&") {
+        while (this.source.notEquals("\n")) {
+          if (this.source.next() == ";")
+            break;
+        }
+        return "entity";
+      }
+      else if (isWhiteSpace(ch)) {
+        this.source.nextWhile(isWhiteSpace);
+        return "whitespace";
+      }
+      else {
+        this.source.nextWhile(matcher(/[^&<\n]/));
+        return "text";
+      }
+    }
+    function inTag() {
+      var ch = this.source.next();
+      if (ch == ">") {
+        this.state = inText;
+        return "punctuation";
+      }
+      else if (/[?\/]/.test(ch) && this.source.equals(">")) {
+        this.source.next();
+        this.state = inText;
+        return "punctuation";
+      }
+      else if (ch == "=") {
+        return "punctuation";
+      }
+      else if (/[\'\"]/.test(ch)) {
+        this.state = inAttribute(ch);
+        return this.state();
+      }
+      else if (isWhiteSpace(ch)) {
+        this.source.nextWhile(isWhiteSpace);
+        return "whitespace";
+      }
+      else {
+        this.source.nextWhile(matcher(/[^\s\u00a0=<>\"\'\/?]/));
+        return "name";
+      }
+    }
+    function inAttribute(quote) {
+      return function() {
+        while (this.source.notEquals("\n")) {
+          if (this.source.next() == quote) {
+            this.state = inTag;
+            break;
+          }
+        }
+        return "attribute";
+      };
+    }
+    function inBlock(style, terminator) {
+      return function() {
+        var rest = terminator;
+        while (this.source.more() && this.source.notEquals("\n")) {
+          var ch = this.source.next();
+          if (ch == rest.charAt(0)) {
+            rest = rest.slice(1);
+            if (rest.length == 0) {
+              this.state = inText;
+              break;
+            }
+          }
+          else {
+            rest = terminator;
+          }
+        }
+        return style;
+      };
+    }
+
+    return {
+      state: startState || inText,
+      source: source,
+      
+      newLine: function() {
+        this.source.next();
+        return "whitespace";
+      },
+
+      next: function(){
+        if (!this.source.more()) throw StopIteration;
+        
+        var token = {
+          style: (this.source.equals("\n") ? this.newLine() : this.state()),
+          content: this.source.get()
+        };
+        if (token.content != "\n") // newlines must stand alone
+          this.source.nextWhile(isWhiteSpace);
+        token.value = token.content + this.source.get();
+        return token;
+      }
+    };
+  }
+
+  // The parser. The structure of this function largely follows that of
+  // parseJavaScript in parsejavascript.js (there is actually a bit more
+  // shared code than I'd like), but it is quite a bit simpler.
+  var parseXML = function(source) {
+    var tokens = tokenizeXML(source);
+    var cc = [base];
+    var tokenNr = 0, indented = 0;
+    var currentTag = null, context = null;
+    var consume, marked;
+    
+    function push(fs) {
+      for (var i = fs.length - 1; i >= 0; i--)
+        cc.push(fs[i]);
+    }
+    function cont() {
+      push(arguments);
+      consume = true;
+    }
+    function pass() {
+      push(arguments);
+      consume = false;
+    }
+
+    function mark(style) {
+      marked = style;
+    }
+    function expect(text) {
+      return function(style, content) {
+        if (content == text) cont();
+        else mark("error") || cont(arguments.callee);
+      };
+    }
+
+    function pushContext(tagname, startOfLine) {
+      var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent);
+      context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent};
+    }
+    function popContext() {
+      context = context.prev;
+    }
+    function computeIndentation(baseContext) {
+      return function(nextChars) {
+        var context = baseContext;
+        if (context && context.noIndent)
+          return 0;
+        if (context && /^<\//.test(nextChars))
+          context = context.prev;
+        while (context && !context.startOfLine)
+          context = context.prev;
+        if (context)
+          return context.indent + 2;
+        else
+          return 0;
+      };
+    }
+
+    function base() {
+      return pass(element, base);
+    }
+    var harmlessTokens = {"text": true, "entity": true, "comment": true, "cdata": true};
+    function element(style, content) {
+      if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1));
+      else if (content == "</") cont(closetagname, expect(">"));
+      else if (content == "<?") cont(tagname, attributes, expect("?>"));
+      else if (harmlessTokens.hasOwnProperty(style)) cont();
+      else mark("error") || cont();
+    }
+    function tagname(style, content) {
+      if (style == "name") {
+        currentTag = content.toLowerCase();
+        mark("tagname");
+        cont();
+      }
+      else {
+        currentTag = null;
+        pass();
+      }
+    }
+    function closetagname(style, content) {
+      if (style == "name" && context && content.toLowerCase() == context.name) {
+        popContext();
+        mark("tagname");
+      }
+      else {
+        mark("error");
+      }
+      cont();
+    }
+    function endtag(startOfLine) {
+      return function(style, content) {
+        if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont();
+        else if (content == ">") pushContext(currentTag, startOfLine) || cont();
+        else mark("error") || cont(arguments.callee);
+      };
+    }
+    function attributes(style) {
+      if (style == "name") mark("attname") || cont(attribute, attributes);
+      else pass();
+    }
+    function attribute(style, content) {
+      if (content == "=") cont(value);
+      else if (content == ">" || content == "/>") pass(endtag);
+      else pass();
+    }
+    function value(style) {
+      if (style == "attribute") cont(value);
+      else pass();
+    }
+
+    return {
+      next: function(){
+        var token = tokens.next();
+        if (token.style == "whitespace" && tokenNr == 0)
+          indented = token.value.length;
+        else
+          tokenNr++;
+        if (token.content == "\n") {
+          indented = tokenNr = 0;
+          token.indentation = computeIndentation(context);
+        }
+
+        if (token.style == "whitespace" || token.type == "comment")
+          return token;
+
+        while(true){
+          consume = marked = false;
+          cc.pop()(token.style, token.content);
+          if (consume){
+            if (marked)
+              token.style = marked;
+            return token;
+          }
+        }
+      },
+
+      copy: function(){
+        var _cc = cc.concat([]), _tokenState = tokens.state, _context = context;
+        var parser = this;
+        
+        return function(input){
+          cc = _cc.concat([]);
+          tokenNr = indented = 0;
+          context = _context;
+          tokens = tokenizeXML(input, _tokenState);
+          return parser;
+        };
+      }
+    };
+  }
+
+  return {
+    make: parseXML,
+    electricChars: "/",
+    configure: function(config) {
+      if (config.useHTMLKludges)
+        UseKludges = Kludges;
+      else
+        UseKludges = NoKludges;
+    }
+  };
+})();
diff --git a/typo3/sysext/t3editor/jslib/codemirror/patch.codemirror055.diff b/typo3/sysext/t3editor/jslib/codemirror/patch.codemirror055.diff
new file mode 100644 (file)
index 0000000..b3e7d26
--- /dev/null
@@ -0,0 +1,2984 @@
+diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/codemirror.js ./codemirror.js
+--- /var/www/dev/cm055/CodeMirror-0.55/js/codemirror.js        2008-03-30 14:05:26.000000000 +0200
++++ ./codemirror.js    2008-04-04 17:44:43.000000000 +0200
+@@ -41,7 +41,8 @@
+     readOnly: false,
+     width: "100%",
+     height: "300px",
+-    parserConfig: null
++    parserConfig: null,
++      outerEditor: null
+   });
+   function CodeMirror(place, options) {
+diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/editor.js ./editor.js
+--- /var/www/dev/cm055/CodeMirror-0.55/js/editor.js    2008-03-30 14:05:26.000000000 +0200
++++ ./editor.js        2008-04-04 22:36:36.000000000 +0200
+@@ -343,6 +343,13 @@
+       addEventHandler(document, "keypress", method(this, "keyPress"));
+       addEventHandler(document, "keyup", method(this, "keyUp"));
+       addEventHandler(document.body, "paste", method(this, "markCursorDirty"));
++        if (options.outerEditor && options.outerEditor.scroll) {
++              addEventHandler(document, "scroll", method(options.outerEditor, "scroll"));
++              addEventHandler(window, "scroll", method(options.outerEditor, "scroll"));
++        }
++        if (options.outerEditor && options.outerEditor.click) {
++              addEventHandler(document, "click", method(options.outerEditor, "click"));
++        }
+     }
+   }
+@@ -356,6 +363,9 @@
+     importCode: function(code) {
+       this.history.push(null, null, asEditorLines(code));
+       this.history.reset();
++        if (this.options.outerEditor && this.options.outerEditor.updateLinenum) {
++              this.options.outerEditor.updateLinenum(code);
++        }
+     },
+     // Extract the code from the editor.
+@@ -447,9 +457,18 @@
+     // Intercept enter and tab, and assign their new functions.
+     keyDown: function(event) {
+-      // Don't scan when the user is typing.
++
++        // Don't scan when the user is typing.
+       this.delayScanning();
++        if (
++              this.options.outerEditor 
++              && this.options.outerEditor.checkTextModified
++              && !isSafeKey(event.keyCode)
++              && !event.ctrlKey ) {
++                      this.options.outerEditor.checkTextModified();
++        }
++
+       if (event.keyCode == 13) { // enter
+         if (event.ctrlKey) {
+           this.reparseBuffer();
+@@ -458,6 +477,9 @@
+           select.insertNewlineAtCursor(this.win);
+           this.indentAtCursor();
+           select.scrollToCursor(this.container);
++                if (this.options.outerEditor && this.options.outerEditor.updateLinenum) {
++                      this.options.outerEditor.updateLinenum();
++                }
+         }
+         event.stop();
+       }
+@@ -477,22 +499,30 @@
+         else if (event.keyCode == 83 && this.options.saveFunction) { // S
+           this.options.saveFunction();
+           event.stop();
+-        }
++        } 
++              else if (event.keyCode == 122 ) { // F11 toogle fullscreen mode
++                if (this.options.outerEditor && this.options.outerEditor.toggleFullscreen) {
++                      this.options.outerEditor.toggleFullscreen();
++                      event.stop();
++                }
++              }
+       }
+     },
+     // Check for characters that should re-indent the current line,
+     // and prevent Opera from handling enter and tab anyway.
+     keyPress: function(event) {
+-      var electric = Editor.Parser.electricChars;
++        var electric = Editor.Parser.electricChars;
+       // Hack for Opera, and Firefox on OS X, in which stopping a
+       // keydown event does not prevent the associated keypress event
+       // from happening, so we have to cancel enter and tab again
+       // here.
+       if (event.code == 13 || event.code == 9)
+         event.stop();
+-      else if (electric && electric.indexOf(event.character) != -1)
+-        this.parent.setTimeout(method(this, "indentAtCursor"), 0);
++      else if (electric && electric.indexOf(event.character) != -1) {
++              this.parent.setTimeout(method(this, "indentAtCursor"), 0);
++        }
++              
+     },
+     // Mark the node at the cursor dirty when a non-safe key is
+@@ -500,6 +530,18 @@
+     keyUp: function(event) {
+       if (!isSafeKey(event.keyCode))
+         this.markCursorDirty();
++
++        if (event.keyCode == 46       // Delete
++              || event.keyCode == 13  // Return
++              || event.keyCode == 8) {        // Backspace
++               if (this.options.outerEditor && this.options.outerEditor.updateLinenum) {
++                      this.options.outerEditor.updateLinenum();
++               }
++        }
++        if (this.options.outerEditor && this.options.outerEditor.checkBracketAtCursor) {
++              this.options.outerEditor.checkBracketAtCursor();
++        }
++        
+     },
+     // Indent the line following a given <br>, or null for the first
+@@ -604,6 +646,11 @@
+       if (cursor === false)
+         return;
+       var lineStart = startOfLine(cursor);
++        
++        if (this.options.outerEditor && this.options.outerEditor.autoCloseBracket) {
++          this.options.outerEditor.autoCloseBracket(lineStart.previousSibling);
++        }
++                      
+       var whiteSpace = this.indentLineAfter(lineStart);
+       if (cursor == lineStart && whiteSpace)
+           cursor = whiteSpace;
+diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/LICENSE ./LICENSE
+--- /var/www/dev/cm055/CodeMirror-0.55/js/LICENSE      1970-01-01 01:00:00.000000000 +0100
++++ ./LICENSE  2008-04-03 18:28:37.000000000 +0200
+@@ -0,0 +1,23 @@
++ Copyright (c) 2007 Marijn Haverbeke
++
++ This software is provided 'as-is', without any express or implied
++ warranty. In no event will the authors be held liable for any
++ damages arising from the use of this software.
++
++ Permission is granted to anyone to use this software for any
++ purpose, including commercial applications, and to alter it and
++ redistribute it freely, subject to the following restrictions:
++
++ 1. The origin of this software must not be misrepresented; you must
++    not claim that you wrote the original software. If you use this
++    software in a product, an acknowledgment in the product
++    documentation would be appreciated but is not required.
++
++ 2. Altered source versions must be plainly marked as such, and must
++    not be misrepresented as being the original software.
++
++ 3. This notice may not be removed or altered from any source
++    distribution.
++
++ Marijn Haverbeke
++ marijnh at gmail
+diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/mirrorframe.js ./mirrorframe.js
+--- /var/www/dev/cm055/CodeMirror-0.55/js/mirrorframe.js       2008-03-30 14:05:26.000000000 +0200
++++ ./mirrorframe.js   1970-01-01 01:00:00.000000000 +0100
+@@ -1,81 +0,0 @@
+-/* Demonstration of embedding CodeMirror in a bigger application. The
+- * interface defined here is a mess of prompts and confirms, and
+- * should probably not be used in a real project.
+- */
+-
+-function MirrorFrame(place, options) {
+-  this.home = document.createElement("DIV");
+-  if (place.appendChild)
+-    place.appendChild(this.home);
+-  else
+-    place(this.home);
+-
+-  var self = this;
+-  function makeButton(name, action) {
+-    var button = document.createElement("INPUT");
+-    button.type = "button";
+-    button.value = name;
+-    self.home.appendChild(button);
+-    button.onclick = function(){self[action].call(self);};
+-  }
+-
+-  makeButton("Search", "search");
+-  makeButton("Replace", "replace");
+-  makeButton("Current line", "line");
+-  makeButton("Jump to line", "jump");
+-  makeButton("Insert constructor", "macro");
+-  makeButton("Indent all", "reindent");
+-
+-  this.mirror = new CodeMirror(this.home, options);
+-}
+-
+-MirrorFrame.prototype = {
+-  search: function() {
+-    var text = prompt("Enter search term:", "");
+-    if (!text) return;
+-
+-    var first = true;
+-    do {
+-      var cursor = this.mirror.getSearchCursor(text, first);
+-      first = false;
+-      while (cursor.findNext()) {
+-        cursor.select();
+-        if (!confirm("Search again?"))
+-          return;
+-      }
+-    } while (confirm("End of document reached. Start over?"));
+-  },
+-
+-  replace: function() {
+-    // This is a replace-all, but it is possible to implement a
+-    // prompting replace.
+-    var from = prompt("Enter search string:", ""), to;
+-    if (from) to = prompt("What should it be replaced with?", "");
+-    if (!to) return;
+-
+-    var cursor = this.mirror.getSearchCursor(from, false);
+-    while (cursor.findNext())
+-      cursor.replace(to);
+-  },
+-
+-  jump: function() {
+-    var line = prompt("Jump to line:", "");
+-    if (line && !isNaN(Number(line)))
+-      this.mirror.jumpToLine(Number(line));
+-  },
+-
+-  line: function() {
+-    alert("The cursor is currently at line " + this.mirror.currentLine());
+-    this.mirror.focus();
+-  },
+-
+-  macro: function() {
+-    var name = prompt("Name your constructor:", "");
+-    if (name)
+-      this.mirror.replaceSelection("function " + name + "() {\n  \n}\n\n" + name + ".prototype = {\n  \n};\n", true);
+-  },
+-
+-  reindent: function() {
+-    this.mirror.reindent();
+-  }
+-};
+diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/parsetyposcript.js ./parsetyposcript.js
+--- /var/www/dev/cm055/CodeMirror-0.55/js/parsetyposcript.js   1970-01-01 01:00:00.000000000 +0100
++++ ./parsetyposcript.js       2008-04-02 23:43:38.000000000 +0200
+@@ -0,0 +1,457 @@
++/* TypoScript parser
++ *
++ * based on parsejavascript.js by Marijn Haverbeke
++ *
++ * A parser that can be plugged into the CodeMirror system has to
++ * implement the following interface: It is a function that, when
++ * called with a string stream (stringstream.js) as an argument,
++ * returns a MochiKit-style iterator (object with a 'next' method).
++ * This iterator, when called, consumes some input from the string
++ * stream, and returns a token object. Token objects must have a
++ * 'value' property (the text they represent), a 'style' property (the
++ * CSS style that should be used to colour them). Tokens for newline
++ * characters must also have a 'lexicalContext' property, which has an
++ * 'indentation' method that can be used to determine the proper
++ * indentation level for the next line. This method optionally takes
++ * the first character of the next line as an argument, which it can
++ * use to adjust the indentation level.
++ *
++ * So far this should be easy. The hard part is that the iterator
++ * produced by the parse function must also have a 'copy' method. This
++ * method, called without arguments, returns a function representing
++ * the current state of the parser. When this function is later called
++ * with a string stream as its argument, it returns a parser iterator
++ * object that resumes parsing using the old state and the new input
++ * stream. It may assume that only one parser is active at a time, and
++ * clobber the state of the old parser (the implementation below
++ * certianly does).
++ */
++
++// Parse function for TypoScript. Makes use of the tokenizer from
++// tokenizetyposcript.js. Note that your parsers do not have to be
++// this complicated -- if you don't want to recognize local variables,
++// in many languages it is enough to just look for braces, semicolons,
++// parentheses, etc, and know when you are inside a string or comment.
++Editor.Parser = (function() {
++      // Token types that can be considered to be atoms.
++      var atomicTypes = {
++              "atom": true,
++              "number": true,
++              "variable": true,
++              "string": true,
++              "regexp": true
++      };
++
++      // Constructor for the lexical context objects.
++      function TSLexical(indented, column, type, align, prev) {
++              // indentation at start of this line
++              this.indented = indented;
++              // column at which this scope was opened
++              this.column = column;
++              // type of scope ('vardef', 'stat' (statement), '[', '{', or '(')
++              this.type = type;
++              // '[', '{', or '(' blocks that have any text after their opening
++              // character are said to be 'aligned' -- any lines below are
++              // indented all the way to the opening character.
++              if (align != null) {
++                      this.align = align;
++              }
++              // Parent scope, if any.
++              this.prev = prev;
++
++      }
++      
++      
++      // My favourite TypoScript indentation rules.
++      function indentTS(lexical) {
++              return function(firstChars) {
++                      var firstChar = firstChars && firstChars.charAt(0);
++                      var closing = firstChar == lexical.type;
++
++                      if (lexical.type == "{" && firstChar != "}") {
++                              return lexical.indented + 2;
++                      }
++
++                      if (firstChar == "}" && lexical.prev) {
++                              lexical = lexical.prev;
++                      }
++                      
++                      if (lexical.align) {
++                              return lexical.column - (closing ? 1: 0);
++                      } else {
++                              return lexical.indented + (closing ? 0: 2);
++                      }
++
++              };
++      }
++
++      // The parser-iterator-producing function itself.
++      function parseTS(input) {
++              // Wrap the input in a token stream
++              var tokens = tokenizeTypoScript(input);
++              // The parser state. cc is a stack of actions that have to be
++              // performed to finish the current statement. For example we might
++              // know that we still need to find a closing parenthesis and a
++              // semicolon. Actions at the end of the stack go first. It is
++              // initialized with an infinitely looping action that consumes
++              // whole statements.
++              var cc = [statements];
++              // Context contains information about the current local scope, the
++              // variables defined in that, and the scopes above it.
++              var context = null;
++              // The lexical scope, used mostly for indentation.
++              var lexical = new TSLexical( -2, 0, "block", false);
++              // Current column, and the indentation at the start of the current
++              // line. Used to create lexical scope objects.
++              var column = 0;
++              var indented = 0;
++              // Variables which are used by the mark, cont, and pass functions
++              // below to communicate with the driver loop in the 'next'
++              // function.
++              var consume,
++              marked;
++
++              // The iterator object.
++              var parser = {
++                      next: next,
++                      copy: copy
++              };
++
++              function next() {
++                      // Start by performing any 'lexical' actions (adjusting the
++                      // lexical variable), or the operations below will be working
++                      // with the wrong lexical state.
++                      while (cc[cc.length - 1].lex) {
++                              cc.pop()();
++                      }
++
++                      // Fetch a token.
++                      var token = tokens.next();
++                      // Adjust column and indented.
++                      if (token.type == "whitespace" && column == 0) {
++                              indented = token.value.length;
++                      }
++                      column += token.value.length;
++                      if (token.type == "newline") {
++                              indented = column = 0;
++                              // If the lexical scope's align property is still undefined at
++                              // the end of the line, it is an un-aligned scope.
++                              if (! ("align" in lexical)) {
++                                      lexical.align = false;
++                              }
++                      // Newline tokens get a lexical context associated with them,
++                              // which is used for indentation.
++                              token.indentation = indentTS(lexical);
++                      }
++                      // No more processing for meaningless tokens.
++                      if (token.type == "whitespace" || token.type == "newline" || token.type == "comment") {
++                              return token;
++                      }
++                      // When a meaningful token is found and the lexical scope's
++                      // align is undefined, it is an aligned scope.
++                      if (! ("align" in lexical)) {
++                              lexical.align = true;
++                      }
++                      // Execute actions until one 'consumes' the token and we can
++                      // return it. Marked is used to
++                      while (true) {
++                              consume = marked = false;
++                              // Take and execute the topmost action.
++                              cc.pop()(token.type, token.name);
++                              if (consume) {
++                                      // Marked is used to change the style of the current token.
++                                      if (marked) {
++                                              token.style = marked;
++                                      }
++                                      return token;
++                              }
++                      }
++              }
++
++              // This makes a copy of the parser state. It stores all the
++              // stateful variables in a closure, and returns a function that
++              // will restore them when called with a new input stream. Note
++              // that the cc array has to be copied, because it is contantly
++              // being modified. Lexical objects are not mutated, and context
++              // objects are not mutated in a harmful way, so they can be shared
++              // between runs of the parser.
++              function copy() {
++                      var _context = context,
++                      _lexical = lexical,
++                      _cc = cc.concat([]),
++                      _regexp = tokens.regexp,
++                      _comment = tokens.inComment;
++
++                      return function(input) {
++                              context = _context;
++                              lexical = _lexical;
++                              cc = _cc.concat([]);
++                              // copies the array
++                              column = indented = 0;
++                              tokens = tokenizeTypoScript(input);
++                              tokens.regexp = _regexp;
++                              tokens.inComment = _comment;
++                              return parser;
++                      };
++              }
++
++              // Helper function for pushing a number of actions onto the cc
++              // stack in reverse order.
++              function push(fs) {
++                      for (var i = fs.length - 1; i >= 0; i--) {
++                              cc.push(fs[i]);
++                      }
++              }
++
++              // cont and pass are used by the action functions to add other
++              // actions to the stack. cont will cause the current token to be
++              // consumed, pass will leave it for the next action.
++              function cont() {
++                      push(arguments);
++                      consume = true;
++              }
++
++              function pass() {
++                      push(arguments);
++                      consume = false;
++              }
++
++              // Used to change the style of the current token.
++              function mark(style) {
++                      marked = style;
++              }
++
++              // Push a new scope. Will automatically link the the current
++              // scope.
++              function pushcontext() {
++                      context = {
++                              prev: context,
++                              vars: {
++                                      "this": true,
++                                      "arguments": true
++                              }
++                      };
++              }
++
++              // Pop off the current scope.
++              function popcontext() {
++                      context = context.prev;
++              }
++
++              // Register a variable in the current scope.
++              function register(varname) {
++                      if (context) {
++                              mark("variabledef");
++                              context.vars[varname] = true;
++                      }
++              }
++
++              // Push a new lexical context of the given type.
++              function pushlex(type) {
++                      var result = function() {
++                              lexical = new TSLexical(indented, column, type, null, lexical)
++                      };
++                      result.lex = true;
++                      return result;
++              }
++
++              // Pop off the current lexical context.
++              function poplex() {
++                      lexical = lexical.prev;
++              }
++
++              poplex.lex = true;
++              // The 'lex' flag on these actions is used by the 'next' function
++              // to know they can (and have to) be ran before moving on to the
++              // next token.
++
++              // Creates an action that discards tokens until it finds one of
++              // the given type.
++              function expect(wanted) {
++                      return function(type) {
++                              if (type == wanted) {
++                                      cont();
++                              } else {
++                                      cont(arguments.callee);
++                              }
++                      };
++              }
++
++              // Looks for a statement, and then calls itself.
++              function statements(type) {
++                      return pass(statement, statements);
++              }
++              // Dispatches various types of statements based on the type of the
++              // current token.
++              function statement(type) {
++                      if (type == "{") {
++                              cont(pushlex("{"), block, poplex);
++                      } else {
++                              cont();
++                      }
++              }
++
++              // Dispatch expression types.
++              function expression(type) {
++                      if (atomicTypes.hasOwnProperty(type)) {
++                              cont(maybeoperator);
++
++                      } else if (type == "function") {
++                              cont(functiondef);
++
++                      } else if (type == "keyword c") {
++                              cont(expression);
++
++                      } else if (type == "(") {
++                              cont(pushlex(")"), expression, expect(")"), poplex);
++
++                      } else if (type == "operator") {
++                              cont(expression);
++
++                      } else if (type == "[") {
++                              cont(pushlex("]"), commasep(expression), expect("]"), poplex);
++
++                      } else if (type == "{") {
++                              cont(pushlex("}"), commasep(objprop), expect("}"), poplex);
++                      }
++              }
++
++              // Called for places where operators, function calls, or
++              // subscripts are valid. Will skip on to the next action if none
++              // is found.
++              function maybeoperator(type) {
++                      if (type == "operator") {
++                              cont(expression);
++
++                      } else if (type == "(") {
++                              cont(pushlex(")"), expression, commasep(expression), expect(")"), poplex);
++
++                      } else if (type == ".") {
++                              cont(property, maybeoperator);
++
++                      } else if (type == "[") {
++                              cont(pushlex("]"), expression, expect("]"), poplex);
++                      }
++              }
++
++              // When a statement starts with a variable name, it might be a
++              // label. If no colon follows, it's a regular statement.
++              function maybelabel(type) {
++                      if (type == ":") {
++                              cont(poplex, statement);
++                      } else {
++                              pass(maybeoperator, expect(";"), poplex);
++                      }
++              }
++
++              // Property names need to have their style adjusted -- the
++              // tokenizer think they are variables.
++              function property(type) {
++                      if (type == "variable") {
++                              mark("property");
++                              cont();
++                      }
++              }
++
++              // This parses a property and its value in an object literal.
++              function objprop(type) {
++                      if (type == "variable") {
++                              mark("property");
++                      }
++                      if (atomicTypes.hasOwnProperty(type)) {
++                              cont(expect(":"), expression);
++                      }
++              }
++
++              // Parses a comma-separated list of the things that are recognized
++              // by the 'what' argument.
++              function commasep(what) {
++                      function proceed(type) {
++                              if (type == ",") {
++                                      cont(what, proceed);
++                              }
++                      };
++                      return function() {
++                              pass(what, proceed);
++                      };
++              }
++
++              // Look for statements until a closing brace is found.
++              function block(type) {
++                      if (type == "}") {
++                              cont();
++                      } else {
++                              pass(statement, block);
++                      }
++              }
++
++              // Look for statements until a closing brace is found.
++              function condition(type) {
++                      if (type == "]") {
++                              cont();
++                      } else {
++                              pass(statement, block);
++                      }
++              }
++
++              // Variable definitions are split into two actions -- 1 looks for
++              // a name or the end of the definition, 2 looks for an '=' sign or
++              // a comma.
++              function vardef1(type, value) {
++                      if (type == "variable") {
++                              register(value);
++                              cont(vardef2);
++                      } else {
++                              cont();
++                      }
++              }
++
++              function vardef2(type) {
++                      if (type == "operator") {
++                              cont(expression, vardef2);
++                      } else if (type == ",") {
++                              cont(vardef1);
++                      }
++              }
++
++              // For loops.
++              function forspec1(type, value) {
++                      if (type == "var") {
++                              cont(vardef1, forspec2);
++                      } else {
++                              cont(expression, forspec2);
++                      }
++              }
++
++              function forspec2(type) {
++                      if (type == ",") {
++                              cont(forspec1);
++                      }
++                      if (type == ";") {
++                              cont(expression, expect(";"), expression);
++                      }
++              }
++
++              // A function definition creates a new context, and the variables
++              // in its argument list have to be added to this context.
++              function functiondef(type, value) {
++                      if (type == "variable") {
++                              register(value);
++                              cont(functiondef);
++                      } else if (type == "(") {
++                              cont(pushcontext, commasep(funarg), expect(")"), statement, popcontext);
++                      }
++              }
++
++              function funarg(type, value) {
++                      if (type == "variable") {
++                              register(value);
++                              cont();
++                      }
++              }
++
++              return parser;
++      }
++      
++      return {make: parseTS, electricChars: "{}"};
++})();
+\ No newline at end of file
+diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/patch.codemirror055.diff ./patch.codemirror055.diff
+--- /var/www/dev/cm055/CodeMirror-0.55/js/patch.codemirror055.diff     1970-01-01 01:00:00.000000000 +0100
++++ ./patch.codemirror055.diff 2008-04-09 21:20:34.000000000 +0200
+@@ -0,0 +1,710 @@
++diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/codemirror.js ./codemirror.js
++--- /var/www/dev/cm055/CodeMirror-0.55/js/codemirror.js       2008-03-30 14:05:26.000000000 +0200
+++++ ./codemirror.js   2008-04-04 17:44:43.000000000 +0200
++@@ -41,7 +41,8 @@
++     readOnly: false,
++     width: "100%",
++     height: "300px",
++-    parserConfig: null
+++    parserConfig: null,
+++     outerEditor: null
++   });
++ 
++   function CodeMirror(place, options) {
++diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/editor.js ./editor.js
++--- /var/www/dev/cm055/CodeMirror-0.55/js/editor.js   2008-03-30 14:05:26.000000000 +0200
+++++ ./editor.js       2008-04-04 22:36:36.000000000 +0200
++@@ -343,6 +343,13 @@
++       addEventHandler(document, "keypress", method(this, "keyPress"));
++       addEventHandler(document, "keyup", method(this, "keyUp"));
++       addEventHandler(document.body, "paste", method(this, "markCursorDirty"));
+++       if (options.outerEditor && options.outerEditor.scroll) {
+++             addEventHandler(document, "scroll", method(options.outerEditor, "scroll"));
+++             addEventHandler(window, "scroll", method(options.outerEditor, "scroll"));
+++       }
+++       if (options.outerEditor && options.outerEditor.click) {
+++             addEventHandler(document, "click", method(options.outerEditor, "click"));
+++       }
++     }
++   }
++ 
++@@ -356,6 +363,9 @@
++     importCode: function(code) {
++       this.history.push(null, null, asEditorLines(code));
++       this.history.reset();
+++       if (this.options.outerEditor && this.options.outerEditor.updateLinenum) {
+++             this.options.outerEditor.updateLinenum(code);
+++       }
++     },
++ 
++     // Extract the code from the editor.
++@@ -447,9 +457,18 @@
++ 
++     // Intercept enter and tab, and assign their new functions.
++     keyDown: function(event) {
++-      // Don't scan when the user is typing.
+++
+++       // Don't scan when the user is typing.
++       this.delayScanning();
++ 
+++       if (
+++             this.options.outerEditor 
+++             && this.options.outerEditor.checkTextModified
+++             && !isSafeKey(event.keyCode)
+++             && !event.ctrlKey ) {
+++                     this.options.outerEditor.checkTextModified();
+++       }
+++
++       if (event.keyCode == 13) { // enter
++         if (event.ctrlKey) {
++           this.reparseBuffer();
++@@ -458,6 +477,9 @@
++           select.insertNewlineAtCursor(this.win);
++           this.indentAtCursor();
++           select.scrollToCursor(this.container);
+++               if (this.options.outerEditor && this.options.outerEditor.updateLinenum) {
+++                     this.options.outerEditor.updateLinenum();
+++               }
++         }
++         event.stop();
++       }
++@@ -477,22 +499,30 @@
++         else if (event.keyCode == 83 && this.options.saveFunction) { // S
++           this.options.saveFunction();
++           event.stop();
++-        }
+++        } 
+++             else if (event.keyCode == 122 ) { // F11 toogle fullscreen mode
+++               if (this.options.outerEditor && this.options.outerEditor.toggleFullscreen) {
+++                     this.options.outerEditor.toggleFullscreen();
+++                     event.stop();
+++               }
+++             }
++       }
++     },
++ 
++     // Check for characters that should re-indent the current line,
++     // and prevent Opera from handling enter and tab anyway.
++     keyPress: function(event) {
++-      var electric = Editor.Parser.electricChars;
+++       var electric = Editor.Parser.electricChars;
++       // Hack for Opera, and Firefox on OS X, in which stopping a
++       // keydown event does not prevent the associated keypress event
++       // from happening, so we have to cancel enter and tab again
++       // here.
++       if (event.code == 13 || event.code == 9)
++         event.stop();
++-      else if (electric && electric.indexOf(event.character) != -1)
++-        this.parent.setTimeout(method(this, "indentAtCursor"), 0);
+++      else if (electric && electric.indexOf(event.character) != -1) {
+++             this.parent.setTimeout(method(this, "indentAtCursor"), 0);
+++       }
+++             
++     },
++ 
++     // Mark the node at the cursor dirty when a non-safe key is
++@@ -500,6 +530,18 @@
++     keyUp: function(event) {
++       if (!isSafeKey(event.keyCode))
++         this.markCursorDirty();
+++
+++       if (event.keyCode == 46       // Delete
+++             || event.keyCode == 13  // Return
+++             || event.keyCode == 8) {        // Backspace
+++              if (this.options.outerEditor && this.options.outerEditor.updateLinenum) {
+++                     this.options.outerEditor.updateLinenum();
+++              }
+++       }
+++       if (this.options.outerEditor && this.options.outerEditor.checkBracketAtCursor) {
+++             this.options.outerEditor.checkBracketAtCursor();
+++       }
+++       
++     },
++ 
++     // Indent the line following a given <br>, or null for the first
++@@ -604,6 +646,11 @@
++       if (cursor === false)
++         return;
++       var lineStart = startOfLine(cursor);
+++       
+++       if (this.options.outerEditor && this.options.outerEditor.autoCloseBracket) {
+++         this.options.outerEditor.autoCloseBracket(lineStart.previousSibling);
+++       }
+++                     
++       var whiteSpace = this.indentLineAfter(lineStart);
++       if (cursor == lineStart && whiteSpace)
++           cursor = whiteSpace;
++diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/LICENSE ./LICENSE
++--- /var/www/dev/cm055/CodeMirror-0.55/js/LICENSE     1970-01-01 01:00:00.000000000 +0100
+++++ ./LICENSE 2008-04-03 18:28:37.000000000 +0200
++@@ -0,0 +1,23 @@
+++ Copyright (c) 2007 Marijn Haverbeke
+++
+++ This software is provided 'as-is', without any express or implied
+++ warranty. In no event will the authors be held liable for any
+++ damages arising from the use of this software.
+++
+++ Permission is granted to anyone to use this software for any
+++ purpose, including commercial applications, and to alter it and
+++ redistribute it freely, subject to the following restrictions:
+++
+++ 1. The origin of this software must not be misrepresented; you must
+++    not claim that you wrote the original software. If you use this
+++    software in a product, an acknowledgment in the product
+++    documentation would be appreciated but is not required.
+++
+++ 2. Altered source versions must be plainly marked as such, and must
+++    not be misrepresented as being the original software.
+++
+++ 3. This notice may not be removed or altered from any source
+++    distribution.
+++
+++ Marijn Haverbeke
+++ marijnh at gmail
++diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/mirrorframe.js ./mirrorframe.js
++--- /var/www/dev/cm055/CodeMirror-0.55/js/mirrorframe.js      2008-03-30 14:05:26.000000000 +0200
+++++ ./mirrorframe.js  1970-01-01 01:00:00.000000000 +0100
++@@ -1,81 +0,0 @@
++-/* Demonstration of embedding CodeMirror in a bigger application. The
++- * interface defined here is a mess of prompts and confirms, and
++- * should probably not be used in a real project.
++- */
++-
++-function MirrorFrame(place, options) {
++-  this.home = document.createElement("DIV");
++-  if (place.appendChild)
++-    place.appendChild(this.home);
++-  else
++-    place(this.home);
++-
++-  var self = this;
++-  function makeButton(name, action) {
++-    var button = document.createElement("INPUT");
++-    button.type = "button";
++-    button.value = name;
++-    self.home.appendChild(button);
++-    button.onclick = function(){self[action].call(self);};
++-  }
++-
++-  makeButton("Search", "search");
++-  makeButton("Replace", "replace");
++-  makeButton("Current line", "line");
++-  makeButton("Jump to line", "jump");
++-  makeButton("Insert constructor", "macro");
++-  makeButton("Indent all", "reindent");
++-
++-  this.mirror = new CodeMirror(this.home, options);
++-}
++-
++-MirrorFrame.prototype = {
++-  search: function() {
++-    var text = prompt("Enter search term:", "");
++-    if (!text) return;
++-
++-    var first = true;
++-    do {
++-      var cursor = this.mirror.getSearchCursor(text, first);
++-      first = false;
++-      while (cursor.findNext()) {
++-        cursor.select();
++-        if (!confirm("Search again?"))
++-          return;
++-      }
++-    } while (confirm("End of document reached. Start over?"));
++-  },
++-
++-  replace: function() {
++-    // This is a replace-all, but it is possible to implement a
++-    // prompting replace.
++-    var from = prompt("Enter search string:", ""), to;
++-    if (from) to = prompt("What should it be replaced with?", "");
++-    if (!to) return;
++-
++-    var cursor = this.mirror.getSearchCursor(from, false);
++-    while (cursor.findNext())
++-      cursor.replace(to);
++-  },
++-
++-  jump: function() {
++-    var line = prompt("Jump to line:", "");
++-    if (line && !isNaN(Number(line)))
++-      this.mirror.jumpToLine(Number(line));
++-  },
++-
++-  line: function() {
++-    alert("The cursor is currently at line " + this.mirror.currentLine());
++-    this.mirror.focus();
++-  },
++-
++-  macro: function() {
++-    var name = prompt("Name your constructor:", "");
++-    if (name)
++-      this.mirror.replaceSelection("function " + name + "() {\n  \n}\n\n" + name + ".prototype = {\n  \n};\n", true);
++-  },
++-
++-  reindent: function() {
++-    this.mirror.reindent();
++-  }
++-};
++diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/parsetyposcript.js ./parsetyposcript.js
++--- /var/www/dev/cm055/CodeMirror-0.55/js/parsetyposcript.js  1970-01-01 01:00:00.000000000 +0100
+++++ ./parsetyposcript.js      2008-04-02 23:43:38.000000000 +0200
++@@ -0,0 +1,457 @@
+++/* TypoScript parser
+++ *
+++ * based on parsejavascript.js by Marijn Haverbeke
+++ *
+++ * A parser that can be plugged into the CodeMirror system has to
+++ * implement the following interface: It is a function that, when
+++ * called with a string stream (stringstream.js) as an argument,
+++ * returns a MochiKit-style iterator (object with a 'next' method).
+++ * This iterator, when called, consumes some input from the string
+++ * stream, and returns a token object. Token objects must have a
+++ * 'value' property (the text they represent), a 'style' property (the
+++ * CSS style that should be used to colour them). Tokens for newline
+++ * characters must also have a 'lexicalContext' property, which has an
+++ * 'indentation' method that can be used to determine the proper
+++ * indentation level for the next line. This method optionally takes
+++ * the first character of the next line as an argument, which it can
+++ * use to adjust the indentation level.
+++ *
+++ * So far this should be easy. The hard part is that the iterator
+++ * produced by the parse function must also have a 'copy' method. This
+++ * method, called without arguments, returns a function representing
+++ * the current state of the parser. When this function is later called
+++ * with a string stream as its argument, it returns a parser iterator
+++ * object that resumes parsing using the old state and the new input
+++ * stream. It may assume that only one parser is active at a time, and
+++ * clobber the state of the old parser (the implementation below
+++ * certianly does).
+++ */
+++
+++// Parse function for TypoScript. Makes use of the tokenizer from
+++// tokenizetyposcript.js. Note that your parsers do not have to be
+++// this complicated -- if you don't want to recognize local variables,
+++// in many languages it is enough to just look for braces, semicolons,
+++// parentheses, etc, and know when you are inside a string or comment.
+++Editor.Parser = (function() {
+++     // Token types that can be considered to be atoms.
+++     var atomicTypes = {
+++             "atom": true,
+++             "number": true,
+++             "variable": true,
+++             "string": true,
+++             "regexp": true
+++     };
+++
+++     // Constructor for the lexical context objects.
+++     function TSLexical(indented, column, type, align, prev) {
+++             // indentation at start of this line
+++             this.indented = indented;
+++             // column at which this scope was opened
+++             this.column = column;
+++             // type of scope ('vardef', 'stat' (statement), '[', '{', or '(')
+++             this.type = type;
+++             // '[', '{', or '(' blocks that have any text after their opening
+++             // character are said to be 'aligned' -- any lines below are
+++             // indented all the way to the opening character.
+++             if (align != null) {
+++                     this.align = align;
+++             }
+++             // Parent scope, if any.
+++             this.prev = prev;
+++
+++     }
+++     
+++     
+++     // My favourite TypoScript indentation rules.
+++     function indentTS(lexical) {
+++             return function(firstChars) {
+++                     var firstChar = firstChars && firstChars.charAt(0);
+++                     var closing = firstChar == lexical.type;
+++
+++                     if (lexical.type == "{" && firstChar != "}") {
+++                             return lexical.indented + 2;
+++                     }
+++
+++                     if (firstChar == "}" && lexical.prev) {
+++                             lexical = lexical.prev;
+++                     }
+++                     
+++                     if (lexical.align) {
+++                             return lexical.column - (closing ? 1: 0);
+++                     } else {
+++                             return lexical.indented + (closing ? 0: 2);
+++                     }
+++
+++             };
+++     }
+++
+++     // The parser-iterator-producing function itself.
+++     function parseTS(input) {
+++             // Wrap the input in a token stream
+++             var tokens = tokenizeTypoScript(input);
+++             // The parser state. cc is a stack of actions that have to be
+++             // performed to finish the current statement. For example we might
+++             // know that we still need to find a closing parenthesis and a
+++             // semicolon. Actions at the end of the stack go first. It is
+++             // initialized with an infinitely looping action that consumes
+++             // whole statements.
+++             var cc = [statements];
+++             // Context contains information about the current local scope, the
+++             // variables defined in that, and the scopes above it.
+++             var context = null;
+++             // The lexical scope, used mostly for indentation.
+++             var lexical = new TSLexical( -2, 0, "block", false);
+++             // Current column, and the indentation at the start of the current
+++             // line. Used to create lexical scope objects.
+++             var column = 0;
+++             var indented = 0;
+++             // Variables which are used by the mark, cont, and pass functions
+++             // below to communicate with the driver loop in the 'next'
+++             // function.
+++             var consume,
+++             marked;
+++
+++             // The iterator object.
+++             var parser = {
+++                     next: next,
+++                     copy: copy
+++             };
+++
+++             function next() {
+++                     // Start by performing any 'lexical' actions (adjusting the
+++                     // lexical variable), or the operations below will be working
+++                     // with the wrong lexical state.
+++                     while (cc[cc.length - 1].lex) {
+++                             cc.pop()();
+++                     }
+++
+++                     // Fetch a token.
+++                     var token = tokens.next();
+++                     // Adjust column and indented.
+++                     if (token.type == "whitespace" && column == 0) {
+++                             indented = token.value.length;
+++                     }
+++                     column += token.value.length;
+++                     if (token.type == "newline") {
+++                             indented = column = 0;
+++                             // If the lexical scope's align property is still undefined at
+++                             // the end of the line, it is an un-aligned scope.
+++                             if (! ("align" in lexical)) {
+++                                     lexical.align = false;
+++                             }
+++                     // Newline tokens get a lexical context associated with them,
+++                             // which is used for indentation.
+++                             token.indentation = indentTS(lexical);
+++                     }
+++                     // No more processing for meaningless tokens.
+++                     if (token.type == "whitespace" || token.type == "newline" || token.type == "comment") {
+++                             return token;
+++                     }
+++                     // When a meaningful token is found and the lexical scope's
+++                     // align is undefined, it is an aligned scope.
+++                     if (! ("align" in lexical)) {
+++                             lexical.align = true;
+++                     }
+++                     // Execute actions until one 'consumes' the token and we can
+++                     // return it. Marked is used to
+++                     while (true) {
+++                             consume = marked = false;
+++                             // Take and execute the topmost action.
+++                             cc.pop()(token.type, token.name);
+++                             if (consume) {
+++                                     // Marked is used to change the style of the current token.
+++                                     if (marked) {
+++                                             token.style = marked;
+++                                     }
+++                                     return token;
+++                             }
+++                     }
+++             }
+++
+++             // This makes a copy of the parser state. It stores all the
+++             // stateful variables in a closure, and returns a function that
+++             // will restore them when called with a new input stream. Note
+++             // that the cc array has to be copied, because it is contantly
+++             // being modified. Lexical objects are not mutated, and context
+++             // objects are not mutated in a harmful way, so they can be shared
+++             // between runs of the parser.
+++             function copy() {
+++                     var _context = context,
+++                     _lexical = lexical,
+++                     _cc = cc.concat([]),
+++                     _regexp = tokens.regexp,
+++                     _comment = tokens.inComment;
+++
+++                     return function(input) {
+++                             context = _context;
+++                             lexical = _lexical;
+++                             cc = _cc.concat([]);
+++                             // copies the array
+++                             column = indented = 0;
+++                             tokens = tokenizeTypoScript(input);
+++                             tokens.regexp = _regexp;
+++                             tokens.inComment = _comment;
+++                             return parser;
+++                     };
+++             }
+++
+++             // Helper function for pushing a number of actions onto the cc
+++             // stack in reverse order.
+++             function push(fs) {
+++                     for (var i = fs.length - 1; i >= 0; i--) {
+++                             cc.push(fs[i]);
+++                     }
+++             }
+++
+++             // cont and pass are used by the action functions to add other
+++             // actions to the stack. cont will cause the current token to be
+++             // consumed, pass will leave it for the next action.
+++             function cont() {
+++                     push(arguments);
+++                     consume = true;
+++             }
+++
+++             function pass() {
+++                     push(arguments);
+++                     consume = false;
+++             }
+++
+++             // Used to change the style of the current token.
+++             function mark(style) {
+++                     marked = style;
+++             }
+++
+++             // Push a new scope. Will automatically link the the current
+++             // scope.
+++             function pushcontext() {
+++                     context = {
+++                             prev: context,
+++                             vars: {
+++                                     "this": true,
+++                                     "arguments": true
+++                             }
+++                     };
+++             }
+++
+++             // Pop off the current scope.
+++             function popcontext() {
+++                     context = context.prev;
+++             }
+++
+++             // Register a variable in the current scope.
+++             function register(varname) {
+++                     if (context) {
+++                             mark("variabledef");
+++                             context.vars[varname] = true;
+++                     }
+++             }
+++
+++             // Push a new lexical context of the given type.
+++             function pushlex(type) {
+++                     var result = function() {
+++                             lexical = new TSLexical(indented, column, type, null, lexical)
+++                     };
+++                     result.lex = true;
+++                     return result;
+++             }
+++
+++             // Pop off the current lexical context.
+++             function poplex() {
+++                     lexical = lexical.prev;
+++             }
+++
+++             poplex.lex = true;
+++             // The 'lex' flag on these actions is used by the 'next' function
+++             // to know they can (and have to) be ran before moving on to the
+++             // next token.
+++
+++             // Creates an action that discards tokens until it finds one of
+++             // the given type.
+++             function expect(wanted) {
+++                     return function(type) {
+++                             if (type == wanted) {
+++                                     cont();
+++                             } else {
+++                                     cont(arguments.callee);
+++                             }
+++                     };
+++             }
+++
+++             // Looks for a statement, and then calls itself.
+++             function statements(type) {
+++                     return pass(statement, statements);
+++             }
+++             // Dispatches various types of statements based on the type of the
+++             // current token.
+++             function statement(type) {
+++                     if (type == "{") {
+++                             cont(pushlex("{"), block, poplex);
+++                     } else {
+++                             cont();
+++                     }
+++             }
+++
+++             // Dispatch expression types.
+++             function expression(type) {
+++                     if (atomicTypes.hasOwnProperty(type)) {
+++                             cont(maybeoperator);
+++
+++                     } else if (type == "function") {
+++                             cont(functiondef);
+++
+++                     } else if (type == "keyword c") {
+++                             cont(expression);
+++
+++                     } else if (type == "(") {
+++                             cont(pushlex(")"), expression, expect(")"), poplex);
+++
+++                     } else if (type == "operator") {
+++                             cont(expression);
+++
+++                     } else if (type == "[") {
+++                             cont(pushlex("]"), commasep(expression), expect("]"), poplex);
+++
+++                     } else if (type == "{") {
+++                             cont(pushlex("}"), commasep(objprop), expect("}"), poplex);
+++                     }
+++             }
+++
+++             // Called for places where operators, function calls, or
+++             // subscripts are valid. Will skip on to the next action if none
+++             // is found.
+++             function maybeoperator(type) {
+++                     if (type == "operator") {
+++                             cont(expression);
+++
+++                     } else if (type == "(") {
+++                             cont(pushlex(")"), expression, commasep(expression), expect(")"), poplex);
+++
+++                     } else if (type == ".") {
+++                             cont(property, maybeoperator);
+++
+++                     } else if (type == "[") {
+++                             cont(pushlex("]"), expression, expect("]"), poplex);
+++                     }
+++             }
+++
+++             // When a statement starts with a variable name, it might be a
+++             // label. If no colon follows, it's a regular statement.
+++             function maybelabel(type) {
+++                     if (type == ":") {
+++                             cont(poplex, statement);
+++                     } else {
+++                             pass(maybeoperator, expect(";"), poplex);
+++                     }
+++             }
+++
+++             // Property names need to have their style adjusted -- the
+++             // tokenizer think they are variables.
+++             function property(type) {
+++                     if (type == "variable") {
+++                             mark("property");
+++                             cont();
+++                     }
+++             }
+++
+++             // This parses a property and its value in an object literal.
+++             function objprop(type) {
+++                     if (type == "variable") {
+++                             mark("property");
+++                     }
+++                     if (atomicTypes.hasOwnProperty(type)) {
+++                             cont(expect(":"), expression);
+++                     }
+++             }
+++
+++             // Parses a comma-separated list of the things that are recognized
+++             // by the 'what' argument.
+++             function commasep(what) {
+++                     function proceed(type) {
+++                             if (type == ",") {
+++                                     cont(what, proceed);
+++                             }
+++                     };
+++                     return function() {
+++                             pass(what, proceed);
+++                     };
+++             }
+++
+++             // Look for statements until a closing brace is found.
+++             function block(type) {
+++                     if (type == "}") {
+++                             cont();
+++                     } else {
+++                             pass(statement, block);
+++                     }
+++             }
+++
+++             // Look for statements until a closing brace is found.
+++             function condition(type) {
+++                     if (type == "]") {
+++                             cont();
+++                     } else {
+++                             pass(statement, block);
+++                     }
+++             }
+++
+++             // Variable definitions are split into two actions -- 1 looks for
+++             // a name or the end of the definition, 2 looks for an '=' sign or
+++             // a comma.
+++             function vardef1(type, value) {
+++                     if (type == "variable") {
+++                             register(value);
+++                             cont(vardef2);
+++                     } else {
+++                             cont();
+++                     }
+++             }
+++
+++             function vardef2(type) {
+++                     if (type == "operator") {
+++                             cont(expression, vardef2);
+++                     } else if (type == ",") {
+++                             cont(vardef1);
+++                     }
+++             }
+++
+++             // For loops.
+++             function forspec1(type, value) {
+++                     if (type == "var") {
+++                             cont(vardef1, forspec2);
+++                     } else {
+++                             cont(expression, forspec2);
+++                     }
+++             }
+++
+++             function forspec2(type) {
+++                     if (type == ",") {
+++                             cont(forspec1);
+++                     }
+++                     if (type == ";") {
+++                             cont(expression, expect(";"), expression);
+++                     }
+++             }
+++
+++             // A function definition creates a new context, and the variables
+++             // in its argument list have to be added to this context.
+++             function functiondef(type, value) {
+++                     if (type == "variable") {
+++                             register(value);
+++                             cont(functiondef);
+++                     } else if (type == "(") {
+++                             cont(pushcontext, commasep(funarg), expect(")"), statement, popcontext);
+++                     }
+++             }
+++
+++             function funarg(type, value) {
+++                     if (type == "variable") {
+++                             register(value);
+++                             cont();
+++                     }
+++             }
+++
+++             return parser;
+++     }
+++     
+++     return {make: parseTS, electricChars: "{}"};
+++})();
++\ No newline at end of file
+diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/select.js ./select.js
+--- /var/www/dev/cm055/CodeMirror-0.55/js/select.js    2008-03-30 14:05:26.000000000 +0200
++++ ./select.js        2008-04-04 22:48:43.000000000 +0200
+@@ -120,6 +120,17 @@
+         range.select();
+       }
+     }
++      
++      // Insert a custom string at current cursor position (added for t3editor)
++      select.insertTextAtCursor = function(window, text) {
++              var selection = window.document.selection;
++              if (selection) {
++                      var range = selection.createRange();
++                      range.pasteHTML(text);
++                      range.collapse(false);
++                      range.select();
++              }
++      };
+     // Used to normalize the effect of the enter key, since browsers
+     // do widely different things when pressing enter in designMode.
+@@ -237,7 +248,7 @@
+       var selection = window.getSelection();
+       selection.removeAllRanges();
+       selection.addRange(range);
+-    };
++    }
+     function selectionRange(window) {
+       var selection = window.getSelection();
+       if (!selection || selection.rangeCount == 0)
+@@ -369,26 +380,18 @@
+       selectRange(range, win);
+     };
+-    insertNodeAtCursor = function(window, node) {
+-      var range = selectionRange(window);
+-      if (!range) return;
+-
+-      // On Opera, insertNode is completely broken when the range is
+-      // in the middle of a text node.
+-      if (window.opera && range.startContainer.nodeType == 3 && range.startOffset != 0) {
+-        var start = range.startContainer, text = start.nodeValue;
+-        start.parentNode.insertBefore(window.document.createTextNode(text.substr(0, range.startOffset)), start);
+-        start.nodeValue = text.substr(range.startOffset);
+-        start.parentNode.insertBefore(node, start);
+-      }
+-      else {
+-        range.insertNode(node);
+-      }
+-
+-      range.setEndAfter(node);
+-      range.collapse(false);
+-      selectRange(range, window);
+-    }
++      // added for t3editor
++      select.insertTextAtCursor = function(window, text) {
++              var selection = window.getSelection();
++              if (selection && selection.rangeCount > 0) {
++                      var range = selection.getRangeAt(0);
++                      textnode = window.document.createTextNode(text);
++                      range.insertNode(textnode);
++                      range.setEndAfter(textnode);
++                      range.collapse(false);
++                      selectRange(range, window);
++              }
++      };
+     select.insertNewlineAtCursor = function(window) {
+       insertNodeAtCursor(window, window.document.createElement("BR"));
+@@ -474,4 +477,25 @@
+         win.scrollTo(0, y);
+     };
+   }
++  
++  insertNodeAtCursor = function(window, node) {
++      var range = selectionRange(window);
++      if (!range) return;
++
++      // On Opera, insertNode is completely broken when the range is
++      // in the middle of a text node.
++      if (window.opera && range.startContainer.nodeType == 3 && range.startOffset != 0) {
++        var start = range.startContainer, text = start.nodeValue;
++        start.parentNode.insertBefore(window.document.createTextNode(text.substr(0, range.startOffset)), start);
++        start.nodeValue = text.substr(range.startOffset);
++        start.parentNode.insertBefore(node, start);
++      }
++      else {
++        range.insertNode(node);
++      }
++
++      range.setEndAfter(node);
++      range.collapse(false);
++      selectRange(range, window);
++    }
+ }());
+diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/tokenizetyposcript.js ./tokenizetyposcript.js
+--- /var/www/dev/cm055/CodeMirror-0.55/js/tokenizetyposcript.js        1970-01-01 01:00:00.000000000 +0100
++++ ./tokenizetyposcript.js    2008-04-02 16:23:19.000000000 +0200
+@@ -0,0 +1,1446 @@
++/* Tokenizer for TypoScript code
++ *
++ * based on tokenizejavascript.js by Marijn Haverbeke
++ */
++
++// List of "reserved" word in typoscript and a css-class
++var typoscriptWords = {
++      '_CSS_DEFAULT_STYLE': 'keyword',
++      '_DEFAULT_PI_VARS': 'keyword',
++      '_GIFBUILDER': 'keyword',
++      '_LOCAL_LANG': 'keyword',
++      'CARRAY': 'keyword',
++      'CASE': 'keyword',
++      'CLEARGIF': 'keyword',
++      'COA': 'keyword',
++      'COA_INT': 'keyword',
++      'COBJ_ARRAY': 'keyword',
++      'COLUMNS': 'keyword',
++      'CONFIG': 'keyword',
++      'CONSTANTS': 'keyword',
++      'CONTENT': 'keyword',
++      'CTABLE': 'keyword',
++      'CType': 'keyword',
++      'DB': 'keyword',
++      'DOCUMENT_BODY': 'keyword',
++      'EDITPANEL': 'keyword',
++      'EFFECT': 'keyword',
++      'FE_DATA': 'keyword',
++      'FE_TABLE': 'keyword',
++      'FEData': 'keyword',
++      'FILE': 'keyword',
++      'FORM': 'keyword',
++      'FRAME': 'keyword',
++      'FRAMESET': 'keyword',
++      'GIFBUILDER': 'keyword',
++      'global': 'keyword',
++      'globalString': 'keyword',
++      'globalVar': 'keyword',
++      'GMENU': 'keyword',
++      'GMENU_FOLDOUT': 'keyword',
++      'GMENU_LAYERS': 'keyword',
++      'GP': 'keyword',
++      'HMENU': 'keyword',
++      'HRULER': 'keyword',
++      'HTML': 'keyword',
++      'IENV': 'keyword',
++      'IMAGE': 'keyword',
++      'IMG_RESOURCE': 'keyword',
++      'IMGMENU': 'keyword',
++      'IMGMENUITEM': 'keyword',
++      'IMGTEXT': 'keyword',
++      'INCLUDE_TYPOSCRIPT': 'keyword',
++      'includeLibs': 'keyword',
++      'JSMENU': 'keyword',
++      'JSMENUITEM': 'keyword',
++      'LIT': 'keyword',
++      'LOAD_REGISTER': 'keyword',
++      'META': 'keyword',
++      'MULTIMEDIA': 'keyword',
++      'OTABLE': 'keyword',
++      'PAGE': 'keyword',
++      'PAGE_TARGET': 'keyword',
++      'PAGE_TSCONFIG_ID': 'keyword',
++      'PAGE_TSCONFIG_IDLIST': 'keyword',
++      'PAGE_TSCONFIG_STR': 'keyword',
++      'PHP_SCRIPT': 'keyword',
++      'PHP_SCRIPT_EXT': 'keyword',
++      'PHP_SCRIPT_INT': 'keyword',
++      'RECORDS': 'keyword',
++      'REMOTE_ADDR': 'keyword',
++      'RESTORE_REGISTER': 'keyword',
++      'RTE': 'keyword',
++      'SEARCHRESULT': 'keyword',
++      'SHARED': 'keyword',
++      'TCAdefaults': 'keyword',
++      'TCEFORM': 'keyword',
++      'TCEMAIN': 'keyword',
++      'TEMPLATE': 'keyword',
++      'TEXT': 'keyword',
++      'TMENU': 'keyword',
++      'TMENU_LAYERS': 'keyword',
++      'TMENUITEM': 'keyword',
++      'TSFE': 'keyword',
++      'USER': 'keyword',
++      'USER_INT': 'keyword',
++      'userFunc': 'keyword',
++
++      '_offset': 'reserved',
++      'absRefPrefix': 'reserved',
++      'accessibility': 'reserved',
++      'accessKey': 'reserved',
++      'addAttributes': 'reserved',
++      'addExtUrlsAndShortCuts': 'reserved',
++      'addItems': 'reserved',
++      'additionalHeaders': 'reserved',
++      'additionalParams': 'reserved',
++      'addParams': 'reserved',
++      'addQueryString': 'reserved',
++      'adjustItemsH': 'reserved',
++      'adjustSubItemsH': 'reserved',
++      'adminPanelStyles': 'reserved',
++      'after': 'reserved',
++      'afterImg': 'reserved',
++      'afterImgLink': 'reserved',
++      'afterImgTagParams': 'reserved',
++      'afterROImg': 'reserved',
++      'afterWrap': 'reserved',
++      'age': 'reserved',
++      'alertPopups': 'reserved',
++      'align': 'reserved',
++      'allow': 'reserved',
++      'allowCaching': 'reserved',
++      'allowedAttribs': 'reserved',
++      'allowedClasses': 'reserved',
++      'allowedCols': 'reserved',
++      'allowEdit': 'reserved',
++      'allowedNewTables': 'reserved',
++      'allowNew': 'reserved',
++      'allowTags': 'reserved',
++      'allowTVlisting': 'reserved',
++      'allSaveFunctions': 'reserved',
++      'allStdWrap': 'reserved',
++      'allWrap': 'reserved',
++      'alternateBgColors': 'reserved',
++      'alternativeSortingField': 'reserved',
++      'alternativeTempPath': 'reserved',
++      'altImgResource': 'reserved',
++      'altLabels': 'reserved',
++      'altTarget': 'reserved',
++      'altText': 'reserved',
++      'altUrl': 'reserved',
++      'altUrl_noDefaultParams': 'reserved',
++      'altWrap': 'reserved',
++      'always': 'reserved',
++      'alwaysActivePIDlist': 'reserved',
++      'alwaysLink': 'reserved',
++      'alwaysShowClickMenuInTopFrame': 'reserved',
++      'andWhere': 'reserved',
++      'angle': 'reserved',
++      'antiAlias': 'reserved',
++      'append': 'reserved',
++      'applyTotalH': 'reserved',
++      'applyTotalW': 'reserved',
++      'archive': 'reserved',
++      'archiveTypoLink': 'reserved',
++      'arrayReturnMode': 'reserved',
++      'arrowACT': 'reserved',
++      'arrowImgParams': 'reserved',
++      'arrowNO': 'reserved',
++      'ATagAfterWrap': 'reserved',
++      'ATagBeforeWrap': 'reserved',
++      'ATagParams': 'reserved',
++      'ATagTitle': 'reserved',
++      'attribute': 'reserved',
++      'autoInsertPID': 'reserved',
++      'autoLevels': 'reserved',
++      'autonumber': 'reserved',
++      'backColor': 'reserved',
++      'background': 'reserved',
++      'badMess': 'reserved',
++      'baseURL': 'reserved',
++      'before': 'reserved',
++      'beforeImg': 'reserved',
++      'beforeImgLink': 'reserved',
++      'beforeImgTagParams': 'reserved',
++      'beforeROImg': 'reserved',
++      'beforeWrap': 'reserved',
++      'begin': 'reserved',
++      'beLoginLinkIPList': 'reserved',
++      'beLoginLinkIPList_login': 'reserved',
++      'beLoginLinkIPList_logout': 'reserved',
++      'bgCol': 'reserved',
++      'bgImg': 'reserved',
++      'blankStrEqFalse': 'reserved',
++      'blur': 'reserved',
++      'bm': 'reserved',
++      'bodyTag': 'reserved',
++      'bodyTagAdd': 'reserved',
++      'bodyTagCObject': 'reserved',
++      'bodyTagMargins': 'reserved',
++      'bodytext': 'reserved',
++      'border': 'reserved',
++      'borderCol': 'reserved',
++      'bordersWithin': 'reserved',
++      'borderThick': 'reserved',
++      'bottomBackColor': 'reserved',
++      'bottomContent': 'reserved',
++      'bottomHeight': 'reserved',
++      'bottomImg': 'reserved',
++      'bottomImg_mask': 'reserved',
++      'br': 'reserved',
++      'brTag': 'reserved',
++      'bullet': 'reserved',
++      'bulletlist': 'reserved',
++      'bytes': 'reserved',
++      'cache_clearAtMidnight': 'reserved',
++      'cache_period': 'reserved',
++      'caption': 'reserved',
++      'caption_stdWrap': 'reserved',
++      'captionAlign': 'reserved',
++      'captionHeader': 'reserved',
++      'captionSplit': 'reserved',
++      'case': 'reserved',
++      'casesensitiveComp': 'reserved',
++      'cellpadding': 'reserved',
++      'cellspacing': 'reserved',
++      'centerImgACT': 'reserved',
++      'centerImgCUR': 'reserved',
++      'centerImgNO': 'reserved',
++      'centerLeftImgACT': 'reserved',
++      'centerLeftImgCUR': 'reserved',
++      'centerLeftImgNO': 'reserved',
++      'centerRightImgACT': 'reserved',
++      'centerRightImgCUR': 'reserved',
++      'centerRightImgNO': 'reserved',
++      'char': 'reserved',
++      'charcoal': 'reserved',
++      'charMapConfig': 'reserved',
++      'check': 'reserved',
++      'class': 'reserved',
++      'classesAnchor': 'reserved',
++      'classesCharacter': 'reserved',
++      'classesImage': 'reserved',
++      'classesParagraph': 'reserved',
++      'classicPageEditMode': 'reserved',
++      'clear': 'reserved',
++      'clearCache': 'reserved',
++      'clearCache_disable': 'reserved',
++      'clearCache_pageGrandParent': 'reserved',
++      'clearCache_pageSiblingChildren': 'reserved',
++      'clearCacheCmd': 'reserved',
++      'clearCacheLevels': 'reserved',
++      'clearCacheOfPages': 'reserved',
++      'clickMenuTimeOut': 'reserved',
++      'clickTitleMode': 'reserved',
++      'clipboardNumberPads': 'reserved',
++      'cMargins': 'reserved',
++      'cObjNum': 'reserved',
++      'collapse': 'reserved',
++      'color': 'reserved',
++      'color1': 'reserved',
++      'color2': 'reserved',
++      'color3': 'reserved',
++      'color4': 'reserved',
++      'colors': 'reserved',
++      'colour': 'reserved',
++      'colPos_list': 'reserved',
++      'colRelations': 'reserved',
++      'cols': 'reserved',
++      'colSpace': 'reserved',
++      'comment_auto': 'reserved',
++      'commentWrap': 'reserved',
++      'compensateFieldWidth': 'reserved',
++      'compX': 'reserved',
++      'compY': 'reserved',
++      'condensedMode': 'reserved',
++      'conf': 'reserved',
++      'constants': 'reserved',
++      'content_from_pid_allowOutsideDomain': 'reserved',
++      'contextMenu': 'reserved',
++      'copyLevels': 'reserved',
++      'count_HMENU_MENUOBJ': 'reserved',
++      'count_menuItems': 'reserved',
++      'count_MENUOBJ': 'reserved',
++      'create': 'reserved',
++      'createFoldersInEB': 'reserved',
++      'crop': 'reserved',
++      'csConv': 'reserved',
++      'CSS_inlineStyle': 'reserved',
++      'current': 'reserved',
++      'curUid': 'reserved',
++      'cWidth': 'reserved',
++      'data': 'reserved',
++      'dataWrap': 'reserved',
++      'date': 'reserved',
++      'date_stdWrap': 'reserved',
++      'datePrefix': 'reserved',
++      'debug': 'reserved',
++      'debugData': 'reserved',
++      'debugFunc': 'reserved',
++      'debugItemConf': 'reserved',
++      'debugRenumberedObject': 'reserved',
++      'default': 'reserved',
++      'defaultAlign': 'reserved',
++      'defaultCmd': 'reserved',
++      'defaultFileUploads': 'reserved',
++      'defaultHeaderType': 'reserved',
++      'defaultOutput': 'reserved',
++      'defaults': 'reserved',
++      'defaultType': 'reserved',
++      'delete': 'reserved',
++      'denyTags': 'reserved',
++      'depth': 'reserved',
++      'DESC': 'reserved',
++      'dimensions': 'reserved',
++      'directionLeft': 'reserved',
++      'directionUp': 'reserved',
++      'disableAdvanced': 'reserved',
++      'disableAllHeaderCode': 'reserved',
++      'disableAltText': 'reserved',
++      'disableBigButtons': 'reserved',
++      'disableCacheSelector': 'reserved',
++      'disableCharsetHeader': 'reserved',
++      'disableCMlayers': 'reserved',
++      'disabled': 'reserved',
++      'disableDelete': 'reserved',
++      'disableDocModuleInAB': 'reserved',
++      'disableDocSelector': 'reserved',
++      'disableHideAtCopy': 'reserved',
++      'disableIconLinkToContextmenu': 'reserved',
++      'disableItems': 'reserved',
++      'disableNewContentElementWizard': 'reserved',
++      'disableNoMatchingValueElement': 'reserved',
++      'disablePageExternalUrl': 'reserved',
++      'disablePrefixComment': 'reserved',
++      'disablePrependAtCopy': 'reserved',
++      'disableSearchBox': 'reserved',
++      'disableSingleTableView': 'reserved',
++      'disableTabInTextarea': 'reserved',
++      'displayActiveOnLoad': 'reserved',
++      'displayContent': 'reserved',
++      'displayFieldIcons': 'reserved',
++      'displayIcons': 'reserved',
++      'displayMessages': 'reserved',
++      'displayQueries': 'reserved',
++      'displayRecord': 'reserved',
++      'displayTimes': 'reserved',
++      'distributeX': 'reserved',
++      'distributeY': 'reserved',
++      'DIV': 'reserved',
++      'doctype': 'reserved',
++      'doctypeSwitch': 'reserved',
++      'doktype': 'reserved',
++      'doNotLinkIt': 'reserved',
++      'doNotShowLink': 'reserved',
++      'doNotStripHTML': 'reserved',
++      'dontCheckPid': 'reserved',
++      'dontFollowMouse': 'reserved',
++      'dontHideOnMouseUp': 'reserved',
++      'dontLinkIfSubmenu': 'reserved',
++      'dontShowPalettesOnFocusInAB': 'reserved',
++      'dontWrapInTable': 'reserved',
++      'doubleBrTag': 'reserved',
++      'doublePostCheck': 'reserved',
++      'dWorkArea': 'reserved',
++      'edge': 'reserved',
++      'edit_docModuleUplaod': 'reserved',
++      'edit_docModuleUpload': 'reserved',
++      'edit_RTE': 'reserved',
++      'edit_showFieldHelp': 'reserved',
++      'edit_wideDocument': 'reserved',
++      'editFieldsAtATime': 'reserved',
++      'editFormsOnPage': 'reserved',
++      'editIcons': 'reserved',
++      'editNoPopup': 'reserved',
++      'editPanel': 'reserved',
++      'elements': 'reserved',
++      'emailMeAtLogin': 'reserved',
++      'emailMess': 'reserved',
++      'emboss': 'reserved',
++      'enable': 'reserved',
++      'encapsLines': 'reserved',
++      'encapsLinesStdWrap': 'reserved',
++      'encapsTagList': 'reserved',
++      'entryLevel': 'reserved',
++      'equalH': 'reserved',
++      'everybody': 'reserved',
++      'excludeDoktypes': 'reserved',
++      'excludeUidList': 'reserved',
++      'expAll': 'reserved',
++      'expand': 'reserved',
++      'explode': 'reserved',
++      'ext': 'reserved',
++      'externalBlocks': 'reserved',
++      'extTarget': 'reserved',
++      'face': 'reserved',
++      'fe_adminLib': 'reserved',
++      'field': 'reserved',
++      'fieldOrder': 'reserved',
++      'fieldRequired': 'reserved',
++      'fields': 'reserved',
++      'fieldWrap': 'reserved',
++      'file': 'reserved',
++      'file1': 'reserved',
++      'file2': 'reserved',
++      'file3': 'reserved',
++      'file4': 'reserved',
++      'file5': 'reserved',
++      'filelink': 'reserved',
++      'filelist': 'reserved',
++      'firstLabel': 'reserved',
++      'firstLabelGeneral': 'reserved',
++      'fixAttrib': 'reserved',
++      'flip': 'reserved',
++      'flop': 'reserved',
++      'foldSpeed': 'reserved',
++      'foldTimer': 'reserved',
++      'fontColor': 'reserved',
++      'fontFile': 'reserved',
++      'fontOffset': 'reserved',
++      'fontSize': 'reserved',
++      'fontSizeMultiplicator': 'reserved',
++      'fontTag': 'reserved',
++      'forceDisplayFieldIcons': 'reserved',
++      'forceDisplayIcons': 'reserved',
++      'forceNoPopup': 'reserved',
++      'forceTemplateParsing': 'reserved',
++      'forceTypeValue': 'reserved',
++      'format': 'reserved',
++      'frame': 'reserved',
++      'frameReloadIfNotInFrameset': 'reserved',
++      'frameSet': 'reserved',
++      'freezeMouseover': 'reserved',
++      'ftu': 'reserved',
++      'function': 'reserved',
++      'gamma': 'reserved',
++      'gapBgCol': 'reserved',
++      'gapLineCol': 'reserved',
++      'gapLineThickness': 'reserved',
++      'gapWidth': 'reserved',
++      'get': 'reserved',
++      'getBorder': 'reserved',
++      'getLeft': 'reserved',
++      'getRight': 'reserved',
++      'globalNesting': 'reserved',
++      'goodMess': 'reserved',
++      'gray': 'reserved',
++      'group': 'reserved',
++      'groupBy': 'reserved',
++      'groupid': 'reserved',
++      'header': 'reserved',
++      'header_layout': 'reserved',
++      'headerComment': 'reserved',
++      'headerData': 'reserved',
++      'headerSpace': 'reserved',
++      'headTag': 'reserved',
++      'height': 'reserved',
++      'helpText': 'reserved',
++      'hidden': 'reserved',
++      'hiddenFields': 'reserved',
++      'hide': 'reserved',
++      'hideButCreateMap': 'reserved',
++      'hideMenuTimer': 'reserved',
++      'hideMenuWhenNotOver': 'reserved',
++      'hidePStyleItems': 'reserved',
++      'hideRecords': 'reserved',
++      'hideSubmoduleIcons': 'reserved',
++      'highColor': 'reserved',
++      'history': 'reserved',
++      'hover': 'reserved',
++      'hoverStyle': 'reserved',
++      'HTMLparser': 'reserved',
++      'HTMLparser_tags': 'reserved',
++      'htmlSpecialChars': 'reserved',
++      'htmlTag_dir': 'reserved',
++      'htmlTag_langKey': 'reserved',
++      'htmlTag_setParams': 'reserved',
++      'http': 'reserved',
++      'icon': 'reserved',
++      'icon_image_ext_list': 'reserved',
++      'icon_link': 'reserved',
++      'iconCObject': 'reserved',
++      'ifEmpty': 'reserved',
++      'image': 'reserved',
++      'image_compression': 'reserved',
++      'image_effects': 'reserved',
++      'image_frames': 'reserved',
++      'imageLinkWrap': 'reserved',
++      'imagePath': 'reserved',
++      'images': 'reserved',
++      'imageWrapIfAny': 'reserved',
++      'imgList': 'reserved',
++      'imgMap': 'reserved',
++      'imgMapExtras': 'reserved',
++      'imgMax': 'reserved',
++      'imgNameNotRandom': 'reserved',
++      'imgNamePrefix': 'reserved',
++      'imgObjNum': 'reserved',
++      'imgParams': 'reserved',
++      'imgPath': 'reserved',
++      'imgStart': 'reserved',
++      'import': 'reserved',
++      'inc': 'reserved',
++      'includeCSS': 'reserved',
++      'includeLibrary': 'reserved',
++      'includeNotInMenu': 'reserved',
++      'incT3Lib_htmlmail': 'reserved',
++      'index': 'reserved',
++      'index_descrLgd': 'reserved',
++      'index_enable': 'reserved',
++      'index_externals': 'reserved',
++      'inlineStyle2TempFile': 'reserved',
++      'innerStdWrap': 'reserved',
++      'innerStdWrap_all': 'reserved',
++      'innerWrap': 'reserved',
++      'innerWrap2': 'reserved',
++      'input': 'reserved',
++      'inputLevels': 'reserved',
++      'insertClassesFromRTE': 'reserved',
++      'insertData': 'reserved',
++      'insertDmailerBoundaries': 'reserved',
++      'intensity': 'reserved',
++      'intTarget': 'reserved',
++      'intval': 'reserved',
++      'invert': 'reserved',
++      'IProcFunc': 'reserved',
++      'itemArrayProcFunc': 'reserved',
++      'itemH': 'reserved',
++      'items': 'reserved',
++      'itemsProcFunc': 'reserved',
++      'iterations': 'reserved',
++      'join': 'reserved',
++      'JSWindow': 'reserved',
++      'JSwindow_params': 'reserved',
++      'jumpurl': 'reserved',
++      'jumpUrl': 'reserved',
++      'jumpurl_enable': 'reserved',
++      'jumpurl_mailto_disable': 'reserved',
++      'jumpUrl_transferSession': 'reserved',
++      'keep': 'reserved',
++      'keepEntries': 'reserved',
++      'keepNonMatchedTags': 'reserved',
++      'key': 'reserved',
++      'label': 'reserved',
++      'labelStdWrap': 'reserved',
++      'labelWrap': 'reserved',
++      'lang': 'reserved',
++      'language': 'reserved',
++      'language_alt': 'reserved',
++      'languageField': 'reserved',
++      'layer_menu_id': 'reserved',
++      'layerStyle': 'reserved',
++      'left': 'reserved',
++      'leftIcons': 'reserved',
++      'leftImgACT': 'reserved',
++      'leftImgCUR': 'reserved',
++      'leftImgNO': 'reserved',
++      'leftjoin': 'reserved',
++      'leftOffset': 'reserved',
++      'levels': 'reserved',
++      'leveluid': 'reserved',
++      'limit': 'reserved',
++      'line': 'reserved',
++      'lineColor': 'reserved',
++      'lineThickness': 'reserved',
++      'linkPrefix': 'reserved',
++      'linkTitleToSelf': 'reserved',
++      'linkVars': 'reserved',
++      'linkWrap': 'reserved',
++      'listNum': 'reserved',
++      'listOnlyInSingleTableView': 'reserved',
++      'lm': 'reserved',
++      'locale_all': 'reserved',
++      'localNesting': 'reserved',
++      'locationData': 'reserved',
++      'lockFilePath': 'reserved',
++      'lockPosition': 'reserved',
++      'lockPosition_addSelf': 'reserved',
++      'lockPosition_adjust': 'reserved',
++      'lockToIP': 'reserved',
++      'longdescURL': 'reserved',
++      'lowColor': 'reserved',
++      'lower': 'reserved',
++      'LR': 'reserved',
++      'mailto': 'reserved',
++      'main': 'reserved',
++      'mainScript': 'reserved',
++      'makelinks': 'reserved',
++      'markerWrap': 'reserved',
++      'mask': 'reserved',
++      'max': 'reserved',
++      'maxAge': 'reserved',
++      'maxAgeDays': 'reserved',
++      'maxChars': 'reserved',
++      'maxH': 'reserved',
++      'maxHeight': 'reserved',
++      'maxItems': 'reserved',
++      'maxW': 'reserved',
++      'maxWidth': 'reserved',
++      'maxWInText': 'reserved',
++      'mayNotCreateEditShortcuts': 'reserved',
++      'menu_type': 'reserved',
++      'menuBackColor': 'reserved',
++      'menuHeight': 'reserved',
++      'menuName': 'reserved',
++      'menuOffset': 'reserved',
++      'menuWidth': 'reserved',
++      'message_page_is_being_generated': 'reserved',
++      'message_preview': 'reserved',
++      'meta': 'reserved',
++      'metaCharset': 'reserved',
++      'method': 'reserved',
++      'min': 'reserved',
++      'minH': 'reserved',
++      'minItems': 'reserved',
++      'minW': 'reserved',
++      'mode': 'reserved',
++      'moduleMenuCollapsable': 'reserved',
++      'MP_defaults': 'reserved',
++      'MP_disableTypolinkClosestMPvalue': 'reserved',
++      'MP_mapRootPoints': 'reserved',
++      'name': 'reserved',
++      'navFrameResizable': 'reserved',
++      'navFrameWidth': 'reserved',
++      'nesting': 'reserved',
++      'netprintApplicationLink': 'reserved',
++      'neverHideAtCopy': 'reserved',
++      'newPageWiz': 'reserved',
++      'newRecordFromTable': 'reserved',
++      'newWindow': 'reserved',
++      'newWizards': 'reserved',
++      'next': 'reserved',
++      'niceText': 'reserved',
++      'nicetext': 'reserved',
++      'no_cache': 'reserved',
++      'no_search': 'reserved',
++      'noAttrib': 'reserved',
++      'noBlur': 'reserved',
++      'noCache': 'reserved',
++      'noCols': 'reserved',
++      'noCreateRecordsLink': 'reserved',
++      'noLink': 'reserved',
++      'noLinkUnderline': 'reserved',
++      'noMatchingValue_label': 'reserved',
++      'noMenuMode': 'reserved',
++      'nonCachedSubst': 'reserved',
++      'nonTypoTagStdWrap': 'reserved',
++      'nonTypoTagUserFunc': 'reserved',
++      'nonWrappedTag': 'reserved',
++      'noOrderBy': 'reserved',
++      'noPageTitle': 'reserved',
++      'noRows': 'reserved',
++      'noScaleUp': 'reserved',
++      'noStretchAndMarginCells': 'reserved',
++      'noThumbsInEB': 'reserved',
++      'noThumbsInRTEimageSelect': 'reserved',
++      'notification_email_charset': 'reserved',
++      'notification_email_encoding': 'reserved',
++      'notification_email_urlmode': 'reserved',
++      'noTrimWrap': 'reserved',
++      'noValueInsert': 'reserved',
++      'obj': 'reserved',
++      'offset': 'reserved',
++      'offsetWrap': 'reserved',
++      'onlineWorkspaceInfo': 'reserved',
++      'onlyCurrentPid': 'reserved',
++      'opacity': 'reserved',
++      'orderBy': 'reserved',
++      'outerWrap': 'reserved',
++      'outline': 'reserved',
++      'outputLevels': 'reserved',
++      'override': 'reserved',
++      'overrideAttribs': 'reserved',
++      'overrideEdit': 'reserved',
++      'overrideId': 'reserved',
++      'overridePageModule': 'reserved',
++      'overrideWithExtension': 'reserved',
++      'pageFrameObj': 'reserved',
++      'pageGenScript': 'reserved',
++      'pageTitleFirst': 'reserved',
++      'parameter': 'reserved',
++      'params': 'reserved',
++      'parseFunc': 'reserved',
++      'parser': 'reserved',
++      'password': 'reserved',
++      'path': 'reserved',
++      'permissions': 'reserved',
++      'pid_list': 'reserved',
++      'pidInList': 'reserved',
++      'pixelSpaceFontSizeRef': 'reserved',
++      'plaintextLib': 'reserved',
++      'plainTextStdWrap': 'reserved',
++      'postCObject': 'reserved',
++      'postLineBlanks': 'reserved',
++      'postLineChar': 'reserved',
++      'postLineLen': 'reserved',
++      'postUserFunc': 'reserved',
++      'postUserFuncInt': 'reserved',
++      'preBlanks': 'reserved',
++      'preCObject': 'reserved',
++      'prefix': 'reserved',
++      'prefixComment': 'reserved',
++      'prefixLocalAnchors': 'reserved',
++      'prefixRelPathWith': 'reserved',
++      'preIfEmptyListNum': 'reserved',
++      'preLineBlanks': 'reserved',
++      'preLineChar': 'reserved',
++      'preLineLen': 'reserved',
++      'prepend': 'reserved',
++      'preserveEntities': 'reserved',
++      'preUserFunc': 'reserved',
++      'prev': 'reserved',
++      'previewBorder': 'reserved',
++      'prevnextToSection': 'reserved',
++      'printheader': 'reserved',
++      'prioriCalc': 'reserved',
++      'proc': 'reserved',
++      'processScript': 'reserved',
++      'properties': 'reserved',
++      'protect': 'reserved',
++      'protectLvar': 'reserved',
++      'publish_levels': 'reserved',
++      'QEisDefault': 'reserved',
++      'quality': 'reserved',
++      'radio': 'reserved',
++      'radioWrap': 'reserved',
++      'range': 'reserved',
++      'rawUrlEncode': 'reserved',
++      'recipient': 'reserved',
++      'recursive': 'reserved',
++      'recursiveDelete': 'reserved',
++      'redirect': 'reserved',
++      'redirectToURL': 'reserved',
++      'reduceColors': 'reserved',
++      'register': 'reserved',
++      'relativeToParentLayer': 'reserved',
++      'relativeToTriggerItem': 'reserved',
++      'relPathPrefix': 'reserved',
++      'remap': 'reserved',
++      'remapTag': 'reserved',
++      'removeBadHTML': 'reserved',
++      'removeDefaultJS': 'reserved',
++      'removeIfEquals': 'reserved',
++      'removeIfFalse': 'reserved',
++      'removeItems': 'reserved',
++      'removeObjectsOfDummy': 'reserved',
++      'removePrependedNumbers': 'reserved',
++      'removeTags': 'reserved',
++      'removeWrapping': 'reserved',
++      'renderCharset': 'reserved',
++      'renderWrap': 'reserved',
++      'reset': 'reserved',
++      'resources': 'reserved',
++      'resultObj': 'reserved',
++      'returnLast': 'reserved',
++      'returnUrl': 'reserved',
++      'rightImgACT': 'reserved',
++      'rightImgCUR': 'reserved',
++      'rightImgNO': 'reserved',
++      'rightjoin': 'reserved',
++      'rm': 'reserved',
++      'rmTagIfNoAttrib': 'reserved',
++      'RO_chBgColor': 'reserved',
++      'rotate': 'reserved',
++      'rows': 'reserved',
++      'rowSpace': 'reserved',
++      'RTEfullScreenWidth': 'reserved',
++      'rules': 'reserved',
++      'sample': 'reserved',
++      'saveClipboard': 'reserved',
++      'saveDocNew': 'reserved',
++      'secondRow': 'reserved',
++      'section': 'reserved',
++      'sectionIndex': 'reserved',
++      'select': 'reserved',
++      'select_key': 'reserved',
++      'selectFields': 'reserved',
++      'separator': 'reserved',
++      'set': 'reserved',
++      'setContentToCurrent': 'reserved',
++      'setCurrent': 'reserved',
++      'setfixed': 'reserved',
++      'setFixedHeight': 'reserved',
++      'setFixedWidth': 'reserved',
++      'setJS_mouseOver': 'reserved',
++      'setJS_openPic': 'reserved',
++      'setOnly': 'reserved',
++      'shadow': 'reserved',
++      'sharpen': 'reserved',
++      'shear': 'reserved',
++      'short': 'reserved',
++      'shortcut': 'reserved',
++      'shortcut_onEditId_dontSetPageTree': 'reserved',
++      'shortcut_onEditId_keepExistingExpanded': 'reserved',
++      'shortcutFrame': 'reserved',
++      'shortcutGroups': 'reserved',
++      'shortcutIcon': 'reserved',
++      'show': 'reserved',
++      'showAccessRestrictedPages': 'reserved',
++      'showActive': 'reserved',
++      'showClipControlPanelsDespiteOfCMlayers': 'reserved',
++      'showFirst': 'reserved',
++      'showHiddenPages': 'reserved',
++      'showHiddenRecords': 'reserved',
++      'showHistory': 'reserved',
++      'showPageIdWithTitle': 'reserved',
++      'showTagFreeClasses': 'reserved',
++      'simulateDate': 'reserved',
++      'simulateStaticDocuments': 'reserved',
++      'simulateStaticDocuments_addTitle': 'reserved',
++      'simulateStaticDocuments_dontRedirectPathInfoError': 'reserved',
++      'simulateStaticDocuments_noTypeIfNoTitle': 'reserved',
++      'simulateStaticDocuments_pEnc': 'reserved',
++      'simulateStaticDocuments_pEnc_onlyP': 'reserved',
++      'simulateUserGroup': 'reserved',
++      'singlePid': 'reserved',
++      'site_author': 'reserved',
++      'site_reserved': 'reserved',
++      'sitetitle': 'reserved',
++      'siteUrl': 'reserved',
++      'size': 'reserved',
++      'smallFormFields': 'reserved',
++      'solarize': 'reserved',
++      'sorting': 'reserved',
++      'source': 'reserved',
++      'space': 'reserved',
++      'spaceAfter': 'reserved',
++      'spaceBefore': 'reserved',
++      'spaceBelowAbove': 'reserved',
++      'spaceLeft': 'reserved',
++      'spaceRight': 'reserved',
++      'spacing': 'reserved',
++      'spamProtectEmailAddresses': 'reserved',
++      'spamProtectEmailAddresses_atSubst': 'reserved',
++      'spamProtectEmailAddresses_lastDotSubst': 'reserved',
++      'special': 'reserved',
++      'splitChar': 'reserved',
++      'splitRendering': 'reserved',
++      'src': 'reserved',
++      'startInTaskCenter': 'reserved',
++      'stayFolded': 'reserved',
++      'stdheader': 'reserved',
++      'stdWrap': 'reserved',
++      'stdWrap2': 'reserved',
++      'strftime': 'reserved',
++      'stripHtml': 'reserved',
++      'styles': 'reserved',
++      'stylesheet': 'reserved',
++      'submenuObjSuffixes': 'reserved',
++      'subMenuOffset': 'reserved',
++      'submit': 'reserved',
++      'subst_elementUid': 'reserved',
++      'substMarksSeparately': 'reserved',
++      'substring': 'reserved',
++      'swirl': 'reserved',
++      'sword': 'reserved',
++      'sword_noMixedCase': 'reserved',
++      'SWORD_PARAMS': 'reserved',
++      'sword_standAlone': 'reserved',
++      'sys_language_mode': 'reserved',
++      'sys_language_overlay': 'reserved',
++      'sys_language_softMergeIfNotBlank': 'reserved',
++      'sys_language_uid': 'reserved',
++      'table': 'reserved',
++      'tableCellColor': 'reserved',
++      'tableParams': 'reserved',
++      'tables': 'reserved',
++      'tableStdWrap': 'reserved',
++      'tableStyle': 'reserved',
++      'tableWidth': 'reserved',
++      'tags': 'reserved',
++      'target': 'reserved',
++      'TDparams': 'reserved',
++      'templateContent': 'reserved',
++      'templateFile': 'reserved',
++      'text': 'reserved',
++      'textarea': 'reserved',
++      'textMargin': 'reserved',
++      'textMargin_outOfText': 'reserved',
++      'textMaxLength': 'reserved',
++      'textObjNum': 'reserved',
++      'textPos': 'reserved',
++      'textStyle': 'reserved',
++      'thickness': 'reserved',
++      'thumbnailsByDefault': 'reserved',
++      'tile': 'reserved',
++      'time_stdWrap': 'reserved',
++      'tipafriendLib': 'reserved',
++      'title': 'reserved',
++      'titleLen': 'reserved',
++      'titleTagFunction': 'reserved',
++      'titleText': 'reserved',
++      'tm': 'reserved',
++      'token': 'reserved',
++      'topOffset': 'reserved',
++      'totalWidth': 'reserved',
++      'transparentBackground': 'reserved',
++      'transparentColor': 'reserved',
++      'trim': 'reserved',
++      'tsdebug_tree': 'reserved',
++      'type': 'reserved',
++      'typeNum': 'reserved',
++      'types': 'reserved',
++      'typolinkCheckRootline': 'reserved',
++      'uidInList': 'reserved',
++      'unset': 'reserved',
++      'uploadFieldsInTopOfEB': 'reserved',
++      'uploads': 'reserved',
++      'upper': 'reserved',
++      'useCacheHash': 'reserved',
++      'useLargestItemX': 'reserved',
++      'useLargestItemY': 'reserved',
++      'user': 'reserved',
++      'userdefined': 'reserved',
++      'userfunction': 'reserved',
++      'userid': 'reserved',
++      'userIdColumn': 'reserved',
++      'USERNAME_substToken': 'reserved',
++      'userProc': 'reserved',
++      'value': 'reserved',
++      'valueArray': 'reserved',
++      'wave': 'reserved',
++      'where': 'reserved',
++      'width': 'reserved',
++      'wiz': 'reserved',
++      'wordSpacing': 'reserved',
++      'workArea': 'reserved',
++      'wrap': 'reserved',
++      'wrap1': 'reserved',
++      'wrap2': 'reserved',
++      'wrap3': 'reserved',
++      'wrapAfterTags': 'reserved',
++      'wrapAlign': 'reserved',
++      'wrapFieldName': 'reserved',
++      'wrapItemAndSub': 'reserved',
++      'wrapNonWrappedLines': 'reserved',
++      'wraps': 'reserved',
++      'xhtml_cleaning': 'reserved',
++      'xmlprologue': 'reserved',
++      'xPosOffset': 'reserved',
++      'yPosOffset': 'reserved',
++
++      'admPanel': 'keyword2',
++      'alt_print': 'keyword2',
++      'auth': 'keyword2',
++      'browser': 'keyword2',
++      'cache': 'keyword2',
++      'CHECK': 'keyword2',
++      'cObj': 'keyword2',
++      'cObject': 'keyword2',
++      'COMMENT': 'keyword2',
++      'config': 'keyword2',
++      'content': 'keyword2',
++      'copy': 'keyword2',
++      'CSS_inlineStyle': 'keyword2',
++      'cut': 'keyword2',
++      'dataArray': 'keyword2',
++      'dayofmonth': 'keyword2',
++      'dayofweek': 'keyword2',
++      'db_list': 'keyword2',
++      'device': 'keyword2',
++      'dynCSS': 'keyword2',
++      'edit': 'keyword2',
++      'edit_access': 'keyword2',
++      'edit_pageheader': 'keyword2',
++      'folder': 'keyword2',
++      'folderTree': 'keyword2',
++      'foldoutMenu': 'keyword2',
++      'Functions': 'keyword2',
++      'gmenu_foldout': 'keyword2',
++      'gmenu_layers': 'keyword2',
++      'hostname': 'keyword2',
++      'hour': 'keyword2',
++      'imgList': 'keyword2',
++      'imgResource': 'keyword2',
++      'imgText': 'keyword2',
++      'info': 'keyword2',
++      'IP': 'keyword2',
++      'jsmenu': 'keyword2',
++      'JSwindow': 'keyword2',
++      'LABEL': 'keyword2',
++      'layout': 'keyword2',
++      'lib': 'keyword2',
++      'loginUser': 'keyword2',
++      'marks': 'keyword2',
++      'minute': 'keyword2',
++      'mod': 'keyword2',
++      'module': 'keyword2',
++      'month': 'keyword2',
++      'move_wizard': 'keyword2',
++      'new': 'keyword2',
++      'new_wizard': 'keyword2',
++      'noResultObj': 'keyword2',
++      'numRows': 'keyword2',
++      'options': 'keyword2',
++      'page': 'keyword2',
++      'pageTree': 'keyword2',
++      'paste': 'keyword2',
++      'perms': 'keyword2',
++      'PIDinRootline': 'keyword2',
++      'PIDupinRootline': 'keyword2',
++      'plugin': 'keyword2',
++      'postform': 'keyword2',
++      'postform_newThread': 'keyword2',
++      'preview': 'keyword2',
++      'publish': 'keyword2',
++      'RADIO': 'keyword2',
++      'renderObj': 'keyword2',
++      'REQ': 'keyword2',
++      'RTE': 'keyword2',
++      'RTE_compliant': 'keyword2',
++      'select': 'keyword2',
++      'setup': 'keyword2',
++      'split': 'keyword2',
++      'stat': 'keyword2',
++      'stat_apache': 'keyword2',
++      'stat_apache_logfile': 'keyword2',
++      'stat_apache_noHost': 'keyword2',
++      'stat_apache_notExtended': 'keyword2',
++      'stat_apache_pagenames': 'keyword2',
++      'stat_excludeBEuserHits': 'keyword2',
++      'stat_excludeIPList': 'keyword2',
++      'stat_mysql': 'keyword2',
++      'stat_titleLen': 'keyword2',
++      'stat_typeNumList': 'keyword2',
++      'stdWrap': 'keyword2',
++      'subparts': 'keyword2',
++      'system': 'keyword2',
++      'temp': 'keyword2',
++      'template': 'keyword2',
++      'treeLevel': 'keyword2',
++      'tsdebug': 'keyword2',
++      'typolink': 'keyword2',
++      'url': 'keyword2',
++      'useragent': 'keyword2',
++      'userFunc': 'keyword2',
++      'version': 'keyword2',
++      'view': 'keyword2',
++      'workOnSubpart': 'keyword2',
++
++      'ACT': 'keyword3',
++      'ACTIFSUB': 'keyword3',
++      'ACTIFSUBRO': 'keyword',
++      'ACTRO': 'keyword3',
++      'all': 'keyword3',
++      'arrowACT': 'keyword3',
++      'arrowNO': 'keyword3',
++      'ascii': 'keyword3',
++      'atLeast': 'keyword3',
++      'atMost': 'keyword3',
++      'BE': 'keyword3',
++      'be_groups': 'keyword3',
++      'be_users': 'keyword3',
++      'BOX': 'keyword3',
++      'browse': 'keyword3',
++      'bullets': 'keyword3',
++      'CUR': 'keyword3',
++      'CURIFSUB': 'keyword3',
++      'CURIFSUBRO': 'keyword3',
++      'CURRO': 'keyword3',
++      'default': 'keyword3',
++      'description': 'keyword3',
++      'directory': 'keyword3',
++      'directReturn': 'keyword3',
++      'div': 'keyword3',
++      'else': 'keyword3',
++      'email': 'keyword3',
++      'end': 'keyword3',
++      'equals': 'keyword3',
++      'external': 'keyword3',
++      'false': 'keyword3',
++      'FE': 'keyword3',
++      'fe_groups': 'keyword3',
++      'fe_users': 'keyword3',
++      'feadmin': 'keyword3',
++      'header': 'keyword3',
++      'html': 'keyword3',
++      'id': 'keyword3',
++      'if': 'keyword3',
++      'ifEmpty': 'keyword3',
++      'IFSUB': 'keyword3',
++      'IFSUBRO': 'keyword3',
++      'image': 'keyword3',
++      'inBranch': 'keyword3',
++      'isFalse': 'keyword3',
++      'isGreaterThan': 'keyword3',
++      'isInList': 'keyword3',
++      'isLessThan': 'keyword3',
++      'isPositive': 'keyword3',
++      'isTrue': 'keyword3',
++      'keyword3': 'keyword3',
++      'language': 'keyword3',
++      'leveltitle': 'keyword3',
++      'list': 'keyword3',
++      'login': 'keyword3',
++      'mailform': 'keyword3',
++      'media': 'keyword3',
++      'menu': 'keyword3',
++      'mod': 'keyword3',
++      'multimedia': 'keyword3',
++      'negate': 'keyword3',
++      'NEW': 'keyword3',
++      'NO': 'keyword3',
++      'none': 'keyword3',
++      'pages': 'keyword3',
++      'pages_language_overlay': 'keyword3',
++      'parseFunc_RTE': 'keyword3',
++      'pid': 'keyword3',
++      'required': 'keyword3',
++      'RO': 'keyword3',
++      'rootline': 'keyword3',
++      'script': 'keyword3',
++      'search': 'keyword3',
++      'shortcut': 'keyword3',
++      'sitemap': 'keyword3',
++      'SPC': 'keyword3',
++      'splash': 'keyword3',
++      'sys_dmail': 'keyword3',
++      'sys_domain': 'keyword3',
++      'sys_filemounts': 'keyword3',
++      'sys_note': 'keyword3',
++      'sys_template': 'keyword3',
++      'tabel': 'keyword3',
++      'text': 'keyword3',
++      'textpic': 'keyword3',
++      'this': 'keyword3',
++      'top': 'keyword3',
++      'true': 'keyword3',
++      'tt_address': 'keyword3',
++      'tt_board': 'keyword3',
++      'tt_board_list': 'keyword3',
++      'tt_board_tree': 'keyword3',
++      'tt_calender': 'keyword3',
++      'tt_content': 'keyword3',
++      'tt_guest': 'keyword3',
++      'tt_news': 'keyword3',
++      'tt_poll': 'keyword3',
++      'tt_products': 'keyword3',
++      'tt_rating': 'keyword3',
++      'twice': 'keyword3',
++      'tx_automaketemplate_pi1': 'keyword3',
++      'tx_belog_webinfo': 'keyword3',
++      'tx_browserpagetitle': 'keyword3',
++      'tx_browserpagetitle_browser_title': 'keyword3',
++      'tx_chcforum_pi1': 'keyword3',
++      'tx_cms_layout': 'keyword3',
++      'tx_cms_webinfo_hits': 'keyword3',
++      'tx_cms_webinfo_lang': 'keyword3',
++      'tx_cms_webinfo_page': 'keyword3',
++      'tx_cssstyledcontent_pi1': 'keyword3',
++      'tx_dephpot_pi1': 'keyword3',
++      'tx_extkey': 'keyword3',
++      'tx_extkey_controllers': 'keyword3',
++      'tx_extkey_login': 'keyword3',
++      'tx_extrapagecmoptions': 'keyword3',
++      'tx_funcwizards_webfunc': 'keyword3',
++      'tx_gooffotoboek_pi1': 'keyword3',
++      'tx_impexp': 'keyword3',
++      'tx_impexp_clickmenu': 'keyword3',
++      'tx_impexp_modfunc1': 'keyword3',
++      'tx_indexed_search_extparse': 'keyword3',
++      'tx_indexedsearch': 'keyword3',
++      'tx_indexedsearch_indexer': 'keyword3',
++      'tx_indexedsearch_lexer': 'keyword3',
++      'tx_indexedsearch_modfunc1': 'keyword3',
++      'tx_indexedsearch_modfunc2': 'keyword3',
++      'tx_indexedsearch_pihook': 'keyword3',
++      'tx_infopagetsconfig_webinfo': 'keyword3',
++      'tx_install': 'keyword3',
++      'tx_lzgallery_pi1': 'keyword3',
++      'tx_mhajaxsearch_pi1': 'keyword3',
++      'tx_mhajaxsearch_q': 'keyword3',
++      'tx_mhajaxsearch_result': 'keyword3',
++      'tx_mhajaxsearch_search': 'keyword3',
++      'tx_newloginbox_pi1': 'keyword3',
++      'tx_newloginbox_pi3': 'keyword3',
++      'tx_open_printlink': 'keyword3',
++      'tx_pdfgenerator': 'keyword3',
++      'tx_realurl_advanced': 'keyword3',
++      'tx_realurl_enable': 'keyword3',
++      'tx_realurl_pathsegment': 'keyword3',
++      'tx_realurl_pi1': 'keyword3',
++      'tx_rlmptmplselector': 'keyword3',
++      'tx_rlmptmplselector_pi1': 'keyword3',
++      'tx_sochat_pi1': 'keyword3',
++      'tx_srfeuserregister_pi1': 'keyword3',
++      'tx_sv_auth': 'keyword3',
++      'tx_sv_authbase': 'keyword3',
++      'tx_sysaction': 'keyword3',
++      'tx_templavoila_pi1': 'keyword3',
++      'tx_terdoc_pi1': 'keyword3',
++      'tx_ttnews': 'keyword3',
++      'tx_ttnews_catmenu': 'keyword3',
++      'tx_ttnews_itemsProcFunc': 'keyword3',
++      'tx_ttnews_tcemain': 'keyword3',
++      'tx_ttnews_treeview=': 'keyword3',
++      'tx_ttproducts_pi1': 'keyword3',
++      'tx_veguestbook_pi1': 'keyword3',
++      'tx_version_cm1': 'keyword3',
++      'tx_vjchat_chat': 'keyword3',
++      'tx_vjchat_pi1': 'keyword3',
++      'tx_wizardcrpages_webfunc_2': 'keyword3',
++      'tx_wizardsortpages_webfunc_2': 'keyword3',
++      'tx_wwwebstats4u_pi1': 'keyword3',
++      'uid': 'keyword3',
++      'uniqueGlobal': 'keyword3',
++      'uniqueLocal': 'keyword3',
++      'unsetEmpty': 'keyword3',
++      'updated': 'keyword3',
++      'uploads': 'keyword3',
++      'us': 'keyword3',
++      'user_task': 'keyword3',
++      'USERDEF1': 'keyword3',
++      'USERDEF1RO': 'keyword3',
++      'USERDEF2': 'keyword3',
++      'USERDEF2RO': 'keyword3',
++      'usergroup': 'keyword3',
++      'USR': 'keyword3',
++      'USRRO': 'keyword3',
++      'web_func': 'keyword3',
++      'web_info': 'keyword3',
++      'web_layout': 'keyword3',
++      'web_list': 'keyword3',
++      'web_ts': 'keyword',
++      'xhtml_strict': 'keyword3',
++      'xhtml_trans': 'keyword3',
++      'XY': 'keyword3',
++      'ypMenu': 'keyword3'
++}
++
++var tokenizeTypoScript = function() {
++
++      // Some helper regexp matchers.
++      var isOperatorChar = matcher(/[\+\-\*\&\%\/=<>!\?]/);
++      var isDigit = matcher(/[0-9]/);
++      var isHexDigit = matcher(/[0-9A-Fa-f]/);
++      var isWordChar = matcher(/[\w\$_]/);
++
++      function isWhiteSpace(ch) {
++              // Unfortunately, IE's regexp matcher thinks non-breaking spaces
++              // aren't whitespace. Also, in our scheme newlines are no
++              // whitespace (they are another special case).
++              return ch != "\n" && (ch == nbsp || /\s/.test(ch));
++      }
++
++      // This function produces a MochiKit-style iterator that tokenizes
++      // the output of the given stringstream (see stringstream.js).
++      // Tokens are objects with a type, style, and value property. The
++      // value contains the textual content of the token. Because this may
++      // include trailing whitespace (for efficiency reasons), some
++      // tokens, such a variable names, also have a name property
++      // containing their actual textual value.
++      return function(source) {
++              // Produce a value to return. Automatically skips and includes any
++              // whitespace. The base argument is prepended to the value
++              // property and assigned to the name property -- this is used when
++              // the caller has already extracted the text from the stream
++              // himself.
++              function result(type, style, base) {
++                      // nextWhile(isWhiteSpace); - comment thats line because needed for autocomplete
++                      var value = {
++                              type: type,
++                              style: style,
++                              value: (base ? base + source.get() : source.get())
++                      };
++                      if (base) {
++                              value.name = base;
++                      }
++                      return value;
++              }
++
++              // Advance the text stream over characters for which test returns
++              // true. (The characters that are 'consumed' like this can later
++              // be retrieved by calling source.get()).
++              function nextWhile(test) {
++                      var next;
++                      while ((next = source.peek()) && test(next)) {
++                              source.next();
++                      }
++              }
++
++              // Advance the stream until the given character (not preceded by a
++              // backslash) is encountered (or a newline is found).
++              function nextUntilUnescaped(end) {
++                      var escaped = false;
++                      var next;
++                      while ((next = source.peek()) && next != "\n") {
++                              source.next();
++                              if (next == end && !escaped) {
++                                      break;
++                              }
++                              escaped = next == "\\";
++                      }
++              }
++
++              function readHexNumber() {
++                      source.next();
++                      // skip the 'x'
++                      nextWhile(isHexDigit);
++                      return result("number", "atom");
++              }
++
++              function readNumber() {
++                      nextWhile(isDigit);
++                      if (source.peek() == ".") {
++                              source.next();
++                              nextWhile(isDigit);
++                      }
++
++                      if (source.peek() == "e" || source.peek() == "E") {
++                              source.next();
++                              if (source.peek() == "-") {
++                                      source.next();
++                              }
++                              nextWhile(isDigit);
++                      }
++                      return result("number", "atom");
++              }
++
++              // Read a word, look it up in keywords. If not found, it is a
++              // variable, otherwise it is a keyword of the type found.
++              function readWord() {
++                      nextWhile(isWordChar);
++                      var word = source.get();
++                      var known = typoscriptWords.hasOwnProperty(word) && {
++                              type: 'keyword',
++                              style: typoscriptWords[word]
++                      };
++                      return known ?
++                              result(known.type, known.style, word) :
++                              result("variable", "other", word);
++              }
++
++              function readRegexp() {
++                      nextUntilUnescaped("/");
++                      nextWhile(matcher(/[gi]/));
++                      return result("regexp", "string");
++              }
++
++              // Mutli-line comments are tricky. We want to return the newlines
++              // embedded in them as regular newline tokens, and then continue
++              // returning a comment token for every line of the comment. So
++              // some state has to be saved (inComment) to indicate whether we
++              // are inside a /* */ sequence.
++              function readMultilineComment(start) {
++                      this.inComment = true;
++                      var maybeEnd = (start == "*");
++                      while (true) {
++                              var next = source.peek();
++                              if (next == "\n") {
++                                      break;
++                              }
++                              source.next();
++                              if (next == "/" && maybeEnd) {
++                                      this.inComment = false;
++                                      break;
++                              }
++                              maybeEnd = (next == "*");
++                      }
++
++                      return result("comment", "ts-comment");
++              }
++
++              // Fetch the next token. Dispatches on first character in the
++              // stream, or first two characters when the first is a slash. The
++              // || things are a silly trick to keep simple cases on a single
++              // line.
++              function next() {
++                      var token = null;
++                      var ch = source.next();
++                      if (ch == "\n") {
++                              token = {
++                                      type: "newline",
++                                      style: "whitespace",
++                                      value: source.get()
++                              };
++
++                      } else if (this.inComment) {
++                              token = readMultilineComment.call(this, ch);
++
++                      } else if (this.inValue) {
++                              token = nextUntilUnescaped(null) || {
++                                      type: "value",
++                                      style: "ts-value",
++                                      value: source.get()
++                              };
++                              this.inValue = false;
++
++                      } else if (isWhiteSpace(ch)) {
++                              token = nextWhile(isWhiteSpace) || result("whitespace", "whitespace");
++
++                      } else if (ch == "\"" || ch == "'") {
++                              token = nextUntilUnescaped(ch) || result("string", "string");
++
++                      } else if (ch == "<"
++                        || ch == ">"
++                        || ch == "=") {
++                              this.inValue = true;
++                              token = result(ch, "ts-operator");
++
++                      } else if (ch == "[") {
++                              token = nextUntilUnescaped("]") || result("condition", "ts-condition");
++
++                      // with punctuation, the type of the token is the symbol itself
++                      } else if (/[\[\]\(\),;\:\.\<\>\=]/.test(ch)) {
++                              token = result(ch, "ts-operator");
++
++                      } else if (ch == "{") {
++                              token = result(ch, "ts-operator curly-bracket-open");
++
++                      } else if (ch == "}") {
++                              token = result(ch, "ts-operator curly-bracket-close");
++
++                      } else if (ch == "0" && (source.peek() == "x" || source.peek() == "X")) {
++                              token = readHexNumber();
++
++                      } else if (isDigit(ch)) {
++                              token = readNumber();
++
++                      } else if (ch == "/") {
++                              next = source.peek();
++
++                              if (next == "*") {
++                                      token = readMultilineComment.call(this, ch);
++
++                              } else if (next == "/") {
++                                      token = nextUntilUnescaped(null) || result("comment", "ts-comment");
++
++                              } else if (this.regexp) {
++                                      token = readRegexp();
++
++                              } else {
++                                      token = nextWhile(isOperatorChar) || result("operator", "ts-operator");
++                              }
++
++                      } else if (ch == "#") {
++                              token = nextUntilUnescaped(null) || result("comment", "ts-comment");
++
++                      } else if (isOperatorChar(ch)) {
++                              token = nextWhile(isOperatorChar) || result("operator", "ts-operator");
++
++                      } else {
++                              token = readWord();
++                      }
++
++                      // JavaScript's syntax rules for when a slash might be the start
++                      // of a regexp and when it is just a division operator are kind
++                      // of non-obvious. This decides, based on the current token,
++                      // whether the next token could be a regular expression.
++                      if (token.style != "whitespace" && token != "comment") {
++                              this.regexp = token.type == "operator" || token.type == "keyword c" || token.type.match(/[\[{}\(,;:]/);
++                      }
++                      return token;
++              }
++
++              // Wrap it in an iterator. The state (regexp and inComment) is
++              // exposed because a parser will need to save it when making a
++              // copy of its state.
++              return {
++                      next: next,
++                      regexp: true,
++                      inComment: false,
++                      inValue: false
++              };
++      }
++} ();
+\ No newline at end of file
+diff -uN /var/www/dev/cm055/CodeMirror-0.55/js/util.js ./util.js
+--- /var/www/dev/cm055/CodeMirror-0.55/js/util.js      2008-03-30 14:05:26.000000000 +0200
++++ ./util.js  2008-04-02 22:29:50.000000000 +0200
+@@ -138,3 +138,10 @@
+   else
+     node.attachEvent("on" + type, wrapHandler);
+ }
++
++
++
++// fix prototype issue: ajax request do not respect charset of the page and screw up code
++if (document.characterSet != "UTF-8") {
++      encodeURIComponent = escape;
++}
diff --git a/typo3/sysext/t3editor/jslib/codemirror/select.js b/typo3/sysext/t3editor/jslib/codemirror/select.js
new file mode 100644 (file)
index 0000000..f9019fc
--- /dev/null
@@ -0,0 +1,501 @@
+/* Functionality for finding, storing, and re-storing selections
+ *
+ * This does not provide a generic API, just the minimal functionality
+ * required by the CodeMirror system.
+ */
+
+// Namespace object.
+var select = {};
+
+(function() {
+  var ie_selection = document.selection && document.selection.createRangeCollection;
+
+  // Find the 'top-level' (defined as 'a direct child of the node
+  // passed as the top argument') node that the given node is
+  // contained in. Return null if the given node is not inside the top
+  // node.
+  function topLevelNodeAt(node, top) {
+    while (node && node.parentNode != top)
+      node = node.parentNode;
+    return node;
+  }
+
+  // Find the top-level node that contains the node before this one.
+  function topLevelNodeBefore(node, top) {
+    while (!node.previousSibling && node.parentNode != top)
+      node = node.parentNode;
+    return topLevelNodeAt(node.previousSibling, top);
+  }
+
+  // Most functions are defined in two ways, one for the IE selection
+  // model, one for the W3C one.
+  if (ie_selection) {
+    // Store the current selection in such a way that it can be
+    // restored after we manipulated the DOM tree. For IE, we store
+    // pixel coordinates.
+    select.markSelection = function (win) {
+      var selection = win.document.selection;
+      var start = selection.createRange(), end = start.duplicate();
+      var bookmark = start.getBookmark();
+      start.collapse(true);
+      end.collapse(false);
+
+      var body = win.document.body;
+      // And we better hope no fool gave this window a padding or a
+      // margin, or all these computations will be in vain.
+      return {start: {x: start.boundingLeft + body.scrollLeft - 1,
+                      y: start.boundingTop + body.scrollTop},
+              end: {x: end.boundingLeft + body.scrollLeft - 1,
+                    y: end.boundingTop + body.scrollTop},
+              window: win,
+              bookmark: bookmark};
+    };
+
+    // Restore a stored selection.
+    select.selectMarked = function(sel) {
+      if (!sel)
+        return;
+      var range1 = sel.window.document.body.createTextRange(), range2 = range1.duplicate();
+      var done = false;
+      if (sel.start.y >= 0 && sel.end.y < sel.window.document.body.clientHeight) {
+        // This can fail for various hard-to-handle reasons, so we
+        // fall back to moveToBookmark when it throws.
+        try {
+          range1.moveToPoint(sel.start.x, sel.start.y);
+          range2.moveToPoint(sel.end.x, sel.end.y);
+          range1.setEndPoint("EndToStart", range2);
+          done = true;
+        } catch(e) {}
+      }
+      if (!done) {
+        range1.moveToBookmark(sel.bookmark);
+      }
+      range1.select();
+    };
+
+    // Get the top-level node that one end of the cursor is inside or
+    // after. Note that this returns false for 'no cursor', and null
+    // for 'start of document'.
+    select.selectionTopNode = function(container, start) {
+      var selection = container.ownerDocument.selection;
+      if (!selection) return false;
+
+      var range = selection.createRange();
+      range.collapse(start);
+      var around = range.parentElement();
+      if (around && isAncestor(container, around)) {
+        // Only use this node if the selection is not at its start.
+        var range2 = range.duplicate();
+        range2.moveToElementText(around);
+        if (range.compareEndPoints("StartToStart", range2) == -1)
+          return topLevelNodeAt(around, container);
+      }
+      // Fall-back hack
+      range.pasteHTML("<span id='xxx-temp-xxx'></span>");
+      var temp = container.ownerDocument.getElementById("xxx-temp-xxx");
+      var result = topLevelNodeBefore(temp, container);
+      removeElement(temp);
+      return result;
+    };
+
+    // Not needed in IE model -- see W3C model.
+    select.replaceSelection = function(){};
+
+    // Place the cursor after this.start. This is only useful when
+    // manually moving the cursor instead of restoring it to its old
+    // position.
+    select.focusAfterNode = function(node, container) {
+      var range = container.ownerDocument.body.createTextRange();
+      range.moveToElementText(node || container);
+      range.collapse(!node);
+      range.select();
+    };
+
+    function insertAtCursor(window, html) {
+      var selection = window.document.selection;
+      if (selection) {
+        var range = selection.createRange();
+        range.pasteHTML(html);
+        range.collapse(false);
+        range.select();
+      }
+    }
+       
+       // Insert a custom string at current cursor position (added for t3editor)
+       select.insertTextAtCursor = function(window, text) {
+               var selection = window.document.selection;
+               if (selection) {
+                       var range = selection.createRange();
+                       range.pasteHTML(text);
+                       range.collapse(false);
+                       range.select();
+               }
+       };
+
+    // Used to normalize the effect of the enter key, since browsers
+    // do widely different things when pressing enter in designMode.
+    select.insertNewlineAtCursor = function(window) {
+      insertAtCursor(window, "<br/>");
+    };
+
+    // Get the BR node at the start of the line on which the cursor
+    // currently is, and the offset into the line. Returns null as
+    // node if cursor is on first line.
+    select.cursorPos = function(container, start) {
+      var selection = container.ownerDocument.selection;
+      if (!selection) return null;
+
+      var topNode = select.selectionTopNode(container, start);
+      while (topNode && topNode.nodeName != "BR")
+        topNode = topNode.previousSibling;
+
+      var range = selection.createRange(), range2 = range.duplicate();
+      range.collapse(start);
+      if (topNode) {
+        range2.moveToElementText(topNode);
+        range2.collapse(false);
+      }
+      else {
+        range2.moveToElementText(container);
+        range2.collapse(true);
+      }
+      range.setEndPoint("StartToStart", range2);
+
+      return {node: topNode, offset: range.text.length};
+    };
+
+    select.setCursorPos = function(container, from, to) {
+      function rangeAt(pos) {
+        var range = container.ownerDocument.body.createTextRange();
+        if (!pos.node) {
+          range.moveToElementText(container);
+          range.collapse(true);
+        }
+        else {
+          range.moveToElementText(pos.node);
+          range.collapse(false);
+        }
+        range.move("character", pos.offset);
+        return range;
+      }
+
+      var range = rangeAt(from);
+      if (to && to != from)
+        range.setEndPoint("EndToEnd", rangeAt(to));
+      range.select();
+    }
+
+    // Make sure the cursor is visible.
+    select.scrollToCursor = function(container) {
+      var selection = container.ownerDocument.selection;
+      if (!selection) return null;
+      selection.createRange().scrollIntoView();
+    };
+  }
+  // W3C model
+  else {
+    // This is used to fix an issue with getting the scroll position
+    // in Opera.
+    var opera_scroll = !window.scrollX && !window.scrollY;
+
+    // Store start and end nodes, and offsets within these, and refer
+    // back to the selection object from those nodes, so that this
+    // object can be updated when the nodes are replaced before the
+    // selection is restored.
+    select.markSelection = function (win) {
+      var selection = win.getSelection();
+      if (!selection || selection.rangeCount == 0)
+        return null;
+      var range = selection.getRangeAt(0);
+
+      var result = {start: {node: range.startContainer, offset: range.startOffset},
+                    end: {node: range.endContainer, offset: range.endOffset},
+                    window: win,
+                    scrollX: opera_scroll && win.document.body.scrollLeft,
+                    scrollY: opera_scroll && win.document.body.scrollTop};
+
+      // We want the nodes right at the cursor, not one of their
+      // ancestors with a suitable offset. This goes down the DOM tree
+      // until a 'leaf' is reached (or is it *up* the DOM tree?).
+      function normalize(point){
+        while (point.node.nodeType != 3 && point.node.nodeName != "BR") {
+          var newNode = point.node.childNodes[point.offset] || point.node.nextSibling;
+          point.offset = 0;
+          while (!newNode && point.node.parentNode) {
+            point.node = point.node.parentNode;
+            newNode = point.node.nextSibling;
+          }
+          point.node = newNode;
+          if (!newNode)
+            break;
+        }
+      }
+
+      normalize(result.start);
+      normalize(result.end);
+      // Make the links back to the selection object (see
+      // replaceSelection).
+      if (result.start.node)
+        result.start.node.selectStart = result.start;
+      if (result.end.node)
+        result.end.node.selectEnd = result.end;
+
+      return result;
+    };
+
+    // Helper for selecting a range object.
+    function selectRange(range, window) {
+      var selection = window.getSelection();
+      selection.removeAllRanges();
+      selection.addRange(range);
+    }
+    function selectionRange(window) {
+      var selection = window.getSelection();
+      if (!selection || selection.rangeCount == 0)
+        return false;
+      else
+        return selection.getRangeAt(0);
+    }
+
+    // Finding the top-level node at the cursor in the W3C is, as you
+    // can see, quite an involved process.
+    select.selectionTopNode = function(container, start) {
+      var range = selectionRange(container.ownerDocument.defaultView);
+      if (!range) return false;
+
+      var node = start ? range.startContainer : range.endContainer;
+      var offset = start ? range.startOffset : range.endOffset;
+
+      // For text nodes, we look at the node itself if the cursor is
+      // inside, or at the node before it if the cursor is at the
+      // start.
+      if (node.nodeType == 3){
+        if (offset > 0)
+          return topLevelNodeAt(node, container);
+        else
+          return topLevelNodeBefore(node, container);
+      }
+      // Occasionally, browsers will return the HTML node as
+      // selection. If the offset is 0, we take the start of the frame
+      // ('after null'), otherwise, we take the last node.
+      else if (node.nodeName == "HTML") {
+        return (offset == 1 ? null : container.lastChild);
+      }
+      // If the given node is our 'container', we just look up the
+      // correct node by using the offset.
+      else if (node == container) {
+        return (offset == 0) ? null : node.childNodes[offset - 1];
+      }
+      // In any other case, we have a regular node. If the cursor is
+      // at the end of the node, we use the node itself, if it is at
+      // the start, we use the node before it, and in any other
+      // case, we look up the child before the cursor and use that.
+      else {
+        if (offset == node.childNodes.length)
+          return topLevelNodeAt(node, container);
+        else if (offset == 0)
+          return topLevelNodeBefore(node, container);
+        else
+          return topLevelNodeAt(node.childNodes[offset - 1], container);
+      }
+    };
+
+    select.selectMarked = function (sel) {
+      if (!sel)
+        return;
+      var win = sel.window;
+      var range = win.document.createRange();
+
+      function setPoint(point, which) {
+        if (point.node) {
+          // Remove the link back to the selection.
+          delete point.node["select" + which];
+          // Some magic to generalize the setting of the start and end
+          // of a range.
+          if (point.offset == 0)
+            range["set" + which + "Before"](point.node);
+          else
+            range["set" + which](point.node, point.offset);
+        }
+        else {
+          range.setStartAfter(win.document.body.lastChild || win.document.body);
+        }
+      }
+
+      // Have to restore the scroll position of the frame in Opera.
+      if (opera_scroll){
+        sel.window.document.body.scrollLeft = sel.scrollX;
+        sel.window.document.body.scrollTop = sel.scrollY;
+      }
+      setPoint(sel.end, "End");
+      setPoint(sel.start, "Start");
+      selectRange(range, win);
+    };
+
+    // This is called by the code in codemirror.js whenever it is
+    // replacing a part of the DOM tree. The function sees whether the
+    // given oldNode is part of the current selection, and updates
+    // this selection if it is. Because nodes are often only partially
+    // replaced, the length of the part that gets replaced has to be
+    // taken into account -- the selection might stay in the oldNode
+    // if the newNode is smaller than the selection's offset. The
+    // offset argument is needed in case the selection does move to
+    // the new object, and the given length is not the whole length of
+    // the new node (part of it might have been used to replace
+    // another node).
+    select.replaceSelection = function(oldNode, newNode, length, offset) {
+      function replace(which) {
+        var selObj = oldNode["select" + which];
+        if (selObj) {
+          if (selObj.offset > length) {
+            selObj.offset -= length;
+          }
+          else {
+            newNode["select" + which] = selObj;
+            delete oldNode["select" + which];
+            selObj.node = newNode;
+            selObj.offset += (offset || 0);
+          }
+        }
+      }
+      replace("Start");
+      replace("End");
+    };
+
+    select.focusAfterNode = function(node, container) {
+      var win = container.ownerDocument.defaultView,
+          range = win.document.createRange();
+      range.setStartBefore(container.firstChild || container);
+      // In Opera, setting the end of a range at the end of a line
+      // (before a BR) will cause the cursor to appear on the next
+      // line, so we set the end inside of the start node when
+      // possible.
+      if (node && !node.firstChild)
+        range.setEndAfter(node);
+      else if (node)
+        range.setEnd(node, node.childNodes.length);
+      else
+        range.setEndBefore(container.firstChild || container);
+      range.collapse(false);
+      selectRange(range, win);
+    };
+
+       // added for t3editor
+       select.insertTextAtCursor = function(window, text) {
+               var selection = window.getSelection();
+               if (selection && selection.rangeCount > 0) {
+                       var range = selection.getRangeAt(0);
+                       textnode = window.document.createTextNode(text);
+                       range.insertNode(textnode);
+                       range.setEndAfter(textnode);
+                       range.collapse(false);
+                       selectRange(range, window);
+               }
+       };
+
+    select.insertNewlineAtCursor = function(window) {
+      insertNodeAtCursor(window, window.document.createElement("BR"));
+    };
+
+    select.cursorPos = function(container, start) {
+      var range = selectionRange(window);
+      if (!range) return;
+
+      var topNode = select.selectionTopNode(container, start);
+      while (topNode && topNode.nodeName != "BR")
+        topNode = topNode.previousSibling;
+
+      range = range.cloneRange();
+      range.collapse(start);
+      if (topNode)
+        range.setStartAfter(topNode);
+      else
+        range.setStartBefore(container);
+      return {node: topNode, offset: range.toString().length};
+    };
+
+    select.setCursorPos = function(container, from, to) {
+      var win = container.ownerDocument.defaultView,
+          range = win.document.createRange();
+
+      function setPoint(node, offset, side) {
+        if (!node)
+          node = container.firstChild;
+        else
+          node = node.nextSibling;
+
+        if (offset == 0) {
+          range["set" + side + "Before"](node);
+          return true;
+        }
+
+        var backlog = []
+        function decompose(node) {
+          if (node.nodeType == 3)
+            backlog.push(node);
+          else
+            forEach(node.childNodes, decompose);
+        }
+        while (true) {
+          while (node && !backlog.length) {
+            decompose(node);
+            node = node.nextSibling;
+          }
+          var cur = backlog.shift();
+          if (!cur) return false;
+
+          var length = cur.nodeValue.length;
+          if (length >= offset) {
+            range["set" + side](cur, offset);
+            return true;
+          }
+          offset -= length;
+        }
+      }
+
+      to = to || from;
+      if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start"))
+        selectRange(range, win);
+    };
+
+    select.scrollToCursor = function(container) {
+      var body = container.ownerDocument.body, win = container.ownerDocument.defaultView;
+      var element = select.selectionTopNode(container, true) || container.firstChild;
+      
+      // In Opera, BR elements *always* have a scrollTop property of zero. Go Opera.
+      while (element && !element.offsetTop)
+        element = element.previousSibling;
+
+      var y = 0, pos = element;
+      while (pos && pos.offsetParent) {
+        y += pos.offsetTop;
+        pos = pos.offsetParent;
+      }
+
+      var screen_y = y - body.scrollTop;
+      if (screen_y < 0 || screen_y > win.innerHeight - 10)
+        win.scrollTo(0, y);
+    };
+  }
+  
+  insertNodeAtCursor = function(window, node) {
+      var range = selectionRange(window);
+      if (!range) return;
+
+      // On Opera, insertNode is completely broken when the range is
+      // in the middle of a text node.
+      if (window.opera && range.startContainer.nodeType == 3 && range.startOffset != 0) {
+        var start = range.startContainer, text = start.nodeValue;
+        start.parentNode.insertBefore(window.document.createTextNode(text.substr(0, range.startOffset)), start);
+        start.nodeValue = text.substr(range.startOffset);
+        start.parentNode.insertBefore(node, start);
+      }
+      else {
+        range.insertNode(node);
+      }
+
+      range.setEndAfter(node);
+      range.collapse(false);
+      selectRange(range, window);
+    }
+}());
diff --git a/typo3/sysext/t3editor/jslib/codemirror/stringstream.js b/typo3/sysext/t3editor/jslib/codemirror/stringstream.js
new file mode 100644 (file)
index 0000000..4220e26
--- /dev/null
@@ -0,0 +1,121 @@
+/* String streams are the things fed to parsers (which can feed them
+ * to a tokenizer if they want). They provide peek and next methods
+ * for looking at the current character (next 'consumes' this
+ * character, peek does not), and a get method for retrieving all the
+ * text that was consumed since the last time get was called.
+ *
+ * An easy mistake to make is to let a StopIteration exception finish
+ * the token stream while there are still characters pending in the
+ * string stream (hitting the end of the buffer while parsing a
+ * token). To make it easier to detect such errors, the strings throw
+ * an exception when this happens.
+ */
+
+(function(){
+  // Generic operations that apply to stringstreams.
+  var base = {
+    more: function() {
+      return this.peek() !== null;
+    },
+    applies: function(test) {
+      var next = this.peek();
+      return (next !== null && test(next));
+    },
+    nextWhile: function(test) {
+      while (this.applies(test))
+        this.next();
+    },
+    equals: function(ch) {
+      return ch === this.peek();
+    },
+    notEquals: function(ch) {
+      var next = this.peek();
+      return (next !== null && next != ch);
+    }
+  };
+
+  // Make a stream out of a single string. Not used by the editor, but
+  // very useful for testing your parser.
+  window.singleStringStream = function(string) {
+    var pos = 0, start = 0;
+    return update({
+      peek: function() {
+        if (pos < string.length)
+          return string.charAt(pos);
+        else
+          return null;
+      },
+      next: function() {
+        if (pos >= string.length) {
+          if (pos < start)
+            throw "End of stringstream reached without emptying buffer.";
+          else 
+            throw StopIteration;
+        }
+        return string.charAt(pos++);
+      },
+      get: function() {
+        var result = string.slice(start, pos);
+        start = pos;
+        return result;
+      }
+    }, base);
+  }
+
+  // Make a string stream out of an iterator that returns strings. This
+  // is applied to the result of traverseDOM (see codemirror.js), and
+  // the resulting stream is fed to the parser.
+  window.multiStringStream = function(source){
+    source = iter(source);
+    var current = "", pos = 0;
+    var peeked = null, accum = "";
+
+    return update({
+      peek: function(){
+        if (!peeked) {
+          try {peeked = this.step();}
+          catch (e) {
+            if (e != StopIteration) throw e;
+            else peeked = null;
+          }
+        }
+        return peeked;
+      },
+      step: function(){
+        if (peeked){
+          var temp = peeked;
+          peeked = null;
+          return temp;
+        }
+        while (pos == current.length){
+          accum += current;
+          current = ""; // In case source.next() throws
+          pos = 0;
+          current = source.next();
+        }
+        return current.charAt(pos++);
+
+      },
+      next: function(){
+        try {return this.step();}
+        catch (e) {
+          if (e == StopIteration && accum.length > 0)
+            throw "End of stringstream reached without emptying buffer ('" + accum + "').";
+          else
+            throw e;
+        }
+      },
+      get: function(){
+        var temp = accum;
+        var realPos = peeked ? pos - 1 : pos;
+        accum = "";
+        if (realPos > 0){
+          temp += current.slice(0, realPos);
+          current = current.slice(realPos);
+          pos = peeked ? 1 : 0;
+        }
+        return temp;
+      }
+    }, base);
+  }
+})();
diff --git a/typo3/sysext/t3editor/jslib/codemirror/tokenizejavascript.js b/typo3/sysext/t3editor/jslib/codemirror/tokenizejavascript.js
new file mode 100644 (file)
index 0000000..7eeca3f
--- /dev/null
@@ -0,0 +1,182 @@
+/* Tokenizer for JavaScript code */
+
+var tokenizeJavaScript = function(){
+  // A map of JavaScript's keywords. The a/b/c keyword distinction is
+  // very rough, but it gives the parser enough information to parse
+  // correct code correctly (we don't care much how we parse incorrect
+  // code). The style information included in these objects is used by
+  // the highlighter to pick the correct CSS style for a token.
+  var keywords = function(){
+    function result(type, style){
+      return {type: type, style: style};
+    }
+    // keywords that take a parenthised expression, and then a
+    // statement (if)
+    var keywordA = result("keyword a", "keyword");
+    // keywords that take just a statement (else)
+    var keywordB = result("keyword b", "keyword");
+    // keywords that optionally take an expression, and form a
+    // statement (return)
+    var keywordC = result("keyword c", "keyword");
+    var operator = result("operator", "keyword");
+    var atom = result("atom", "atom");
+    return {
+      "if": keywordA, "switch": keywordA, "while": keywordA, "with": keywordA,
+      "else": keywordB, "do": keywordB, "try": keywordB, "finally": keywordB,
+      "return": keywordC, "break": keywordC, "continue": keywordC, "new": keywordC, "delete": keywordC, "throw": keywordC,
+      "in": operator, "typeof": operator, "instanceof": operator,
+      "var": result("var", "keyword"), "function": result("function", "keyword"), "catch": result("catch", "keyword"),
+      "for": result("for", "keyword"), "case": result("case", "keyword"),
+      "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
+    };
+  }();
+
+  // Some helper regexp matchers.
+  var isOperatorChar = matcher(/[\+\-\*\&\%\/=<>!\?]/);
+  var isDigit = matcher(/[0-9]/);
+  var isHexDigit = matcher(/[0-9A-Fa-f]/);
+  var isWordChar = matcher(/[\w\$_]/);
+  function isWhiteSpace(ch){
+    // In our scheme newlines are no whitespace (they are another
+    // special case).
+    return ch != "\n" && realWhiteSpace.test(ch);
+  }
+
+  // This function produces a MochiKit-style iterator that tokenizes
+  // the output of the given stringstream (see stringstream.js).
+  // Tokens are objects with a type, style, and value property. The
+  // value contains the textual content of the token. Because this may
+  // include trailing whitespace (for efficiency reasons), some
+  // tokens, such a variable names, also have a name property
+  // containing their actual textual value.
+  return function(source){
+    // Produce a value to return. Automatically skips and includes any
+    // whitespace. The base argument is prepended to the value
+    // property and assigned to the name property -- this is used when
+    // the caller has already extracted the text from the stream
+    // himself.
+    function result(type, style, base){
+      source.nextWhile(isWhiteSpace);
+      var value = {type: type, style: style, value: (base ? base + source.get() : source.get())};
+      if (base) value.name = base;
+      return value;
+    }
+
+    // Advance the stream until the given character (not preceded by a
+    // backslash) is encountered (or a newline is found).
+    function nextUntilUnescaped(end){
+      var escaped = false;
+      var next;
+      while(source.notEquals("\n")){
+        var next = source.next();
+        if (next == end && !escaped)
+          break;
+        escaped = next == "\\";
+      }
+    }
+  
+    function readHexNumber(){
+      source.next(); // skip the 'x'
+      source.nextWhile(isHexDigit);
+      return result("number", "atom");
+    }
+    function readNumber(){
+      source.nextWhile(isDigit);
+      if (source.equals(".")){
+        source.next();
+        source.nextWhile(isDigit);
+      }
+      if (source.equals("e") || source.equals("E")){
+        source.next();
+        if (source.equals("-"))
+          source.next();
+        source.nextWhile(isDigit);
+      }
+      return result("number", "atom");
+    }
+    // Read a word, look it up in keywords. If not found, it is a
+    // variable, otherwise it is a keyword of the type found.
+    function readWord(){
+      source.nextWhile(isWordChar);
+      var word = source.get();
+      var known = keywords.hasOwnProperty(word) && keywords.propertyIsEnumerable(word) && keywords[word];
+      return known ? result(known.type, known.style, word) : result("variable", "variable", word);
+    }
+    function readRegexp(){
+      nextUntilUnescaped("/");
+      source.nextWhile(matcher(/[gi]/));
+      return result("regexp", "string");
+    }
+    // Mutli-line comments are tricky. We want to return the newlines
+    // embedded in them as regular newline tokens, and then continue
+    // returning a comment token for every line of the comment. So
+    // some state has to be saved (inComment) to indicate whether we
+    // are inside a /* */ sequence.
+    function readMultilineComment(start){
+      this.inComment = true;
+      var maybeEnd = (start == "*");
+      while(true){
+        if (source.equals("\n"))
+          break;
+        var next = source.next();
+        if (next == "/" && maybeEnd){
+          this.inComment = false;
+          break;
+        }
+        maybeEnd = (next == "*");
+      }
+      return result("comment", "comment");
+    }
+
+    // Fetch the next token. Dispatches on first character in the
+    // stream, or first two characters when the first is a slash. The
+    // || things are a silly trick to keep simple cases on a single
+    // line.
+    function next(){
+      var token = null;
+      var ch = source.next();
+      if (ch == "\n")
+        token = {type: "newline", style: "whitespace", value: source.get()};
+      else if (this.inComment)
+        token = readMultilineComment.call(this, ch);
+      else if (isWhiteSpace(ch))
+        token = source.nextWhile(isWhiteSpace) || result("whitespace", "whitespace");
+      else if (ch == "\"" || ch == "'")
+        token = nextUntilUnescaped(ch) || result("string", "string");
+      // with punctuation, the type of the token is the symbol itself
+      else if (/[\[\]{}\(\),;\:\.]/.test(ch))
+        token = result(ch, "punctuation");
+      else if (ch == "0" && (source.equals("x") || source.equals("X")))
+        token = readHexNumber();
+      else if (isDigit(ch))
+        token = readNumber();
+      else if (ch == "/"){
+        if (source.equals("*"))
+          token = readMultilineComment.call(this, ch);
+        else if (source.equals("/"))
+          token = nextUntilUnescaped(null) || result("comment", "comment");
+        else if (this.regexp)
+          token = readRegexp();
+        else
+          token = source.nextWhile(isOperatorChar) || result("operator", "operator");
+      }
+      else if (isOperatorChar(ch))
+        token = source.nextWhile(isOperatorChar) || result("operator", "operator");
+      else
+        token = readWord();
+
+      // JavaScript's syntax rules for when a slash might be the start
+      // of a regexp and when it is just a division operator are kind
+      // of non-obvious. This decides, based on the current token,
+      // whether the next token could be a regular expression.
+      if (token.style != "whitespace" && token != "comment")
+        this.regexp = token.type == "operator" || token.type == "keyword c" || token.type.match(/[\[{}\(,;:]/);
+      return token;
+    }
+
+    // Wrap it in an iterator. The state (regexp and inComment) is
+    // exposed because a parser will need to save it when making a
+    // copy of its state.
+    return {next: next, regexp: true, inComment: false};
+  }
+}();
diff --git a/typo3/sysext/t3editor/jslib/codemirror/tokenizetyposcript.js b/typo3/sysext/t3editor/jslib/codemirror/tokenizetyposcript.js
new file mode 100644 (file)
index 0000000..b29ff73
--- /dev/null
@@ -0,0 +1,1372 @@
+/* Tokenizer for TypoScript code
+ *
+ * based on tokenizejavascript.js by Marijn Haverbeke
+ */
+
+// List of "reserved" word in typoscript and a css-class
+var typoscriptWords = {
+       '_CSS_DEFAULT_STYLE': 'keyword',
+       '_DEFAULT_PI_VARS': 'keyword',
+       '_GIFBUILDER': 'keyword',
+       '_LOCAL_LANG': 'keyword',
+       'CARRAY': 'keyword',
+       'CASE': 'keyword',
+       'CLEARGIF': 'keyword',
+       'COA': 'keyword',
+       'COA_INT': 'keyword',
+       'COBJ_ARRAY': 'keyword',
+       'COLUMNS': 'keyword',
+       'CONFIG': 'keyword',
+       'CONSTANTS': 'keyword',
+       'CONTENT': 'keyword',
+       'CTABLE': 'keyword',
+       'CType': 'keyword',
+       'DB': 'keyword',
+       'DOCUMENT_BODY': 'keyword',
+       'EDITPANEL': 'keyword',
+       'EFFECT': 'keyword',
+       'FE_DATA': 'keyword',
+       'FE_TABLE': 'keyword',
+       'FEData': 'keyword',
+       'FILE': 'keyword',
+       'FORM': 'keyword',
+       'FRAME': 'keyword',
+       'FRAMESET': 'keyword',
+       'GIFBUILDER': 'keyword',
+       'global': 'keyword',
+       'globalString': 'keyword',
+       'globalVar': 'keyword',
+       'GMENU': 'keyword',
+       'GMENU_FOLDOUT': 'keyword',
+       'GMENU_LAYERS': 'keyword',
+       'GP': 'keyword',
+       'HMENU': 'keyword',
+       'HRULER': 'keyword',
+       'HTML': 'keyword',
+       'IENV': 'keyword',
+       'IMAGE': 'keyword',
+       'IMG_RESOURCE': 'keyword',
+       'IMGMENU': 'keyword',
+       'IMGMENUITEM': 'keyword',
+       'IMGTEXT': 'keyword',
+       'INCLUDE_TYPOSCRIPT': 'keyword',
+       'includeLibs': 'keyword',
+       'JSMENU': 'keyword',
+       'JSMENUITEM': 'keyword',
+       'LIT': 'keyword',
+       'LOAD_REGISTER': 'keyword',
+       'META': 'keyword',
+       'MULTIMEDIA': 'keyword',
+       'OTABLE': 'keyword',
+       'PAGE': 'keyword',
+       'PAGE_TARGET': 'keyword',
+       'PAGE_TSCONFIG_ID': 'keyword',
+       'PAGE_TSCONFIG_IDLIST': 'keyword',
+       'PAGE_TSCONFIG_STR': 'keyword',
+       'PHP_SCRIPT': 'keyword',
+       'PHP_SCRIPT_EXT': 'keyword',
+       'PHP_SCRIPT_INT': 'keyword',
+       'RECORDS': 'keyword',
+       'REMOTE_ADDR': 'keyword',
+       'RESTORE_REGISTER': 'keyword',
+       'RTE': 'keyword',
+       'SEARCHRESULT': 'keyword',
+       'SHARED': 'keyword',
+       'TCAdefaults': 'keyword',
+       'TCEFORM': 'keyword',
+       'TCEMAIN': 'keyword',
+       'TEMPLATE': 'keyword',
+       'TEXT': 'keyword',
+       'TMENU': 'keyword',
+       'TMENU_LAYERS': 'keyword',
+       'TMENUITEM': 'keyword',
+       'TSFE': 'keyword',
+       'USER': 'keyword',
+       'USER_INT': 'keyword',
+       
+       'userFunc': 'keyword',
+
+       '_offset': 'reserved',
+       'absRefPrefix': 'reserved',
+       'accessibility': 'reserved',
+       'accessKey': 'reserved',
+       'addAttributes': 'reserved',
+       'addExtUrlsAndShortCuts': 'reserved',
+       'addItems': 'reserved',
+       'additionalHeaders': 'reserved',
+       'additionalParams': 'reserved',
+       'addParams': 'reserved',
+       'addQueryString': 'reserved',
+       'adjustItemsH': 'reserved',
+       'adjustSubItemsH': 'reserved',
+       'adminPanelStyles': 'reserved',
+       'after': 'reserved',
+       'afterImg': 'reserved',
+       'afterImgLink': 'reserved',
+       'afterImgTagParams': 'reserved',
+       'afterROImg': 'reserved',
+       'afterWrap': 'reserved',
+       'age': 'reserved',
+       'alertPopups': 'reserved',
+       'align': 'reserved',
+       'allow': 'reserved',
+       'allowCaching': 'reserved',
+       'allowedAttribs': 'reserved',
+       'allowedClasses': 'reserved',
+       'allowedCols': 'reserved',
+       'allowEdit': 'reserved',
+       'allowedNewTables': 'reserved',
+       'allowNew': 'reserved',
+       'allowTags': 'reserved',
+       'allowTVlisting': 'reserved',
+       'allSaveFunctions': 'reserved',
+       'allStdWrap': 'reserved',
+       'allWrap': 'reserved',
+       'alternateBgColors': 'reserved',
+       'alternativeSortingField': 'reserved',
+       'alternativeTempPath': 'reserved',
+       'altImgResource': 'reserved',
+       'altLabels': 'reserved',
+       'altTarget': 'reserved',
+       'altText': 'reserved',
+       'altUrl': 'reserved',
+       'altUrl_noDefaultParams': 'reserved',
+       'altWrap': 'reserved',
+       'always': 'reserved',
+       'alwaysActivePIDlist': 'reserved',
+       'alwaysLink': 'reserved',
+       'alwaysShowClickMenuInTopFrame': 'reserved',
+       'andWhere': 'reserved',
+       'angle': 'reserved',
+       'antiAlias': 'reserved',
+       'append': 'reserved',
+       'applyTotalH': 'reserved',
+       'applyTotalW': 'reserved',
+       'archive': 'reserved',
+       'archiveTypoLink': 'reserved',
+       'arrayReturnMode': 'reserved',
+       'arrowACT': 'reserved',
+       'arrowImgParams': 'reserved',
+       'arrowNO': 'reserved',
+       'ATagAfterWrap': 'reserved',
+       'ATagBeforeWrap': 'reserved',
+       'ATagParams': 'reserved',
+       'ATagTitle': 'reserved',
+       'attribute': 'reserved',
+       'autoInsertPID': 'reserved',
+       'autoLevels': 'reserved',
+       'autonumber': 'reserved',
+       'backColor': 'reserved',
+       'background': 'reserved',
+       'badMess': 'reserved',
+       'baseURL': 'reserved',
+       'before': 'reserved',
+       'beforeImg': 'reserved',
+       'beforeImgLink': 'reserved',
+       'beforeImgTagParams': 'reserved',
+       'beforeROImg': 'reserved',
+       'beforeWrap': 'reserved',
+       'begin': 'reserved',
+       'beLoginLinkIPList': 'reserved',
+       'beLoginLinkIPList_login': 'reserved',
+       'beLoginLinkIPList_logout': 'reserved',
+       'bgCol': 'reserved',
+       'bgImg': 'reserved',
+       'blankStrEqFalse': 'reserved',
+       'blur': 'reserved',
+       'bm': 'reserved',
+       'bodyTag': 'reserved',
+       'bodyTagAdd': 'reserved',
+       'bodyTagCObject': 'reserved',
+       'bodyTagMargins': 'reserved',
+       'bodytext': 'reserved',
+       'border': 'reserved',
+       'borderCol': 'reserved',
+       'bordersWithin': 'reserved',
+       'borderThick': 'reserved',
+       'bottomBackColor': 'reserved',
+       'bottomContent': 'reserved',
+       'bottomHeight': 'reserved',
+       'bottomImg': 'reserved',
+       'bottomImg_mask': 'reserved',
+       'br': 'reserved',
+       'brTag': 'reserved',
+       'bullet': 'reserved',
+       'bulletlist': 'reserved',
+       'bytes': 'reserved',
+       'cache_clearAtMidnight': 'reserved',
+       'cache_period': 'reserved',
+       'caption': 'reserved',
+       'caption_stdWrap': 'reserved',
+       'captionAlign': 'reserved',
+       'captionHeader': 'reserved',
+       'captionSplit': 'reserved',
+       'case': 'reserved',
+       'casesensitiveComp': 'reserved',
+       'cellpadding': 'reserved',
+       'cellspacing': 'reserved',
+       'centerImgACT': 'reserved',
+       'centerImgCUR': 'reserved',
+       'centerImgNO': 'reserved',
+       'centerLeftImgACT': 'reserved',
+       'centerLeftImgCUR': 'reserved',
+       'centerLeftImgNO': 'reserved',
+       'centerRightImgACT': 'reserved',
+       'centerRightImgCUR': 'reserved',
+       'centerRightImgNO': 'reserved',
+       'char': 'reserved',
+       'charcoal': 'reserved',
+       'charMapConfig': 'reserved',
+       'check': 'reserved',
+       'class': 'reserved',
+       'classesAnchor': 'reserved',
+       'classesCharacter': 'reserved',
+       'classesImage': 'reserved',
+       'classesParagraph': 'reserved',
+       'classicPageEditMode': 'reserved',
+       'clear': 'reserved',
+       'clearCache': 'reserved',
+       'clearCache_disable': 'reserved',
+       'clearCache_pageGrandParent': 'reserved',
+       'clearCache_pageSiblingChildren': 'reserved',
+       'clearCacheCmd': 'reserved',
+       'clearCacheLevels': 'reserved',
+       'clearCacheOfPages': 'reserved',
+       'clickMenuTimeOut': 'reserved',
+       'clickTitleMode': 'reserved',
+       'clipboardNumberPads': 'reserved',
+       'cMargins': 'reserved',
+       'cObjNum': 'reserved',
+       'collapse': 'reserved',
+       'color': 'reserved',
+       'color1': 'reserved',
+       'color2': 'reserved',
+       'color3': 'reserved',
+       'color4': 'reserved',
+       'colors': 'reserved',
+       'colour': 'reserved',
+       'colPos_list': 'reserved',
+       'colRelations': 'reserved',
+       'cols': 'reserved',
+       'colSpace': 'reserved',
+       'comment_auto': 'reserved',
+       'commentWrap': 'reserved',
+       'compensateFieldWidth': 'reserved',
+       'compX': 'reserved',
+       'compY': 'reserved',
+       'condensedMode': 'reserved',
+       'conf': 'reserved',
+       'constants': 'reserved',
+       'content_from_pid_allowOutsideDomain': 'reserved',
+       'contextMenu': 'reserved',
+       'copyLevels': 'reserved',
+       'count_HMENU_MENUOBJ': 'reserved',
+       'count_menuItems': 'reserved',
+       'count_MENUOBJ': 'reserved',
+       'create': 'reserved',
+       'createFoldersInEB': 'reserved',
+       'crop': 'reserved',
+       'csConv': 'reserved',
+       'CSS_inlineStyle': 'reserved',
+       'current': 'reserved',
+       'curUid': 'reserved',
+       'cWidth': 'reserved',
+       'data': 'reserved',
+       'dataWrap': 'reserved',
+       'date': 'reserved',
+       'date_stdWrap': 'reserved',
+       'datePrefix': 'reserved',
+       'debug': 'reserved',
+       'debugData': 'reserved',
+       'debugFunc': 'reserved',
+       'debugItemConf': 'reserved',
+       'debugRenumberedObject': 'reserved',
+       'default': 'reserved',
+       'defaultAlign': 'reserved',
+       'defaultCmd': 'reserved',
+       'defaultFileUploads': 'reserved',
+       'defaultHeaderType': 'reserved',
+       'defaultOutput': 'reserved',
+       'defaults': 'reserved',
+       'defaultType': 'reserved',
+       'delete': 'reserved',
+       'denyTags': 'reserved',
+       'depth': 'reserved',
+       'DESC': 'reserved',
+       'dimensions': 'reserved',
+       'directionLeft': 'reserved',
+       'directionUp': 'reserved',
+       'disableAdvanced': 'reserved',
+       'disableAllHeaderCode': 'reserved',
+       'disableAltText': 'reserved',
+       'disableBigButtons': 'reserved',
+       'disableCacheSelector': 'reserved',
+       'disableCharsetHeader': 'reserved',
+       'disableCMlayers': 'reserved',
+       'disabled': 'reserved',
+       'disableDelete': 'reserved',
+       'disableDocModuleInAB': 'reserved',
+       'disableDocSelector': 'reserved',
+       'disableHideAtCopy': 'reserved',
+       'disableIconLinkToContextmenu': 'reserved',
+       'disableItems': 'reserved',
+       'disableNewContentElementWizard': 'reserved',
+       'disableNoMatchingValueElement': 'reserved',
+       'disablePageExternalUrl': 'reserved',
+       'disablePrefixComment': 'reserved',
+       'disablePrependAtCopy': 'reserved',
+       'disableSearchBox': 'reserved',
+       'disableSingleTableView': 'reserved',
+       'disableTabInTextarea': 'reserved',
+       'displayActiveOnLoad': 'reserved',
+       'displayContent': 'reserved',
+       'displayFieldIcons': 'reserved',
+       'displayIcons': 'reserved',
+       'displayMessages': 'reserved',
+       'displayQueries': 'reserved',
+       'displayRecord': 'reserved',
+       'displayTimes': 'reserved',
+       'distributeX': 'reserved',
+       'distributeY': 'reserved',
+       'DIV': 'reserved',
+       'doctype': 'reserved',
+       'doctypeSwitch': 'reserved',
+       'doktype': 'reserved',
+       'doNotLinkIt': 'reserved',
+       'doNotShowLink': 'reserved',
+       'doNotStripHTML': 'reserved',
+       'dontCheckPid': 'reserved',
+       'dontFollowMouse': 'reserved',
+       'dontHideOnMouseUp': 'reserved',
+       'dontLinkIfSubmenu': 'reserved',
+       'dontShowPalettesOnFocusInAB': 'reserved',
+       'dontWrapInTable': 'reserved',
+       'doubleBrTag': 'reserved',
+       'doublePostCheck': 'reserved',
+       'dWorkArea': 'reserved',
+       'edge': 'reserved',
+       'edit_docModuleUplaod': 'reserved',
+       'edit_docModuleUpload': 'reserved',
+       'edit_RTE': 'reserved',
+       'edit_showFieldHelp': 'reserved',
+       'edit_wideDocument': 'reserved',
+       'editFieldsAtATime': 'reserved',
+       'editFormsOnPage': 'reserved',
+       'editIcons': 'reserved',
+       'editNoPopup': 'reserved',
+       'editPanel': 'reserved',
+       'elements': 'reserved',
+       'emailMeAtLogin': 'reserved',
+       'emailMess': 'reserved',
+       'emboss': 'reserved',
+       'enable': 'reserved',
+       'encapsLines': 'reserved',
+       'encapsLinesStdWrap': 'reserved',
+       'encapsTagList': 'reserved',
+       'entryLevel': 'reserved',
+       'equalH': 'reserved',
+       'everybody': 'reserved',
+       'excludeDoktypes': 'reserved',
+       'excludeUidList': 'reserved',
+       'expAll': 'reserved',
+       'expand': 'reserved',
+       'explode': 'reserved',
+       'ext': 'reserved',
+       'externalBlocks': 'reserved',
+       'extTarget': 'reserved',
+       'face': 'reserved',
+       'fe_adminLib': 'reserved',
+       'field': 'reserved',
+       'fieldOrder': 'reserved',
+       'fieldRequired': 'reserved',
+       'fields': 'reserved',
+       'fieldWrap': 'reserved',
+       'file': 'reserved',
+       'file1': 'reserved',
+       'file2': 'reserved',
+       'file3': 'reserved',
+       'file4': 'reserved',
+       'file5': 'reserved',
+       'filelink': 'reserved',
+       'filelist': 'reserved',
+       'firstLabel': 'reserved',
+       'firstLabelGeneral': 'reserved',
+       'fixAttrib': 'reserved',
+       'flip': 'reserved',
+       'flop': 'reserved',
+       'foldSpeed': 'reserved',
+       'foldTimer': 'reserved',
+       'fontColor': 'reserved',
+       'fontFile': 'reserved',
+       'fontOffset': 'reserved',
+       'fontSize': 'reserved',
+       'fontSizeMultiplicator': 'reserved',
+       'fontTag': 'reserved',
+       'forceDisplayFieldIcons': 'reserved',
+       'forceDisplayIcons': 'reserved',
+       'forceNoPopup': 'reserved',
+       'forceTemplateParsing': 'reserved',
+       'forceTypeValue': 'reserved',
+       'format': 'reserved',
+       'frame': 'reserved',
+       'frameReloadIfNotInFrameset': 'reserved',
+       'frameSet': 'reserved',
+       'freezeMouseover': 'reserved',
+       'ftu': 'reserved',
+       'function': 'reserved',
+       'gamma': 'reserved',
+       'gapBgCol': 'reserved',
+       'gapLineCol': 'reserved',
+       'gapLineThickness': 'reserved',
+       'gapWidth': 'reserved',
+       'get': 'reserved',
+       'getBorder': 'reserved',
+       'getLeft': 'reserved',
+       'getRight': 'reserved',
+       'globalNesting': 'reserved',
+       'goodMess': 'reserved',
+       'gray': 'reserved',
+       'group': 'reserved',
+       'groupBy': 'reserved',
+       'groupid': 'reserved',
+       'header': 'reserved',
+       'header_layout': 'reserved',
+       'headerComment': 'reserved',
+       'headerData': 'reserved',
+       'headerSpace': 'reserved',
+       'headTag': 'reserved',
+       'height': 'reserved',
+       'helpText': 'reserved',
+       'hidden': 'reserved',
+       'hiddenFields': 'reserved',
+       'hide': 'reserved',
+       'hideButCreateMap': 'reserved',
+       'hideMenuTimer': 'reserved',
+       'hideMenuWhenNotOver': 'reserved',
+       'hidePStyleItems': 'reserved',
+       'hideRecords': 'reserved',
+       'hideSubmoduleIcons': 'reserved',
+       'highColor': 'reserved',
+       'history': 'reserved',
+       'hover': 'reserved',
+       'hoverStyle': 'reserved',
+       'HTMLparser': 'reserved',
+       'HTMLparser_tags': 'reserved',
+       'htmlSpecialChars': 'reserved',
+       'htmlTag_dir': 'reserved',
+       'htmlTag_langKey': 'reserved',
+       'htmlTag_setParams': 'reserved',
+       'http': 'reserved',
+       'icon': 'reserved',
+       'icon_image_ext_list': 'reserved',
+       'icon_link': 'reserved',
+       'iconCObject': 'reserved',
+       'ifEmpty': 'reserved',
+       'image': 'reserved',
+       'image_compression': 'reserved',
+       'image_effects': 'reserved',
+       'image_frames': 'reserved',
+       'imageLinkWrap': 'reserved',
+       'imagePath': 'reserved',
+       'images': 'reserved',
+       'imageWrapIfAny': 'reserved',
+       'imgList': 'reserved',
+       'imgMap': 'reserved',
+       'imgMapExtras': 'reserved',
+       'imgMax': 'reserved',
+       'imgNameNotRandom': 'reserved',
+       'imgNamePrefix': 'reserved',
+       'imgObjNum': 'reserved',
+       'imgParams': 'reserved',
+       'imgPath': 'reserved',
+       'imgStart': 'reserved',
+       'import': 'reserved',
+       'inc': 'reserved',
+       'includeCSS': 'reserved',
+       'includeLibrary': 'reserved',
+       'includeNotInMenu': 'reserved',
+       'incT3Lib_htmlmail': 'reserved',
+       'index': 'reserved',
+       'index_descrLgd': 'reserved',
+       'index_enable': 'reserved',
+       'index_externals': 'reserved',
+       'inlineStyle2TempFile': 'reserved',
+       'innerStdWrap': 'reserved',
+       'innerStdWrap_all': 'reserved',
+       'innerWrap': 'reserved',
+       'innerWrap2': 'reserved',
+       'input': 'reserved',
+       'inputLevels': 'reserved',
+       'insertClassesFromRTE': 'reserved',
+       'insertData': 'reserved',
+       'insertDmailerBoundaries': 'reserved',
+       'intensity': 'reserved',
+       'intTarget': 'reserved',
+       'intval': 'reserved',
+       'invert': 'reserved',
+       'IProcFunc': 'reserved',
+       'itemArrayProcFunc': 'reserved',
+       'itemH': 'reserved',
+       'items': 'reserved',
+       'itemsProcFunc': 'reserved',
+       'iterations': 'reserved',
+       'join': 'reserved',
+       'JSWindow': 'reserved',
+       'JSwindow_params': 'reserved',
+       'jumpurl': 'reserved',
+       'jumpUrl': 'reserved',
+       'jumpurl_enable': 'reserved',
+       'jumpurl_mailto_disable': 'reserved',
+       'jumpUrl_transferSession': 'reserved',
+       'keep': 'reserved',
+       'keepEntries': 'reserved',
+       'keepNonMatchedTags': 'reserved',
+       'key': 'reserved',
+       'label': 'reserved',
+       'labelStdWrap': 'reserved',
+       'labelWrap': 'reserved',
+       'lang': 'reserved',
+       'language': 'reserved',
+       'language_alt': 'reserved',
+       'languageField': 'reserved',
+       'layer_menu_id': 'reserved',
+       'layerStyle': 'reserved',
+       'left': 'reserved',
+       'leftIcons': 'reserved',
+       'leftImgACT': 'reserved',
+       'leftImgCUR': 'reserved',
+       'leftImgNO': 'reserved',
+       'leftjoin': 'reserved',
+       'leftOffset': 'reserved',
+       'levels': 'reserved',
+       'leveluid': 'reserved',
+       'limit': 'reserved',
+       'line': 'reserved',
+       'lineColor': 'reserved',
+       'lineThickness': 'reserved',
+       'linkPrefix': 'reserved',
+       'linkTitleToSelf': 'reserved',
+       'linkVars': 'reserved',
+       'linkWrap': 'reserved',
+       'listNum': 'reserved',
+       'listOnlyInSingleTableView': 'reserved',
+       'lm': 'reserved',
+       'locale_all': 'reserved',
+       'localNesting': 'reserved',
+       'locationData': 'reserved',
+       'lockFilePath': 'reserved',
+       'lockPosition': 'reserved',
+       'lockPosition_addSelf': 'reserved',
+       'lockPosition_adjust': 'reserved',
+       'lockToIP': 'reserved',
+       'longdescURL': 'reserved',
+       'lowColor': 'reserved',
+       'lower': 'reserved',
+       'LR': 'reserved',
+       'mailto': 'reserved',
+       'main': 'reserved',
+       'mainScript': 'reserved',
+       'makelinks': 'reserved',
+       'markerWrap': 'reserved',
+       'mask': 'reserved',
+       'max': 'reserved',
+       'maxAge': 'reserved',
+       'maxAgeDays': 'reserved',
+       'maxChars': 'reserved',
+       'maxH': 'reserved',
+       'maxHeight': 'reserved',
+       'maxItems': 'reserved',
+       'maxW': 'reserved',
+       'maxWidth': 'reserved',
+       'maxWInText': 'reserved',
+       'mayNotCreateEditShortcuts': 'reserved',
+       'menu_type': 'reserved',
+       'menuBackColor': 'reserved',
+       'menuHeight': 'reserved',
+       'menuName': 'reserved',
+       'menuOffset': 'reserved',
+       'menuWidth': 'reserved',
+       'message_page_is_being_generated': 'reserved',
+       'message_preview': 'reserved',
+       'meta': 'reserved',
+       'metaCharset': 'reserved',
+       'method': 'reserved',
+       'min': 'reserved',
+       'minH': 'reserved',
+       'minItems': 'reserved',
+       'minW': 'reserved',
+       'mode': 'reserved',
+       'moduleMenuCollapsable': 'reserved',
+       'MP_defaults': 'reserved',
+       'MP_disableTypolinkClosestMPvalue': 'reserved',
+       'MP_mapRootPoints': 'reserved',
+       'name': 'reserved',
+       'navFrameResizable': 'reserved',
+       'navFrameWidth': 'reserved',
+       'nesting': 'reserved',
+       'netprintApplicationLink': 'reserved',
+       'neverHideAtCopy': 'reserved',
+       'newPageWiz': 'reserved',
+       'newRecordFromTable': 'reserved',
+       'newWindow': 'reserved',
+       'newWizards': 'reserved',
+       'next': 'reserved',
+       'niceText': 'reserved',
+       'nicetext': 'reserved',
+       'no_cache': 'reserved',
+       'no_search': 'reserved',
+       'noAttrib': 'reserved',
+       'noBlur': 'reserved',
+       'noCache': 'reserved',
+       'noCols': 'reserved',
+       'noCreateRecordsLink': 'reserved',
+       'noLink': 'reserved',
+       'noLinkUnderline': 'reserved',
+       'noMatchingValue_label': 'reserved',
+       'noMenuMode': 'reserved',
+       'nonCachedSubst': 'reserved',
+       'nonTypoTagStdWrap': 'reserved',
+       'nonTypoTagUserFunc': 'reserved',
+       'nonWrappedTag': 'reserved',
+       'noOrderBy': 'reserved',
+       'noPageTitle': 'reserved',
+       'noRows': 'reserved',
+       'noScaleUp': 'reserved',
+       'noStretchAndMarginCells': 'reserved',
+       'noThumbsInEB': 'reserved',
+       'noThumbsInRTEimageSelect': 'reserved',
+       'notification_email_charset': 'reserved',
+       'notification_email_encoding': 'reserved',
+       'notification_email_urlmode': 'reserved',
+       'noTrimWrap': 'reserved',
+       'noValueInsert': 'reserved',
+       'obj': 'reserved',
+       'offset': 'reserved',
+       'offsetWrap': 'reserved',
+       'onlineWorkspaceInfo': 'reserved',
+       'onlyCurrentPid': 'reserved',
+       'opacity': 'reserved',
+       'orderBy': 'reserved',
+       'outerWrap': 'reserved',
+       'outline': 'reserved',
+       'outputLevels': 'reserved',
+       'override': 'reserved',
+       'overrideAttribs': 'reserved',
+       'overrideEdit': 'reserved',
+       'overrideId': 'reserved',
+       'overridePageModule': 'reserved',
+       'overrideWithExtension': 'reserved',
+       'pageFrameObj': 'reserved',
+       'pageGenScript': 'reserved',
+       'pageTitleFirst': 'reserved',
+       'parameter': 'reserved',
+       'params': 'reserved',
+       'parseFunc': 'reserved',
+       'parser': 'reserved',
+       'password': 'reserved',
+       'path': 'reserved',
+       'permissions': 'reserved',
+       'pid_list': 'reserved',
+       'pidInList': 'reserved',
+       'pixelSpaceFontSizeRef': 'reserved',
+       'plaintextLib': 'reserved',
+       'plainTextStdWrap': 'reserved',
+       'postCObject': 'reserved',
+       'postLineBlanks': 'reserved',
+       'postLineChar': 'reserved',
+       'postLineLen': 'reserved',
+       'postUserFunc': 'reserved',
+       'postUserFuncInt': 'reserved',
+       'preBlanks': 'reserved',
+       'preCObject': 'reserved',
+       'prefix': 'reserved',
+       'prefixComment': 'reserved',
+       'prefixLocalAnchors': 'reserved',
+       'prefixRelPathWith': 'reserved',
+       'preIfEmptyListNum': 'reserved',
+       'preLineBlanks': 'reserved',
+       'preLineChar': 'reserved',
+       'preLineLen': 'reserved',
+       'prepend': 'reserved',
+       'preserveEntities': 'reserved',
+       'preUserFunc': 'reserved',
+       'prev': 'reserved',
+       'previewBorder': 'reserved',
+       'prevnextToSection': 'reserved',
+       'printheader': 'reserved',
+       'prioriCalc': 'reserved',
+       'proc': 'reserved',
+       'processScript': 'reserved',
+       'properties': 'reserved',
+       'protect': 'reserved',
+       'protectLvar': 'reserved',
+       'publish_levels': 'reserved',
+       'QEisDefault': 'reserved',
+       'quality': 'reserved',
+       'radio': 'reserved',
+       'radioWrap': 'reserved',
+       'range': 'reserved',
+       'rawUrlEncode': 'reserved',
+       'recipient': 'reserved',
+       'recursive': 'reserved',
+       'recursiveDelete': 'reserved',
+       'redirect': 'reserved',
+       'redirectToURL': 'reserved',
+       'reduceColors': 'reserved',
+       'register': 'reserved',
+       'relativeToParentLayer': 'reserved',
+       'relativeToTriggerItem': 'reserved',
+       'relPathPrefix': 'reserved',
+       'remap': 'reserved',
+       'remapTag': 'reserved',
+       'removeBadHTML': 'reserved',
+       'removeDefaultJS': 'reserved',
+       'removeIfEquals': 'reserved',
+       'removeIfFalse': 'reserved',
+       'removeItems': 'reserved',
+       'removeObjectsOfDummy': 'reserved',
+       'removePrependedNumbers': 'reserved',
+       'removeTags': 'reserved',
+       'removeWrapping': 'reserved',
+       'renderCharset': 'reserved',
+       'renderWrap': 'reserved',
+       'reset': 'reserved',
+       'resources': 'reserved',
+       'resultObj': 'reserved',
+       'returnLast': 'reserved',
+       'returnUrl': 'reserved',
+       'rightImgACT': 'reserved',
+       'rightImgCUR': 'reserved',
+       'rightImgNO': 'reserved',
+       'rightjoin': 'reserved',
+       'rm': 'reserved',
+       'rmTagIfNoAttrib': 'reserved',
+       'RO_chBgColor': 'reserved',
+       'rotate': 'reserved',
+       'rows': 'reserved',
+       'rowSpace': 'reserved',
+       'RTEfullScreenWidth': 'reserved',
+       'rules': 'reserved',
+       'sample': 'reserved',
+       'saveClipboard': 'reserved',
+       'saveDocNew': 'reserved',
+       'secondRow': 'reserved',
+       'section': 'reserved',
+       'sectionIndex': 'reserved',
+       'select': 'reserved',
+       'select_key': 'reserved',
+       'selectFields': 'reserved',
+       'separator': 'reserved',
+       'set': 'reserved',
+       'setContentToCurrent': 'reserved',
+       'setCurrent': 'reserved',
+       'setfixed': 'reserved',
+       'setFixedHeight': 'reserved',
+       'setFixedWidth': 'reserved',
+       'setJS_mouseOver': 'reserved',
+       'setJS_openPic': 'reserved',
+       'setOnly': 'reserved',
+       'shadow': 'reserved',
+       'sharpen': 'reserved',
+       'shear': 'reserved',
+       'short': 'reserved',
+       'shortcut': 'reserved',
+       'shortcut_onEditId_dontSetPageTree': 'reserved',
+       'shortcut_onEditId_keepExistingExpanded': 'reserved',
+       'shortcutFrame': 'reserved',
+       'shortcutGroups': 'reserved',
+       'shortcutIcon': 'reserved',
+       'show': 'reserved',
+       'showAccessRestrictedPages': 'reserved',
+       'showActive': 'reserved',
+       'showClipControlPanelsDespiteOfCMlayers': 'reserved',
+       'showFirst': 'reserved',
+       'showHiddenPages': 'reserved',
+       'showHiddenRecords': 'reserved',
+       'showHistory': 'reserved',
+       'showPageIdWithTitle': 'reserved',
+       'showTagFreeClasses': 'reserved',
+       'simulateDate': 'reserved',
+       'simulateStaticDocuments': 'reserved',
+       'simulateStaticDocuments_addTitle': 'reserved',
+       'simulateStaticDocuments_dontRedirectPathInfoError': 'reserved',
+       'simulateStaticDocuments_noTypeIfNoTitle': 'reserved',
+       'simulateStaticDocuments_pEnc': 'reserved',
+       'simulateStaticDocuments_pEnc_onlyP': 'reserved',
+       'simulateUserGroup': 'reserved',
+       'singlePid': 'reserved',
+       'site_author': 'reserved',
+       'site_reserved': 'reserved',
+       'sitetitle': 'reserved',
+       'siteUrl': 'reserved',
+       'size': 'reserved',
+       'smallFormFields': 'reserved',
+       'solarize': 'reserved',
+       'sorting': 'reserved',
+       'source': 'reserved',
+       'space': 'reserved',
+       'spaceAfter': 'reserved',
+       'spaceBefore': 'reserved',
+       'spaceBelowAbove': 'reserved',
+       'spaceLeft': 'reserved',
+       'spaceRight': 'reserved',
+       'spacing': 'reserved',
+       'spamProtectEmailAddresses': 'reserved',
+       'spamProtectEmailAddresses_atSubst': 'reserved',
+       'spamProtectEmailAddresses_lastDotSubst': 'reserved',
+       'special': 'reserved',
+       'splitChar': 'reserved',
+       'splitRendering': 'reserved',
+       'src': 'reserved',
+       'startInTaskCenter': 'reserved',
+       'stayFolded': 'reserved',
+       'stdheader': 'reserved',
+       'stdWrap': 'reserved',
+       'stdWrap2': 'reserved',
+       'strftime': 'reserved',
+       'stripHtml': 'reserved',
+       'styles': 'reserved',
+       'stylesheet': 'reserved',
+       'submenuObjSuffixes': 'reserved',
+       'subMenuOffset': 'reserved',
+       'submit': 'reserved',
+       'subst_elementUid': 'reserved',
+       'substMarksSeparately': 'reserved',
+       'substring': 'reserved',
+       'swirl': 'reserved',
+       'sword': 'reserved',
+       'sword_noMixedCase': 'reserved',
+       'SWORD_PARAMS': 'reserved',
+       'sword_standAlone': 'reserved',
+       'sys_language_mode': 'reserved',
+       'sys_language_overlay': 'reserved',
+       'sys_language_softMergeIfNotBlank': 'reserved',
+       'sys_language_uid': 'reserved',
+       'table': 'reserved',
+       'tableCellColor': 'reserved',
+       'tableParams': 'reserved',
+       'tables': 'reserved',
+       'tableStdWrap': 'reserved',
+       'tableStyle': 'reserved',
+       'tableWidth': 'reserved',
+       'tags': 'reserved',
+       'target': 'reserved',
+       'TDparams': 'reserved',
+       'templateContent': 'reserved',
+       'templateFile': 'reserved',
+       'text': 'reserved',
+       'textarea': 'reserved',
+       'textMargin': 'reserved',
+       'textMargin_outOfText': 'reserved',
+       'textMaxLength': 'reserved',
+       'textObjNum': 'reserved',
+       'textPos': 'reserved',
+       'textStyle': 'reserved',
+       'thickness': 'reserved',
+       'thumbnailsByDefault': 'reserved',
+       'tile': 'reserved',
+       'time_stdWrap': 'reserved',
+       'tipafriendLib': 'reserved',
+       'title': 'reserved',
+       'titleLen': 'reserved',
+       'titleTagFunction': 'reserved',
+       'titleText': 'reserved',
+       'tm': 'reserved',
+       'token': 'reserved',
+       'topOffset': 'reserved',
+       'totalWidth': 'reserved',
+       'transparentBackground': 'reserved',
+       'transparentColor': 'reserved',
+       'trim': 'reserved',
+       'tsdebug_tree': 'reserved',
+       'type': 'reserved',
+       'typeNum': 'reserved',
+       'types': 'reserved',
+       'typolinkCheckRootline': 'reserved',
+       'uidInList': 'reserved',
+       'unset': 'reserved',
+       'uploadFieldsInTopOfEB': 'reserved',
+       'uploads': 'reserved',
+       'upper': 'reserved',
+       'useCacheHash': 'reserved',
+       'useLargestItemX': 'reserved',
+       'useLargestItemY': 'reserved',
+       'user': 'reserved',
+       'userdefined': 'reserved',
+       'userfunction': 'reserved',
+       'userid': 'reserved',
+       'userIdColumn': 'reserved',
+       'USERNAME_substToken': 'reserved',
+       'userProc': 'reserved',
+       'value': 'reserved',
+       'valueArray': 'reserved',
+       'wave': 'reserved',
+       'where': 'reserved',
+       'width': 'reserved',
+       'wiz': 'reserved',
+       'wordSpacing': 'reserved',
+       'workArea': 'reserved',
+       'wrap': 'reserved',
+       'wrap1': 'reserved',
+       'wrap2': 'reserved',
+       'wrap3': 'reserved',
+       'wrapAfterTags': 'reserved',
+       'wrapAlign': 'reserved',
+       'wrapFieldName': 'reserved',
+       'wrapItemAndSub': 'reserved',
+       'wrapNonWrappedLines': 'reserved',
+       'wraps': 'reserved',
+       'xhtml_cleaning': 'reserved',
+       'xmlprologue': 'reserved',
+       'xPosOffset': 'reserved',
+       'yPosOffset': 'reserved',
+
+       'admPanel': 'keyword2',
+       'alt_print': 'keyword2',
+       'auth': 'keyword2',
+       'browser': 'keyword2',
+       'cache': 'keyword2',
+       'CHECK': 'keyword2',
+       'cObj': 'keyword2',
+       'cObject': 'keyword2',
+       'COMMENT': 'keyword2',
+       'config': 'keyword2',
+       'content': 'keyword2',
+       'copy': 'keyword2',
+       'CSS_inlineStyle': 'keyword2',
+       'cut': 'keyword2',
+       'dataArray': 'keyword2',
+       'dayofmonth': 'keyword2',
+       'dayofweek': 'keyword2',
+       'db_list': 'keyword2',
+       'device': 'keyword2',
+       'dynCSS': 'keyword2',
+       'edit': 'keyword2',
+       'edit_access': 'keyword2',
+       'edit_pageheader': 'keyword2',
+       'folder': 'keyword2',
+       'folderTree': 'keyword2',
+       'foldoutMenu': 'keyword2',
+       'Functions': 'keyword2',
+       'gmenu_foldout': 'keyword2',
+       'gmenu_layers': 'keyword2',
+       'hostname': 'keyword2',
+       'hour': 'keyword2',
+       'imgList': 'keyword2',
+       'imgResource': 'keyword2',
+       'imgText': 'keyword2',
+       'info': 'keyword2',
+       'IP': 'keyword2',
+       'jsmenu': 'keyword2',
+       'JSwindow': 'keyword2',
+       'LABEL': 'keyword2',
+       'layout': 'keyword2',
+       'lib': 'keyword2',
+       'loginUser': 'keyword2',
+       'marks': 'keyword2',
+       'minute': 'keyword2',
+       'mod': 'keyword2',
+       'module': 'keyword2',
+       'month': 'keyword2',
+       'move_wizard': 'keyword2',
+       'new': 'keyword2',
+       'new_wizard': 'keyword2',
+       'noResultObj': 'keyword2',
+       'numRows': 'keyword2',
+       'options': 'keyword2',
+       'page': 'keyword2',
+       'pageTree': 'keyword2',
+       'paste': 'keyword2',
+       'perms': 'keyword2',
+       'PIDinRootline': 'keyword2',
+       'PIDupinRootline': 'keyword2',
+       'plugin': 'keyword2',
+       'postform': 'keyword2',
+       'postform_newThread': 'keyword2',
+       'preview': 'keyword2',
+       'publish': 'keyword2',
+       'RADIO': 'keyword2',
+       'renderObj': 'keyword2',
+       'REQ': 'keyword2',
+       'RTE': 'keyword2',
+       'RTE_compliant': 'keyword2',
+       'select': 'keyword2',
+       'setup': 'keyword2',
+       'split': 'keyword2',
+       'stat': 'keyword2',
+       'stat_apache': 'keyword2',
+       'stat_apache_logfile': 'keyword2',
+       'stat_apache_noHost': 'keyword2',
+       'stat_apache_notExtended': 'keyword2',
+       'stat_apache_pagenames': 'keyword2',
+       'stat_excludeBEuserHits': 'keyword2',
+       'stat_excludeIPList': 'keyword2',
+       'stat_mysql': 'keyword2',
+       'stat_titleLen': 'keyword2',
+       'stat_typeNumList': 'keyword2',
+       'stdWrap': 'keyword2',
+       'subparts': 'keyword2',
+       'system': 'keyword2',
+       'temp': 'keyword2',
+       'template': 'keyword2',
+       'treeLevel': 'keyword2',
+       'tsdebug': 'keyword2',
+       'typolink': 'keyword2',
+       'url': 'keyword2',
+       'useragent': 'keyword2',
+       'userFunc': 'keyword2',
+       'version': 'keyword2',
+       'view': 'keyword2',
+       'workOnSubpart': 'keyword2',
+
+       'ACT': 'keyword3',
+       'ACTIFSUB': 'keyword3',
+       'ACTIFSUBRO': 'keyword',
+       'ACTRO': 'keyword3',
+       'all': 'keyword3',
+       'arrowACT': 'keyword3',
+       'arrowNO': 'keyword3',
+       'ascii': 'keyword3',
+       'atLeast': 'keyword3',
+       'atMost': 'keyword3',
+       'BE': 'keyword3',
+       'be_groups': 'keyword3',
+       'be_users': 'keyword3',
+       'BOX': 'keyword3',
+       'browse': 'keyword3',
+       'bullets': 'keyword3',
+       'CUR': 'keyword3',
+       'CURIFSUB': 'keyword3',
+       'CURIFSUBRO': 'keyword3',
+       'CURRO': 'keyword3',
+       'default': 'keyword3',
+       'description': 'keyword3',
+       'directory': 'keyword3',
+       'directReturn': 'keyword3',
+       'div': 'keyword3',
+       'else': 'keyword3',
+       'email': 'keyword3',
+       'end': 'keyword3',
+       'equals': 'keyword3',
+       'external': 'keyword3',
+       'false': 'keyword3',
+       'FE': 'keyword3',
+       'fe_groups': 'keyword3',
+       'fe_users': 'keyword3',
+       'feadmin': 'keyword3',
+       'header': 'keyword3',
+       'html': 'keyword3',
+       'id': 'keyword3',
+       'if': 'keyword3',
+       'ifEmpty': 'keyword3',
+       'IFSUB': 'keyword3',
+       'IFSUBRO': 'keyword3',
+       'image': 'keyword3',
+       'inBranch': 'keyword3',
+       'isFalse': 'keyword3',
+       'isGreaterThan': 'keyword3',
+       'isInList': 'keyword3',
+       'isLessThan': 'keyword3',
+       'isPositive': 'keyword3',
+       'isTrue': 'keyword3',
+       'keyword3': 'keyword3',
+       'language': 'keyword3',
+       'leveltitle': 'keyword3',
+       'list': 'keyword3',
+       'login': 'keyword3',
+       'mailform': 'keyword3',
+       'media': 'keyword3',
+       'menu': 'keyword3',
+       'mod': 'keyword3',
+       'multimedia': 'keyword3',
+       'negate': 'keyword3',
+       'NEW': 'keyword3',
+       'NO': 'keyword3',
+       'none': 'keyword3',
+       'pages': 'keyword3',
+       'pages_language_overlay': 'keyword3',
+       'parseFunc_RTE': 'keyword3',
+       'pid': 'keyword3',
+       'required': 'keyword3',
+       'RO': 'keyword3',
+       'rootline': 'keyword3',
+       'script': 'keyword3',
+       'search': 'keyword3',
+       'shortcut': 'keyword3',
+       'sitemap': 'keyword3',
+       'SPC': 'keyword3',
+       'splash': 'keyword3',
+       'sys_dmail': 'keyword3',
+       'sys_domain': 'keyword3',
+       'sys_filemounts': 'keyword3',
+       'sys_note': 'keyword3',
+       'sys_template': 'keyword3',
+       'tabel': 'keyword3',
+       'text': 'keyword3',
+       'textpic': 'keyword3',
+       'this': 'keyword3',
+       'top': 'keyword3',
+       'true': 'keyword3',
+       'twice': 'keyword3',
+       'uid': 'keyword3',
+       'uniqueGlobal': 'keyword3',
+       'uniqueLocal': 'keyword3',
+       'unsetEmpty': 'keyword3',
+       'updated': 'keyword3',
+       'uploads': 'keyword3',
+       'us': 'keyword3',
+       'user_task': 'keyword3',
+       'USERDEF1': 'keyword3',
+       'USERDEF1RO': 'keyword3',
+       'USERDEF2': 'keyword3',
+       'USERDEF2RO': 'keyword3',
+       'usergroup': 'keyword3',
+       'USR': 'keyword3',
+       'USRRO': 'keyword3',
+       'web_func': 'keyword3',
+       'web_info': 'keyword3',
+       'web_layout': 'keyword3',
+       'web_list': 'keyword3',
+       'web_ts': 'keyword',
+       'xhtml_strict': 'keyword3',
+       'xhtml_trans': 'keyword3',
+       'XY': 'keyword3',
+       'ypMenu': 'keyword3'
+}
+
+var tokenizeTypoScript = function() {
+
+       // Some helper regexp matchers.
+       var isOperatorChar = matcher(/[\+\-\*\&\%\/=<>!\?]/);
+       var isDigit = matcher(/[0-9]/);
+       var isHexDigit = matcher(/[0-9A-Fa-f]/);
+       var isWordChar = matcher(/[\w\$_]/);
+
+       function isWhiteSpace(ch) {
+               // Unfortunately, IE's regexp matcher thinks non-breaking spaces
+               // aren't whitespace. Also, in our scheme newlines are no
+               // whitespace (they are another special case).
+               return ch != "\n" && (ch == nbsp || /\s/.test(ch));
+       }
+
+       // This function produces a MochiKit-style iterator that tokenizes
+       // the output of the given stringstream (see stringstream.js).
+       // Tokens are objects with a type, style, and value property. The
+       // value contains the textual content of the token. Because this may
+       // include trailing whitespace (for efficiency reasons), some
+       // tokens, such a variable names, also have a name property
+       // containing their actual textual value.
+       return function(source) {
+               // Produce a value to return. Automatically skips and includes any
+               // whitespace. The base argument is prepended to the value
+               // property and assigned to the name property -- this is used when
+               // the caller has already extracted the text from the stream
+               // himself.
+               function result(type, style, base) {
+                       // nextWhile(isWhiteSpace); - comment thats line because needed for autocomplete
+                       var value = {
+                               type: type,
+                               style: style,
+                               value: (base ? base + source.get() : source.get())
+                       };
+                       if (base) {
+                               value.name = base;
+                       }
+                       return value;
+               }
+
+               // Advance the text stream over characters for which test returns
+               // true. (The characters that are 'consumed' like this can later
+               // be retrieved by calling source.get()).
+               function nextWhile(test) {
+                       var next;
+                       while ((next = source.peek()) && test(next)) {
+                               source.next();
+                       }
+               }
+
+               // Advance the stream until the given character (not preceded by a
+               // backslash) is encountered (or a newline is found).
+               function nextUntilUnescaped(end) {
+                       var escaped = false;
+                       var next;
+                       while ((next = source.peek()) && next != "\n") {
+                               source.next();
+                               if (next == end && !escaped) {
+                                       break;
+                               }
+                               escaped = next == "\\";
+                       }
+               }
+
+               function readHexNumber() {
+                       source.next();
+                       // skip the 'x'
+                       nextWhile(isHexDigit);
+                       return result("number", "atom");
+               }
+
+               function readNumber() {
+                       nextWhile(isDigit);
+                       if (source.peek() == ".") {
+                               source.next();
+                               nextWhile(isDigit);
+                       }
+
+                       if (source.peek() == "e" || source.peek() == "E") {
+                               source.next();
+                               if (source.peek() == "-") {
+                                       source.next();
+                               }
+                               nextWhile(isDigit);
+                       }
+                       return result("number", "atom");
+               }
+
+               // Read a word, look it up in keywords. If not found, it is a
+               // variable, otherwise it is a keyword of the type found.
+               function readWord() {
+                       nextWhile(isWordChar);
+                       var word = source.get();
+                       var known = typoscriptWords.hasOwnProperty(word) && {
+                               type: 'keyword',
+                               style: typoscriptWords[word]
+                       };
+                       return known ?
+                               result(known.type, known.style, word) :
+                               result("variable", "other", word);
+               }
+
+               function readRegexp() {
+                       nextUntilUnescaped("/");
+                       nextWhile(matcher(/[gi]/));
+                       return result("regexp", "string");
+               }
+
+               // Mutli-line comments are tricky. We want to return the newlines
+               // embedded in them as regular newline tokens, and then continue
+               // returning a comment token for every line of the comment. So
+               // some state has to be saved (inComment) to indicate whether we
+               // are inside a /* */ sequence.
+               function readMultilineComment(start) {
+                       this.inComment = true;
+                       var maybeEnd = (start == "*");
+                       while (true) {
+                               var next = source.peek();
+                               if (next == "\n") {
+                                       break;
+                               }
+                               source.next();
+                               if (next == "/" && maybeEnd) {
+                                       this.inComment = false;
+                                       break;
+                               }
+                               maybeEnd = (next == "*");
+                       }
+
+                       return result("comment", "ts-comment");
+               }
+
+               // Fetch the next token. Dispatches on first character in the
+               // stream, or first two characters when the first is a slash. The
+               // || things are a silly trick to keep simple cases on a single
+               // line.
+               function next() {
+                       var token = null;
+                       var ch = source.next();
+                       if (ch == "\n") {
+                               token = {
+                                       type: "newline",
+                                       style: "whitespace",
+                                       value: source.get()
+                               };
+
+                       } else if (this.inComment) {
+                               token = readMultilineComment.call(this, ch);
+
+                       } else if (this.inValue) {
+                               token = nextUntilUnescaped(null) || {
+                                       type: "value",
+                                       style: "ts-value",
+                                       value: source.get()
+                               };
+                               this.inValue = false;
+
+                       } else if (isWhiteSpace(ch)) {
+                               token = nextWhile(isWhiteSpace) || result("whitespace", "whitespace");
+
+                       } else if (ch == "\"" || ch == "'") {
+                               token = nextUntilUnescaped(ch) || result("string", "string");
+
+                       } else if (ch == "<"
+                         || ch == ">"
+                         || ch == "=") {
+                               this.inValue = true;
+                               token = result(ch, "ts-operator");
+
+                       } else if (ch == "[") {
+                               token = nextUntilUnescaped("]") || result("condition", "ts-condition");
+
+                       // with punctuation, the type of the token is the symbol itself
+                       } else if (/[\[\]\(\),;\:\.\<\>\=]/.test(ch)) {
+                               token = result(ch, "ts-operator");
+
+                       } else if (ch == "{") {
+                               token = result(ch, "ts-operator curly-bracket-open");
+
+                       } else if (ch == "}") {
+                               token = result(ch, "ts-operator curly-bracket-close");
+
+                       } else if (ch == "0" && (source.peek() == "x" || source.peek() == "X")) {
+                               token = readHexNumber();
+
+                       } else if (isDigit(ch)) {
+                               token = readNumber();
+
+                       } else if (ch == "/") {
+                               next = source.peek();
+
+                               if (next == "*") {
+                                       token = readMultilineComment.call(this, ch);
+
+                               } else if (next == "/") {
+                                       token = nextUntilUnescaped(null) || result("comment", "ts-comment");
+
+                               } else if (this.regexp) {
+                                       token = readRegexp();
+
+                               } else {
+                                       token = nextWhile(isOperatorChar) || result("operator", "ts-operator");
+                               }
+
+                       } else if (ch == "#") {
+                               token = nextUntilUnescaped(null) || result("comment", "ts-comment");
+
+                       } else if (isOperatorChar(ch)) {
+                               token = nextWhile(isOperatorChar) || result("operator", "ts-operator");
+
+                       } else {
+                               token = readWord();
+                       }
+
+                       // JavaScript's syntax rules for when a slash might be the start
+                       // of a regexp and when it is just a division operator are kind
+                       // of non-obvious. This decides, based on the current token,
+                       // whether the next token could be a regular expression.
+                       if (token.style != "whitespace" && token != "comment") {
+                               this.regexp = token.type == "operator" || token.type == "keyword c" || token.type.match(/[\[{}\(,;:]/);
+                       }
+                       return token;
+               }
+
+               // Wrap it in an iterator. The state (regexp and inComment) is
+               // exposed because a parser will need to save it when making a
+               // copy of its state.
+               return {
+                       next: next,
+                       regexp: true,
+                       inComment: false,
+                       inValue: false
+               };
+       }
+} ();
\ No newline at end of file
diff --git a/typo3/sysext/t3editor/jslib/codemirror/undo.js b/typo3/sysext/t3editor/jslib/codemirror/undo.js
new file mode 100644 (file)
index 0000000..440056c
--- /dev/null
@@ -0,0 +1,377 @@
+/**
+ * Storage and control for undo information within a CodeMirror
+ * editor. 'Why on earth is such a complicated mess required for
+ * that?', I hear you ask. The goal, in implementing this, was to make
+ * the complexity of storing and reverting undo information depend
+ * only on the size of the edited or restored content, not on the size
+ * of the whole document. This makes it necessary to use a kind of
+ * 'diff' system, which, when applied to a DOM tree, causes some
+ * complexity and hackery.
+ *
+ * In short, the editor 'touches' BR elements as it parses them, and
+ * the History stores these. When nothing is touched in commitDelay
+ * milliseconds, the changes are committed: It goes over all touched
+ * nodes, throws out the ones that did not change since last commit or
+ * are no longer in the document, and assembles the rest into zero or
+ * more 'chains' -- arrays of adjacent lines. Links back to these
+ * chains are added to the BR nodes, while the chain that previously
+ * spanned these nodes is added to the undo history. Undoing a change
+ * means taking such a chain off the undo history, restoring its
+ * content (text is saved per line) and linking it back into the
+ * document.
+ */
+
+// A history object needs to know about the DOM container holding the
+// document, the maximum amount of undo levels it should store, the
+// delay (of no input) after which it commits a set of changes, and,
+// unfortunately, the 'parent' window -- a window that is not in
+// designMode, and on which setTimeout works in every browser.
+function History(container, maxDepth, commitDelay, editor) {
+  this.container = container;
+  this.maxDepth = maxDepth; this.commitDelay = commitDelay;
+  this.editor = editor;
+  this.parent = editor.parent;
+  // This line object represents the initial, empty editor.
+  var initial = {text: "", from: null, to: null};
+  // As the borders between lines are represented by BR elements, the
+  // start of the first line and the end of the last one are
+  // represented by null. Since you can not store any properties
+  // (links to line objects) in null, these properties are used in
+  // those cases.
+  this.first = initial; this.last = initial;
+  // Similarly, a 'historyTouched' property is added to the BR in
+  // front of lines that have already been touched, and 'firstTouched'
+  // is used for the first line.
+  this.firstTouched = false;
+  // History is the set of committed changes, touched is the set of
+  // nodes touched since the last commit.
+  this.history = []; this.redoHistory = []; this.touched = [];
+}
+
+History.prototype = {
+  // Mark a node as touched. Null is a valid argument.
+  touch: function(node) {
+    this.setTouched(node);
+    // Schedule a commit (if no other touches come in for commitDelay
+    // milliseconds).
+    this.parent.clearTimeout(this.commitTimeout);
+    this.commitTimeout = this.parent.setTimeout(method(this, "commit"), this.commitDelay);
+  },
+
+  // Undo the last change.
+  undo: function() {
+    // Make sure pending changes have been committed.
+    this.commit();
+
+    if (this.history.length)
+      // Take the top diff from the history, apply it, and store its
+      // shadow in the redo history.
+      this.redoHistory.push(this.updateTo(this.history.pop(), "applyChain"));
+  },
+
+  // Redo the last undone change.
+  redo: function() {
+    this.commit();
+    if (this.redoHistory.length)
+      // The inverse of undo, basically.
+      this.addUndoLevel(this.updateTo(this.redoHistory.pop(), "applyChain"));
+  },
+
+  // Push a changeset into the document.
+  push: function(from, to, lines) {
+    var chain = [];
+    for (var i = 0; i < lines.length; i++) {
+      var end = (i == lines.length - 1) ? to : this.container.ownerDocument.createElement("BR");
+      chain.push({from: from, to: end, text: lines[i]});
+      from = end;
+    }
+    this.pushChains([chain]);
+  },
+
+  pushChains: function(chains) {
+    this.commit();
+    this.addUndoLevel(this.updateTo(chains, "applyChain"));
+    this.redoHistory = [];
+  },
+
+  // Clear the undo history, make the current document the start
+  // position.
+  reset: function() {
+    this.commit();
+    this.history = []; this.redoHistory = [];
+  },
+
+  textAfter: function(br) {
+    return this.after(br).text;
+  },
+
+  nodeAfter: function(br) {
+    return this.after(br).to;
+  },
+
+  nodeBefore: function(br) {
+    return this.before(br).from;
+  },
+
+  // Check whether the touched nodes hold any changes, if so, commit
+  // them.
+  commit: function() {
+    this.parent.clearTimeout(this.commitTimeout);
+    // Make sure there are no pending dirty nodes.
+    this.editor.highlightDirty(true);
+    // Build set of chains.
+    var chains = this.touchedChains(), self = this;
+    
+    if (chains.length) {
+      this.addUndoLevel(this.updateTo(chains, "linkChain"));
+      this.redoHistory = [];
+    }
+  },
+
+  // [ end of public interface ]
+
+  // Update the document with a given set of chains, return its
+  // shadow. updateFunc should be "applyChain" or "linkChain". In the
+  // second case, the chains are taken to correspond the the current
+  // document, and only the state of the line data is updated. In the
+  // first case, the content of the chains is also pushed iinto the
+  // document.
+  updateTo: function(chains, updateFunc) {
+    var shadows = [], dirty = [];
+    for (var i = 0; i < chains.length; i++) {
+      shadows.push(this.shadowChain(chains[i]));
+      dirty.push(this[updateFunc](chains[i]));
+    }
+    if (updateFunc == "applyChain")
+      this.notifyDirty(dirty);
+    return shadows;
+  },
+
+  // Notify the editor that some nodes have changed.
+  notifyDirty: function(nodes) {
+    forEach(nodes, method(this.editor, "addDirtyNode"))
+    this.editor.scheduleHighlight();
+  },
+
+  // Link a chain into the DOM nodes (or the first/last links for null
+  // nodes).
+  linkChain: function(chain) {
+    for (var i = 0; i < chain.length; i++) {
+      var line = chain[i];
+      if (line.from) line.from.historyAfter = line;
+      else this.first = line;
+      if (line.to) line.to.historyBefore = line;
+      else this.last = line;
+    }
+  },
+
+  // Get the line object after/before a given node.
+  after: function(node) {
+    return node ? node.historyAfter : this.first;
+  },
+  before: function(node) {
+    return node ? node.historyBefore : this.last;
+  },
+
+  // Mark a node as touched if it has not already been marked.
+  setTouched: function(node) {
+    if (node) {
+      if (!node.historyTouched) {
+        this.touched.push(node);
+        node.historyTouched = true;
+      }
+    }
+    else {
+      this.firstTouched = true;
+    }
+  },
+
+  // Store a new set of undo info, throw away info if there is more of
+  // it than allowed.
+  addUndoLevel: function(diffs) {
+    this.history.push(diffs);
+    if (this.history.length > this.maxDepth)
+      this.history.shift();
+  },
+
+  // Build chains from a set of touched nodes.
+  touchedChains: function() {
+    var self = this;
+    // Compare two strings, treating nbsps as spaces.
+    function compareText(a, b) {
+      return a.replace(/\u00a0/g, " ") == b.replace(/\u00a0/g, " ");
+    }
+
+    // The temp system is a crummy hack to speed up determining
+    // whether a (currently touched) node has a line object associated
+    // with it. nullTemp is used to store the object for the first
+    // line, other nodes get it stored in their historyTemp property.
+    var nullTemp = null;
+    function temp(node) {return node ? node.historyTemp : nullTemp;}
+    function setTemp(node, line) {
+      if (node) node.historyTemp = line;
+      else nullTemp = line;
+    }
+
+    // Filter out unchanged lines and nodes that are no longer in the
+    // document. Build up line objects for remaining nodes.
+    var lines = [];
+    if (self.firstTouched) self.touched.push(null);
+    forEach(self.touched, function(node) {
+      if (node) {
+        node.historyTouched = false;
+        if (node.parentNode != self.container)
+          return;
+      }
+      else {
+        self.firstTouched = false;
+      }
+
+      var text = [];
+      for (var cur = node ? node.nextSibling : self.container.firstChild;
+           cur && cur.nodeName != "BR"; cur = cur.nextSibling)
+        if (cur.currentText) text.push(cur.currentText);
+
+      var line = {from: node, to: cur, text: text.join("")};
+      var shadow = self.after(node);
+      if (!shadow || !compareText(shadow.text, line.text) || shadow.to != line.to) {
+        lines.push(line);
+        setTemp(node, line);
+      }
+    });
+
+    // Get the BR element after/before the given node.
+    function nextBR(node, dir) {
+      var link = dir + "Sibling", search = node[link];
+      while (search && search.nodeName != "BR")
+        search = search[link];
+      return search;
+    }
+
+    // Assemble line objects into chains by scanning the DOM tree
+    // around them.
+    var chains = []; self.touched = [];
+    forEach(lines, function(line) {
+      // Note that this makes the loop skip line objects that have
+      // been pulled into chains by lines before them.
+      if (!temp(line.from)) return;
+
+      var chain = [], curNode = line.from;
+      // Put any line objects (referred to by temp info) before this
+      // one on the front of the array.
+      while (true) {
+        var curLine = temp(curNode);
+        if (!curLine) break;
+        chain.unshift(curLine);
+        setTemp(curNode, null);
+        if (!curNode) break;
+        curNode = nextBR(curNode, "previous");
+      }
+      curNode = line.to;
+      // Add lines after this one at end of array.
+      while (true) {
+        var curLine = temp(curNode);
+        if (!curLine || !curNode) break;
+        chain.push(curLine);
+        setTemp(curNode, null);
+        curNode = nextBR(curNode, "next");
+      }
+
+      // Chains that can not determine a valid 'shadow' -- a chain
+      // currently stored in the DOM tree that has the same start and
+      // end point -- are put back into the touched set, hoping they
+      // will be valid next time.
+      if (self.after(chain[0].from) && self.before(chain[chain.length - 1].to))
+        chains.push(chain);
+      else
+        forEach(chain, function(line) {self.setTouched(line.from);});
+    });
+
+    return chains;
+  },
+
+  recordChange: function(shadows, chains) {
+    if (this.onChange)
+      this.onChange(shadows, chains);
+  },
+
+  // Find the 'shadow' of a given chain by following the links in the
+  // DOM nodes at its start and end.
+  shadowChain: function(chain) {
+    var shadows = [], next = this.after(chain[0].from), end = chain[chain.length - 1].to;
+    while (true) {
+      shadows.push(next);
+      var nextNode = next.to;
+      if (!nextNode || nextNode == end)
+        break;
+      else
+        next = nextNode.historyAfter;
+    }
+    return shadows;
+  },
+
+  // Update the DOM tree to contain the lines specified in a given
+  // chain, link this chain into the DOM nodes.
+  applyChain: function(chain) {
+    // Some attempt is made to prevent the cursor from jumping
+    // randomly when an undo or redo happens. It still behaves a bit
+    // strange sometimes.
+    var cursor =&