You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lenya.apache.org by an...@apache.org on 2009/01/29 10:49:42 UTC

svn commit: r738808 [1/3] - in /lenya/trunk: legal/ src/modules/editors/config/ src/modules/editors/resources/codemirror/ src/modules/editors/resources/codemirror/0.60/ src/modules/editors/resources/codemirror/0.60/css/ src/modules/editors/resources/co...

Author: andreas
Date: Thu Jan 29 09:49:40 2009
New Revision: 738808

URL: http://svn.apache.org/viewvc?rev=738808&view=rev
Log:
Adding CodeMirror syntax highlighting for source editor. See bug 46611.

Added:
    lenya/trunk/legal/codemirror.license.txt
    lenya/trunk/src/modules/editors/resources/codemirror/
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/csscolors.css
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/docs.css
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/jscolors.css
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/people.jpg   (with props)
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/sparqlcolors.css
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/xmlcolors.css
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/csstest.html
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/htmltest.html
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/index.html
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/codemirror.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/editor.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/mirrorframe.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsecss.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsehtmlmixed.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsejavascript.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsesparql.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsexml.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/select.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/stringstream.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/tokenize.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/tokenizejavascript.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/undo.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/util.js
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/jstest.html
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/manual.html
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/mixedtest.html
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/sparqltest.html
    lenya/trunk/src/modules/editors/resources/codemirror/0.60/story.html
    lenya/trunk/src/modules/editors/resources/css/
    lenya/trunk/src/modules/editors/resources/css/codemirror.css
    lenya/trunk/src/modules/editors/resources/css/sourceEditor.css
    lenya/trunk/src/modules/editors/resources/javascript/sourceEditor/
    lenya/trunk/src/modules/editors/resources/javascript/sourceEditor/codemirror.js
    lenya/trunk/src/modules/editors/resources/javascript/sourceEditor/lenya_glue.js
Removed:
    lenya/trunk/src/modules/editors/resources/javascript/oneform_lenya_glue.js
Modified:
    lenya/trunk/src/modules/editors/config/module.xml
    lenya/trunk/src/modules/editors/usecases/forms/oneform.jx
    lenya/trunk/src/modules/editors/usecases/forms/oneform.xsl

Added: lenya/trunk/legal/codemirror.license.txt
URL: http://svn.apache.org/viewvc/lenya/trunk/legal/codemirror.license.txt?rev=738808&view=auto
==============================================================================
--- lenya/trunk/legal/codemirror.license.txt (added)
+++ lenya/trunk/legal/codemirror.license.txt Thu Jan 29 09:49:40 2009
@@ -0,0 +1,23 @@
+ Copyright (c) 2007-2008 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

Modified: lenya/trunk/src/modules/editors/config/module.xml
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/config/module.xml?rev=738808&r1=738807&r2=738808&view=diff
==============================================================================
--- lenya/trunk/src/modules/editors/config/module.xml (original)
+++ lenya/trunk/src/modules/editors/config/module.xml Thu Jan 29 09:49:40 2009
@@ -25,6 +25,7 @@
   <depends module="org.apache.lenya.modules.sitemanagement"/>
   <depends module="org.apache.lenya.modules.linking"/>
   <depends module="org.apache.lenya.modules.workflow"/>
+  <depends module="org.apache.lenya.modules.codemirror"/>
   <package>org.apache.lenya.modules</package>
   <version>0.3-dev</version>
   <name>Editors</name>

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/csscolors.css
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/csscolors.css?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/csscolors.css (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/csscolors.css Thu Jan 29 09:49:40 2009
@@ -0,0 +1,47 @@
+.editbox {
+  margin: .4em;
+  padding: 0;
+  font-family: monospace;
+  font-size: 10pt;
+  color: black;
+}
+
+pre.code, .editbox {
+  color: #666666;
+}
+
+.editbox p {
+  margin: 0;
+}
+
+span.css-at {
+  color: #770088;
+}
+
+span.css-unit {
+  color: #228811;
+}
+
+span.css-value {
+  color: #770088;
+}
+
+span.css-identifier {
+  color: black;
+}
+
+span.css-important {
+  color: #0000FF;
+}
+
+span.css-colorcode {
+  color: #004499;
+}
+
+span.css-comment {
+  color: #AA7700;
+}
+
+span.css-string {
+  color: #AA2222;
+}

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/docs.css
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/docs.css?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/docs.css (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/docs.css Thu Jan 29 09:49:40 2009
@@ -0,0 +1,42 @@
+body {
+  margin: 0;
+  font-family: tahoma, arial, sans-serif;
+  padding: 3em 6em;
+  color: black;
+}
+
+h1 {
+  font-size: 22pt;
+}
+
+h2 {
+  font-size: 14pt;
+}
+
+p.rel {
+  padding-left: 2em;
+  text-indent: -2em;
+}
+
+div.border {
+  border: 1px solid black;
+  padding: 3px;
+}
+
+code {
+  font-family: courier, monospace;
+  font-size: 90%;
+  color: #155;
+}
+
+pre.code {
+  margin: 1.1em 12px;
+  border: 1px solid #CCCCCC;
+  color: black;
+  padding: .4em;
+  font-family: courier, monospace;
+}
+
+.warn {
+  color: #C00;
+}

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/jscolors.css
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/jscolors.css?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/jscolors.css (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/jscolors.css Thu Jan 29 09:49:40 2009
@@ -0,0 +1,55 @@
+.editbox {
+  margin: .4em;
+  padding: 0;
+  font-family: monospace;
+  font-size: 10pt;
+  color: black;
+}
+
+pre.code, .editbox {
+  color: #666666;
+}
+
+.editbox p {
+  margin: 0;
+}
+
+span.js-punctuation {
+  color: #666666;
+}
+
+span.js-operator {
+  color: #666666;
+}
+
+span.js-keyword {
+  color: #770088;
+}
+
+span.js-atom {
+  color: #228811;
+}
+
+span.js-variable {
+  color: black;
+}
+
+span.js-variabledef {
+  color: #0000FF;
+}
+
+span.js-localvariable {
+  color: #004499;
+}
+
+span.js-property {
+  color: black;
+}
+
+span.js-comment {
+  color: #AA7700;
+}
+
+span.js-string {
+  color: #AA2222;
+}

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/people.jpg
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/people.jpg?rev=738808&view=auto
==============================================================================
Binary file - no diff available.

Propchange: lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/people.jpg
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/sparqlcolors.css
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/sparqlcolors.css?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/sparqlcolors.css (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/sparqlcolors.css Thu Jan 29 09:49:40 2009
@@ -0,0 +1,39 @@
+.editbox {
+  margin: .4em;
+  padding: 0;
+  font-family: monospace;
+  font-size: 10pt;
+  color: black;
+}
+
+.editbox p {
+  margin: 0;
+}
+
+span.sp-keyword {
+  color: #708;
+}
+
+span.sp-prefixed {
+  color: #5d1;
+}
+
+span.sp-var {
+  color: #00c;
+}
+
+span.sp-comment {
+  color: #a70;
+}
+
+span.sp-literal {
+  color: #a22;
+}
+
+span.sp-uri {
+  color: #292;
+}
+
+span.sp-operator {
+  color: #088;
+}

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/xmlcolors.css
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/xmlcolors.css?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/xmlcolors.css (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/css/xmlcolors.css Thu Jan 29 09:49:40 2009
@@ -0,0 +1,51 @@
+.editbox {
+  margin: .4em;
+  padding: 0;
+  font-family: monospace;
+  font-size: 10pt;
+  color: black;
+}
+
+.editbox p {
+  margin: 0;
+}
+
+span.xml-tagname {
+  color: #A0B;
+}
+
+span.xml-attribute {
+  color: #281;
+}
+
+span.xml-punctuation {
+  color: black;
+}
+
+span.xml-attname {
+  color: #00F;
+}
+
+span.xml-comment {
+  color: #A70;
+}
+
+span.xml-cdata {
+  color: #48A;
+}
+
+span.xml-processing {
+  color: #999;
+}
+
+span.xml-entity {
+  color: #A22;
+}
+
+span.xml-error {
+  color: #F00;
+}
+
+span.xml-text {
+  color: black;
+}

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/csstest.html
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/csstest.html?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/csstest.html (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/csstest.html Thu Jan 29 09:49:40 2009
@@ -0,0 +1,60 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <script src="js/codemirror.js" type="text/javascript"></script>
+    <title>CodeMirror: CSS demonstration</title>
+    <link rel="stylesheet" type="text/css" href="css/docs.css"/>
+  </head>
+  <body style="padding: 20px;">
+
+<p>Demonstration of <a href="index.html">CodeMirror</a>'s CSS
+highlighter.</p>
+
+<div class="border">
+<textarea id="code" cols="120" rows="30">
+/* Some example CSS */
+
+@import url("something.css");
+
+body {
+  margin: 0;
+  padding: 3em 6em;
+  font-family: tahoma, arial, sans-serif;
+  color: #000;
+}
+
+#navigation a {
+  font-weight: bold;
+  text-decoration: none !important;
+}
+
+h1 {
+  font-size: 2.5em;
+}
+
+h2 {
+  font-size: 1.7em;
+}
+
+h1:before, h2:before {
+  content: "::";
+}
+
+code {
+  font-family: courier, monospace;
+  font-size: 80%;
+  color: #418A8A;
+}
+</textarea>
+</div>
+
+<script type="text/javascript">
+  var editor = CodeMirror.fromTextArea('code', {
+    height: "350px",
+    parserfile: "parsecss.js",
+    stylesheet: "css/csscolors.css",
+    path: "js/"
+  });
+</script>
+
+  </body>
+</html>

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/htmltest.html
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/htmltest.html?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/htmltest.html (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/htmltest.html Thu Jan 29 09:49:40 2009
@@ -0,0 +1,39 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <script src="js/codemirror.js" type="text/javascript"></script>
+    <title>CodeMirror: HTML/XML demonstration</title>
+    <link rel="stylesheet" type="text/css" href="css/docs.css"/>
+  </head>
+  <body style="padding: 20px;">
+
+<p>This is a simple demonstration of the XML/HTML indentation module
+for <a href="index.html">CodeMirror</a>. The <a
+href="js/parsexml.js">javascript</a> file contains some comments with
+more information.</p>
+
+<div style="border: 1px solid black; padding: 3px;">
+<textarea id="code" cols="120" rows="30">
+&lt;html style="color: green"&gt;
+  &lt;!-- this is a comment --&gt;
+  &lt;head&gt;
+    &lt;title&gt;HTML Example&lt;/title&gt;
+  &lt;/head&gt;
+  &lt;body&gt;
+    The indentation tries to be &lt;em&gt;somewhat &amp;quot;do what
+    I mean&amp;quot;&lt;/em&gt;... but might not match your style.
+  &lt;/body&gt;
+&lt;/html&gt;
+</textarea>
+</div>
+
+<script type="text/javascript">
+  var editor = CodeMirror.fromTextArea('code', {
+    height: "350px",
+    parserfile: "parsexml.js",
+    stylesheet: "css/xmlcolors.css",
+    path: "js/",
+    continuousScanning: 500
+  });
+</script>
+  </body>
+</html>

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/index.html
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/index.html?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/index.html (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/index.html Thu Jan 29 09:49:40 2009
@@ -0,0 +1,163 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>CodeMirror: In-browser code editing</title>
+
+    <link rel="stylesheet" type="text/css" href="css/docs.css"/>
+    <style type="text/css">
+      div.top {text-align: center;}
+      div.top h1 {margin-bottom: 0;}
+      div.top h2 {margin-top: 0; margin-bottom: 1.5em;}
+    </style>
+  </head>
+  <body>
+
+<div style="float: right; padding-left: 10px;"><img src="css/people.jpg" alt=""/></div>
+<div class="top">
+  <h1>CodeMirror</h1>
+  <h2>In-browser code editing made slightly less painful</h2>
+</div>
+
+<p>CodeMirror is a JavaScript program that can be used to create a
+relatively pleasant editor interface for code-like content &#x2015; computer
+programs, HTML markup, and similar. If a parser has been written for
+the language you are editing (we currently have <a
+href="jstest.html">JavaScript</a>, <a href="htmltest.html">XML</a>, <a
+href="csstest.html">CSS</a>, and <a href="sparqltest.html">SPARQL</a>
+covered), the code will be prettily coloured, and indentation will be
+taken care of for you, meaning the cursor will be placed at the right
+position when you press enter, and it is possible to re-indent blocks
+of code automatically.</p>
+
+<p>To get a look at CodeMirror, see the test pages for the various
+parsers...</p>
+
+<ul>
+  <li><a href="jstest.html">JavaScript</a></li>
+  <li><a href="htmltest.html">XML/HTML</a></li>
+  <li><a href="csstest.html">CSS</a></li>
+  <li><a href="sparqltest.html">SPARQL</a></li>
+  <li><a href="mixedtest.html">HTML mixed-mode</a></li>
+</ul>
+
+<p>Or take a look at some real-world uses of the system...</p>
+
+<ul>
+  <li><a href="http://code.google.com/p/rainbow4firebug/">Rainbow for Firebug</a></li>
+  <li><a href="http://dev.freebaseapps.com/">Freebase's Acre IDE</a></li>
+  <li><a href="http://kml-samples.googlecode.com/svn/trunk/interactive/index.html">Google Earth KML sampler</a></li>
+  <li><a href="http://eloquentjavascript.net/chapter1.html">Eloquent JavaScript's console</a></li>
+  <li><a href="http://billmill.org/static/canvastutorial/index.html">A cool tutorial about the &lt;canvas> element</a></li>
+  <li><a href="http://orc.csres.utexas.edu/tryorc.shtml">An online IDE for the Orc programming language</a></li>
+</ul>
+
+<h2>Releases</h2>
+
+<p class="rel"><em>29-12-2008</em>: <a
+href="http://marijn.haverbeke.nl/codemirror/codemirror-0.60.zip">Version
+0.60</a>: This release makes lots of internal changes, so test before
+you upgrade. More robust selection-preservation on IE, allowing styles
+with different font sizes. New <code>activeTokens</code> and
+<code>cursorActivity</code> callbacks, and a more powerful, line-based
+interface for inspecting and manipulating the content of the editor
+(see <a href="manual.html">manual</a>). Fixes the
+<code>replaceSelection</code> problem in IE, and a lot of other,
+smaller issues.</p>
+
+<p class="rel"><em>28-09-2008</em>: <a
+href="http://marijn.haverbeke.nl/codemirror/codemirror-0.58.zip">Version
+0.58</a>: Add parsers for SPARQL and HTML-mixed-mode (nests CSS and JS
+parsers). Also: bracket highlighting, a 'dumb tabs' mode, an
+<code>onChange</code> callback, and heaps of bugfixes. See the manual
+for details on the new features.</p>
+
+<p class="rel"><em>04-07-2008</em>: <a
+href="http://marijn.haverbeke.nl/codemirror/codemirror-0.57.zip">Version
+0.57</a>: A CSS parser and a nice tokenizer framework, bugfixes in the
+XML parser, a few browser-issue workarounds, one of which should fix
+the age-old Firefox cursor-showing-on-wrong line bug.</p>
+
+<p class="rel"><em>25-04-2008</em>: <a
+href="http://marijn.haverbeke.nl/codemirror/codemirror-0.56.zip">Version
+0.56</a>: Fixes for some minor bugs, no new features.</p>
+
+<p class="rel"><em>30-03-2008</em>: <a
+href="http://marijn.haverbeke.nl/codemirror/codemirror-0.55.zip">Version
+0.55</a>: Fixes a bug that broke redo, adds <code>readOnly</code>
+option.</p>
+
+<p class="rel"><em>10-03-2008</em>: <a
+href="http://marijn.haverbeke.nl/codemirror/codemirror-0.54.zip">Version
+0.54</a>: Some new utility functions and options (see
+<code>fromTextArea</code>, <code>saveFunction</code>,
+<code>initCallback</code>, <code>textWrapping</code>,
+<code>disableSpellcheck</code>, <code>jumpToLine</code>, and
+<code>reindent</code> in the <a href="manual.html">manual</a>), many
+bugfixes, and some cleaning up and shrinking of the codebase (<a
+href="http://www.mochikit.com">MochiKit</a> is no longer used).</p>
+
+<p class="rel"><em>26-01-2008</em>: <a
+href="http://marijn.haverbeke.nl/codemirror/codemirror-0.53.zip">Version
+0.53</a>: Adds support for continuous scanning, search/replace,
+undo/redo, jump-to-line, selection getting and setting. See <a
+href="manual.html">the manual</a> for details. Also fixes a bucket of
+bugs.</p>
+
+<h2 id="supported">Supported browsers</h2>
+
+<p>At this time, the following browsers are supported:</p>
+
+<ul>
+  <li>Firefox 1.5 or higher</li>
+  <li>Internet Explorer 6 or higher</li>
+  <li>Safari 3 or higher</li>
+  <li>Opera 9.52 or higher</li>
+  <li>Chrome</li>
+</ul>
+
+<p>Making it work on other browsers that have decent support for the
+W3C DOM model should not be too hard, but I am not actively testing
+against those.</p>
+
+<h2>Getting the code</h2>
+
+<p>All of CodeMirror is released under a <a
+href="LICENSE">BSD-style</a> license. To get it, you can download the
+<a href="http://marijn.haverbeke.nl/codemirror/codemirror.zip">latest
+release</a> or the current <a
+href="http://marijn.haverbeke.nl/codemirror/codemirror-latest.zip">development
+snapshot</a> as zip files, or use the <a
+href="http://www.darcs.net/">darcs</a> version control system to get
+the repository:</p>
+
+<pre class="code">darcs get http://marijn.haverbeke.nl/codemirror</pre>
+
+<p>This second method is recommended if you are planning to hack on
+CodeMirror &#x2015; it makes it easy to record your patches and share them
+with me. To see the repository online, visit the <a
+href="http://marijn.haverbeke.nl/darcsweb.cgi?r=CodeMirror">CodeMirror
+darcsweb</a>.</p>
+
+<h2>Support</h2>
+
+<p>There is a <a
+href="http://groups.google.com/group/codemirror">Google group</a> (a
+sort of mailing list/newsgroup thingy) for discussion and news related
+to CodeMirror. You can also e-mail me directly: <a
+href="mailto:marijnh@gmail.com">Marijn Haverbeke</a>.</p>
+
+<h2>Documentation</h2>
+
+<ul>
+  <li>The <a href="manual.html">manual</a> is all most users will need
+  to read (or skim).</li>
+  <li>If you're interested in working on the code, <a
+  href="story.html">this document</a> about CodeMirror's architecture
+  will be useful.</li>
+  <li>The <a
+  href="http://marijn.haverbeke.nl/darcsweb.cgi?r=CodeMirror;a=tree">source
+  code</a> is, for the most part, rather well commented, so if all
+  else fails, you can try reading it.</li>
+</ul>
+
+  </body>
+</html>

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/codemirror.js
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/codemirror.js?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/codemirror.js (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/codemirror.js Thu Jan 29 09:49:40 2009
@@ -0,0 +1,219 @@
+/* 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", "tokenize.js"],
+    linesPerPass: 15,
+    passDelay: 200,
+    continuousScanning: false,
+    saveFunction: null,
+    onChange: null,
+    undoDepth: 20,
+    undoDelay: 800,
+    disableSpellcheck: true,
+    textWrapping: true,
+    readOnly: false,
+    width: "100%",
+    height: "300px",
+    autoMatchParens: false,
+    parserConfig: null,
+    dumbTabs: false,
+    activeTokens: null,
+    cursorActivity: null
+  });
+
+  function CodeMirror(place, options) {
+    // Use passed options, if any, to override defaults.
+    this.options = options = options || {};
+    setDefaults(options, CodeMirrorConfig);
+
+    var frame = this.frame = document.createElement("IFRAME");
+    frame.src = "javascript:false;";
+    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];
+    if (typeof options.stylesheet == "string")
+      options.stylesheet = [options.stylesheet];
+
+    var html = ["<html><head>"];
+    forEach(options.stylesheet, function(file) {
+      html.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + file + "\"/>");
+    });
+    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);},
+    selection: function() {return this.editor.selectedText();},
+    reindent: function() {this.editor.reindent();},
+
+    focus: function() {
+      this.win.focus();
+      if (this.editor.selectionSnapshot) // IE hack
+        this.win.select.selectCoords(this.win, this.editor.selectionSnapshot);
+    },
+    replaceSelection: function(text) {
+      this.focus();
+      this.editor.replaceSelection(text);
+      return true;
+    },
+    replaceChars: function(text, start, end) {
+      this.editor.replaceChars(text, start, end);
+    },
+    getSearchCursor: function(string, fromCursor) {
+      return this.editor.getSearchCursor(string, fromCursor);
+    },
+
+    cursorPosition: function(start) {
+      if (this.win.select.ie_selection) this.focus();
+      return this.editor.cursorPosition(start);
+    },
+    firstLine: function() {return this.editor.firstLine();},
+    lastLine: function() {return this.editor.lastLine();},
+    nextLine: function(line) {return this.editor.nextLine(line);},
+    prevLine: function(line) {return this.editor.prevLine(line);},
+    lineContent: function(line) {return this.editor.lineContent(line);},
+    setLineContent: function(line, content) {this.editor.setLineContent(line, content);},
+    insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);},
+    selectLines: function(startLine, startOffset, endLine, endOffset) {
+      this.win.focus();
+      this.editor.selectLines(startLine, startOffset, endLine, endOffset);
+    },
+    nthLine: function(n) {
+      var line = this.firstLine();
+      for (; n > 1 && line !== false; n--)
+        line = this.nextLine(line);
+      return line;
+    },
+    lineNumber: function(line) {
+      var num = 0;
+      while (line !== false) {
+        num++;
+        line = this.prevLine(line);
+      }
+      return num;
+    },
+
+    // Old number-based line interface
+    jumpToLine: function(n) {
+      this.selectLines(this.nthLine(n), 0);
+      this.win.focus();
+    },
+    currentLine: function() {
+      return this.lineNumber(this.cursorPosition().line);
+    }
+  };
+
+  CodeMirror.InvalidLineHandle = {toString: function(){return "CodeMirror.InvalidLineHandle";}};
+
+  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;
+  };
+
+  CodeMirror.isProbablySupported = function() {
+    // This is rather awful, but can be useful.
+    var match;
+    if (window.opera)
+      return Number(window.opera.version()) >= 9.52;
+    else if (/Apple Computers, Inc/.test(navigator.vendor) && (match = navigator.userAgent.match(/Version\/(\d+(?:\.\d+)?)\./)))
+      return Number(match[1]) >= 3;
+    else if (document.selection && window.ActiveXObject && (match = navigator.userAgent.match(/MSIE (\d+(?:\.\d*)?)\b/)))
+      return Number(match[1]) >= 6;
+    else if (match = navigator.userAgent.match(/gecko\/(\d{8})/i))
+      return Number(match[1]) >= 20050901;
+    else if (/Chrome\//.test(navigator.userAgent))
+      return true;
+    else
+      return null;
+  };
+
+  return CodeMirror;
+})();

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/editor.js
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/editor.js?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/editor.js (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/editor.js Thu Jan 29 09:49:40 2009
@@ -0,0 +1,1176 @@
+/* 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 safeWhiteSpace, splitSpaces;
+function setWhiteSpaceModel(collapsing) {
+  safeWhiteSpace = collapsing ?
+    // Make sure a string does not contain two consecutive 'collapseable'
+    // whitespace characters.
+    function(n) {
+      var buffer = [], nb = true;
+      for (; n > 0; n--) {
+        buffer.push((nb || n == 1) ? nbsp : " ");
+        nb = !nb;
+      }
+      return buffer.join("");
+    } :
+    function(n) {
+      var buffer = [];
+      for (; n > 0; n--) buffer.push(" ");
+      return buffer.join("");
+    };
+  splitSpaces = collapsing ?
+    // Create a set of white-space characters that will not be collapsed
+    // by the browser, but will not break text-wrapping either.
+    function(string) {
+      if (string.charAt(0) == " ") string = nbsp + string.slice(1);
+      return string.replace(/[\t \u00a0]{2,}/g, function(s) {return safeWhiteSpace(s.length);});
+    } :
+    function(string) {return string;};
+}
+
+function makePartSpan(value, doc) {
+  var text = value;
+  if (value.nodeType == 3) text = value.nodeValue;
+  else value = doc.createTextNode(text);
+
+  var span = doc.createElement("SPAN");
+  span.isPart = true;
+  span.appendChild(value);
+  span.currentText = text;
+  return span;
+}
+
+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};
+
+  function asEditorLines(string) {
+    return splitSpaces(string.replace(/\t/g, "  ").replace(/\u00a0/g, " ")).replace(/\r\n?/g, "\n").split("\n");
+  }
+
+  var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent);
+
+  // 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 = true;
+        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;
+      return function(newnode) {
+        parent.insertBefore(newnode, next);
+      };
+    }
+    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) {
+        select.snapshotChanged();
+        part = makePartSpan(part, owner);
+        text = part.currentText;
+      }
+      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.isPart) {
+        node.currentText = node.firstChild.nodeValue;
+        return !/[\n\t\r]/.test(node.currentText);
+      }
+      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 endOfLine(node, container) {
+    if (!node) node = container.firstChild;
+    while (node && node.nodeName != "BR") node = node.nextSibling;
+    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) {
+        var end = this.editor.replaceRange(this.atOccurrence.from, this.atOccurrence.to, string);
+        this.line = end.node;
+        this.offset = end.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, options.undoDepth, options.undoDelay,
+                               this, options.onChange);
+    var self = this;
+
+    if (!Editor.Parser)
+      throw "No parser loaded.";
+    if (options.parserConfig && Editor.Parser.configure)
+      Editor.Parser.configure(options.parserConfig);
+
+    if (!options.textWrapping)
+      this.container.style.whiteSpace = "pre";
+    setWhiteSpaceModel(options.textWrapping);
+
+    if (!options.readOnly)
+      select.setCursorPos(this.container, {node: null, offset: 0});
+
+    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("BR"));
+
+    if (!options.readOnly) {
+      if (options.continuousScanning !== false) {
+        this.scanner = this.documentScanner(options.linesPerPass);
+        this.delayScanning();
+      }
+
+      function setEditable() {
+        // In IE, designMode frames can not run any scripts, so we use
+        // contentEditable instead.
+        if (document.body.contentEditable != undefined && /MSIE/.test(navigator.userAgent))
+          document.body.contentEditable = "true";
+        else
+          document.designMode = "on";
+      }
+
+      // If setting the frame editable fails, try again when the user
+      // focus it (happens when the frame is not visible on
+      // initialisation, in Firefox).
+      try {
+        setEditable();
+      }
+      catch(e) {
+        var focusEvent = addEventHandler(document, "focus", function() {
+          focusEvent();
+          setEditable();
+        }, true);
+      }
+
+      addEventHandler(document, "keydown", method(this, "keyDown"));
+      addEventHandler(document, "keypress", method(this, "keyPress"));
+      addEventHandler(document, "keyup", method(this, "keyUp"));
+
+      function cursorActivity() {self.cursorActivity(false);}
+      addEventHandler(document.body, "paste", cursorActivity);
+      addEventHandler(document.body, "cut", cursorActivity);
+      addEventHandler(document.body, "mouseup", cursorActivity);
+
+      if (this.options.autoMatchParens)
+        addEventHandler(document.body, "click", method(this, "scheduleParenBlink"));
+    }
+  }
+
+  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();
+    },
+
+    // Extract the code from the editor.
+    getCode: function() {
+      if (!this.container.firstChild)
+        return "";
+
+      var accum = [];
+      select.markSelection(this.win);
+      forEach(traverseDOM(this.container.firstChild), method(accum, "push"));
+      select.selectMarked();
+      return cleanText(accum.join(""));
+    },
+
+    checkLine: function(node) {
+      if (node === false || !(node == null || node.parentNode == this.container))
+        throw parent.CodeMirror.InvalidLineHandle;
+    },
+
+    cursorPosition: function(start) {
+      if (start == null) start = true;
+      var pos = select.cursorPos(this.container, start);
+      if (pos) return {line: pos.node, character: pos.offset};
+      else return {line: null, character: 0};
+    },
+
+    firstLine: function() {
+      return null;
+    },
+
+    lastLine: function() {
+      if (this.container.lastChild) return startOfLine(this.container.lastChild);
+      else return null;
+    },
+
+    nextLine: function(line) {
+      this.checkLine(line);
+      var end = endOfLine(line ? line.nextSibling : this.container.firstChild, this.container);
+      return end || false;
+    },
+
+    prevLine: function(line) {
+      this.checkLine(line);
+      if (line == null) return false;
+      return startOfLine(line.previousSibling);
+    },
+
+    selectLines: function(startLine, startOffset, endLine, endOffset) {
+      this.checkLine(startLine);
+      var start = {node: startLine, offset: startOffset}, end = null;
+      if (endOffset !== undefined) {
+        this.checkLine(endLine);
+        end = {node: endLine, offset: endOffset};
+      }
+      select.setCursorPos(this.container, start, end);
+    },
+
+    lineContent: function(line) {
+      this.checkLine(line);
+      var accum = [];
+      for (line = line ? line.nextSibling : this.container.firstChild;
+           line && line.nodeName != "BR"; line = line.nextSibling)
+        accum.push(line.innerText || line.textContent || line.nodeValue || "");
+      return cleanText(accum.join(""));
+    },
+
+    setLineContent: function(line, content) {
+      this.history.commit();
+      this.replaceRange({node: line, offset: 0},
+                        {node: line, offset: this.history.textAfter(line).length},
+                        content);
+      this.addDirtyNode(line);
+      this.scheduleHighlight();
+    },
+
+    insertIntoLine: function(line, position, content) {
+      var before = null;
+      if (position == "end") {
+        before = endOfLine(line ? line.nextSibling : this.container.firstChild, this.container);
+      }
+      else {
+        for (var cur = line ? line.nextSibling : this.container.firstChild; cur; cur = cur.nextSibling) {
+          if (position == 0) {
+            before = cur;
+            break;
+          }
+          var text = (cur.innerText || cur.textContent || cur.nodeValue || "");
+          if (text.length > position) {
+            before = cur.nextSibling;
+            content = text.slice(0, position) + content + text.slice(position);
+            removeElement(cur);
+            break;
+          }
+          position -= text.length;
+        }
+      }
+
+      var lines = asEditorLines(content), doc = this.container.ownerDocument;
+      for (var i = 0; i < lines.length; i++) {
+        if (i > 0) this.container.insertBefore(doc.createElement("BR"), before);
+        this.container.insertBefore(makePartSpan(lines[i], doc), before);
+      }
+      this.addDirtyNode(line);
+      this.scheduleHighlight();
+    },
+
+    // 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();
+      // Schedule a paren-highlight event, if configured.
+      if (this.options.autoMatchParens)
+        this.scheduleParenBlink();
+
+      if (event.keyCode == 13) { // enter
+        if (event.ctrlKey) {
+          this.reparseBuffer();
+        }
+        else {
+          select.insertNewlineAtCursor(this.win);
+          this.indentAtCursor();
+          select.scrollToCursor(this.container);
+        }
+        event.stop();
+      }
+      else if (event.keyCode == 9) { // tab
+        this.handleTab(!event.ctrlKey && !event.shiftKey);
+        event.stop();
+      }
+      else if (event.ctrlKey || event.metaKey) {
+        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();
+        }
+      }
+    },
+
+    // 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 ((event.character == "[" || event.character == "]") && event.ctrlKey)
+        event.stop(), this.blinkParens();
+      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) {
+      this.cursorActivity(isSafeKey(event.keyCode));
+    },
+
+    // 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, direction) {
+      // 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 newIndent = 0, curIndent = whiteSpace ? whiteSpace.currentText.length : 0;
+      if (start) newIndent = start.indentation(nextChars, curIndent, direction);
+      else if (Editor.Parser.firstIndentation) newIndent = Editor.Parser.firstIndentation(nextChars, curIndent, direction);
+      var indentDiff = newIndent - curIndent;
+
+      // If there is too much, this is just a matter of shrinking a span.
+      if (indentDiff < 0) {
+        if (newIndent == 0) {
+          if (firstText) select.snapshotMove(whiteSpace.firstChild, firstText.firstChild, 0);
+          removeElement(whiteSpace);
+          whiteSpace = null;
+        }
+        else {
+          select.snapshotMove(whiteSpace.firstChild, whiteSpace.firstChild, indentDiff, true);
+          whiteSpace.currentText = safeWhiteSpace(newIndent);
+          whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
+        }
+      }
+      // Not enough...
+      else if (indentDiff > 0) {
+        // If there is whitespace, we grow it.
+        if (whiteSpace) {
+          whiteSpace.currentText = safeWhiteSpace(newIndent);
+          whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
+        }
+        // Otherwise, we have to add a new whitespace node.
+        else {
+          whiteSpace = makePartSpan(safeWhiteSpace(newIndent), this.doc);
+          whiteSpace.className = "whitespace";
+          if (start) insertAfter(whiteSpace, start);
+          else this.container.insertBefore(whiteSpace, this.container.firstChild);
+        }
+        if (firstText) select.snapshotMove(firstText.firstChild, whiteSpace.firstChild, curIndent, false, true);
+      }
+      if (indentDiff != 0) this.addDirtyNode(start);
+      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;
+
+      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();
+    },
+
+    // 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(direction) {
+      if (this.options.dumbTabs) {
+        select.insertTabAtCursor(this.win);
+      }
+      else if (!select.somethingSelected(this.win)) {
+        this.indentAtCursor(direction);
+      }
+      else {
+        var start = select.selectionTopNode(this.container, true),
+            end = select.selectionTopNode(this.container, false);
+        if (start === false || end === false) return;
+        this.indentRegion(start, end, direction);
+      }
+    },
+
+    // Delay (or initiate) the next paren blink event.
+    scheduleParenBlink: function() {
+      if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
+      this.parenEvent = this.parent.setTimeout(method(this, "blinkParens"), 300);
+    },
+
+    isNearParsedNode: function(node) {
+      var distance = 0;
+      while (node && (!node.parserFromHere || node.dirty)) {
+        distance += (node.textContent || node.innerText || "-").length;
+        if (distance > 800) return false;
+        node = node.previousSibling;
+      }
+      return true;
+    },
+
+    // Take the token before the cursor. If it contains a character in
+    // '()[]{}', search for the matching paren/brace/bracket, and
+    // highlight them in green for a moment, or red if no proper match
+    // was found.
+    blinkParens: function() {
+      // Clear the event property.
+      if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
+      this.parenEvent = null;
+
+      // Extract a 'paren' from a piece of text.
+      function paren(node) {
+        if (node.currentText) {
+          var match = node.currentText.match(/^[\s\u00a0]*([\(\)\[\]{}])[\s\u00a0]*$/);
+          return match && match[1];
+        }
+      }
+      // Determine the direction a paren is facing.
+      function forward(ch) {
+        return /[\(\[\{]/.test(ch);
+      }
+
+      var ch, self = this, cursor = select.selectionTopNode(this.container, true);
+      if (!cursor || !this.isNearParsedNode(cursor)) return;
+      this.highlightAtCursor();
+      cursor = select.selectionTopNode(this.container, true);
+      if (!cursor || !(ch = paren(cursor))) return;
+      // We only look for tokens with the same className.
+      var className = cursor.className, dir = forward(ch), match = matching[ch];
+
+      // Since parts of the document might not have been properly
+      // highlighted, and it is hard to know in advance which part we
+      // have to scan, we just try, and when we find dirty nodes we
+      // abort, parse them, and re-try.
+      function tryFindMatch() {
+        var stack = [], ch, ok = true;;
+        for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) {
+          if (runner.className == className && runner.nodeName == "SPAN" && (ch = paren(runner))) {
+            if (forward(ch) == dir)
+              stack.push(ch);
+            else if (!stack.length)
+              ok = false;
+            else if (stack.pop() != matching[ch])
+              ok = false;
+            if (!stack.length) break;
+          }
+          else if (runner.dirty || runner.nodeName != "SPAN" && runner.nodeName != "BR") {
+            return {node: runner, status: "dirty"};
+          }
+        }
+        return {node: runner, status: runner && ok};
+      }
+      // Temporarily give the relevant nodes a colour.
+      function blink(node, ok) {
+        node.style.fontWeight = "bold";
+        node.style.color = ok ? "#8F8" : "#F88";
+        self.parent.setTimeout(function() {node.style.fontWeight = ""; node.style.color = "";}, 500);
+      }
+
+      while (true) {
+        var found = tryFindMatch();
+        if (found.status == "dirty") {
+          this.highlight(found.node, 1);
+          // Needed because in some corner cases a highlight does not
+          // reach a node.
+          found.node.dirty = false;
+          continue;
+        }
+        else {
+          blink(cursor, found.status);
+          if (found.node) blink(found.node, found.status);
+          break;
+        }
+      }
+    },
+
+    // Adjust the amount of whitespace at the start of the line that
+    // the cursor is on so that it is indented properly.
+    indentAtCursor: function(direction) {
+      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);
+      var whiteSpace = this.indentLineAfter(lineStart, direction);
+      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, direction) {
+      select.markSelection(this.win);
+      current = startOfLine(current);
+      end = endOfLine(end, this.container);
+
+      do {
+        this.highlight(current);
+        var hl = this.highlight(current, 1);
+        this.indentLineAfter(current, direction);
+        current = hl ? hl.node : null;
+      } while (current != end);
+      select.selectMarked();
+    },
+
+    // Find the node that the cursor is in, mark it as dirty, and make
+    // sure a highlight pass is scheduled.
+    cursorActivity: function(safe) {
+      if (internetExplorer) {
+        this.container.createTextRange().execCommand("unlink");
+        this.selectionSnapshot = select.selectionCoords(this.win);
+      }
+
+      var activity = this.options.cursorActivity;
+      if (!safe || activity) {
+        var cursor = select.selectionTopNode(this.container, false);
+        if (cursor === false || !this.container.firstChild) return;
+        cursor = cursor || this.container.firstChild;
+        if (activity) activity(cursor);
+        if (!safe) {
+          this.scheduleHighlight();
+          this.addDirtyNode(cursor);
+        }
+      }
+    },
+
+    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.
+      var self = this;
+      this.parent.clearTimeout(this.highlightTimeout);
+      this.highlightTimeout = this.parent.setTimeout(function(){self.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();
+        // IE8 sometimes throws an unexplainable 'invalid argument'
+        // exception for found.parentNode
+        try {
+          // If the node has been coloured in the meantime, or is no
+          // longer in the document, it should not be returned.
+          while (found && found.parentNode != this.container)
+            found = found.parentNode
+          if (found && (found.dirty || found.nodeType == 3))
+            return found;
+        } catch (e) {}
+      }
+      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(force) {
+      var lines = force ? Infinity : this.options.linesPerPass;
+      if (!this.options.readOnly) 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);
+        }
+      }
+      if (!this.options.readOnly) select.selectMarked();
+      if (start)
+        this.scheduleHighlight();
+      return this.dirty.length == 0;
+    },
+
+    // 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;
+        select.markSelection(self.win);
+        var result = self.highlight(pos, linesPer, true);
+        select.selectMarked();
+        var newPos = result ? (result.node && result.node.nextSibling) : null;
+        pos = (pos == newPos) ? null : newPos;
+        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, active = this.options.activeTokens, origFrom = from;
+
+      if (!container.firstChild)
+        return;
+      // lines given as null means 'make sure this BR node has up to date parser information'
+      if (lines == null) {
+        if (!from) return;
+        else from = from.previousSibling;
+      }
+      // 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 && part.className == 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 = makePartSpan(token.value, self.doc);
+        part.className = token.style;
+        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 = stringStream(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();
+          // Allow empty nodes when they are alone on a line, needed
+          // for the FF cursor bug workaround (see select.js,
+          // insertNewlineAtCursor).
+          while (part && part.nodeName == "SPAN" && part.currentText == "") {
+            var old = part;
+            this.remove();
+            part = this.get();
+            // Adjust selection information, if any. See select.js for details.
+            select.snapshotMove(old.firstChild, part.firstChild || part, 0);
+          }
+          return part;
+        }
+      };
+
+      var lineDirty = false, prevLineDirty = true, lineNodes = 0;
+
+      // 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;
+          if (lineDirty) self.history.touch(from);
+          from = 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;
+
+          // No line argument passed means 'go at least until this node'.
+          if (lines == null && part == origFrom) throw StopIteration;
+
+          // A clean line with more than one node means we are done.
+          // Throwing a StopIteration is the way to break out of a
+          // MochiKit forEach loop.
+          if ((lines !== undefined && --lines <= 0) || (!lineDirty && !prevLineDirty && lineNodes > 1 && !cleanLines))
+            throw StopIteration;
+          prevLineDirty = lineDirty; lineDirty = false; lineNodes = 0;
+          parts.next();
+        }
+        else {
+          if (part.nodeName != "SPAN")
+            throw "Parser out of sync. Expected SPAN.";
+          if (part.dirty)
+            lineDirty = true;
+          lineNodes++;
+
+          // 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);
+            if (active) active(newPart, token, self);
+            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.snapshotReplaceNode(part.firstChild, newPart.firstChild, tokensize, offset);
+              if (partsize > tokensize){
+                shortenPart(part, tokensize);
+                tokensize = 0;
+              }
+              else {
+                tokensize -= partsize;
+                offset += partsize;
+                parts.remove();
+              }
+            }
+          }
+        }
+      });
+      if (lineDirty) this.history.touch(from);
+
+      // 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);
+  }
+});

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/mirrorframe.js
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/mirrorframe.js?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/mirrorframe.js (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/mirrorframe.js Thu Jan 29 09:49:40 2009
@@ -0,0 +1,81 @@
+/* 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 == null) 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");
+  },
+
+  reindent: function() {
+    this.mirror.reindent();
+  }
+};

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsecss.js
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsecss.js?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsecss.js (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsecss.js Thu Jan 29 09:49:40 2009
@@ -0,0 +1,155 @@
+/* Simple parser for CSS */
+
+var CSSParser = Editor.Parser = (function() {
+  var tokenizeCSS = (function() {
+    function normal(source, setState) {
+      var ch = source.next();
+      if (ch == "@") {
+        source.nextWhile(matcher(/\w/));
+        return "css-at";
+      }
+      else if (ch == "/" && source.equals("*")) {
+        setState(inCComment);
+        return null;
+      }
+      else if (ch == "<" && source.equals("!")) {
+        setState(inSGMLComment);
+        return null;
+      }
+      else if (ch == "=") {
+        return "css-compare";
+      }
+      else if (source.equals("=") && (ch == "~" || ch == "|")) {
+        source.next();
+        return "css-compare";
+      }
+      else if (ch == "\"" || ch == "'") {
+        setState(inString(ch));
+        return null;
+      }
+      else if (ch == "#") {
+        source.nextWhile(matcher(/\w/));
+        return "css-hash";
+      }
+      else if (ch == "!") {
+        source.nextWhile(matcher(/[ \t]/));
+        source.nextWhile(matcher(/\w/));
+        return "css-important";
+      }
+      else if (/\d/.test(ch)) {
+        source.nextWhile(matcher(/[\w.%]/));
+        return "css-unit";
+      }
+      else if (/[,.+>*\/]/.test(ch)) {
+        return "css-select-op";
+      }
+      else if (/[;{}:\[\]]/.test(ch)) {
+        return "css-punctuation";
+      }
+      else {
+        source.nextWhile(matcher(/[\w\\\-_]/));
+        return "css-identifier";
+      }
+    }
+
+    function inCComment(source, setState) {
+      var maybeEnd = false;
+      while (!source.endOfLine()) {
+        var ch = source.next();
+        if (maybeEnd && ch == "/") {
+          setState(normal);
+          break;
+        }
+        maybeEnd = (ch == "*");
+      }
+      return "css-comment";
+    }
+
+    function inSGMLComment(source, setState) {
+      var dashes = 0;
+      while (!source.endOfLine()) {
+        var ch = source.next();
+        if (dashes >= 2 && ch == ">") {
+          setState(normal);
+          break;
+        }
+        dashes = (ch == "-") ? dashes + 1 : 0;
+      }
+      return "css-comment";
+    }
+
+    function inString(quote) {
+      return function(source, setState) {
+        var escaped = false;
+        while (!source.endOfLine()) {
+          var ch = source.next();
+          if (ch == quote && !escaped)
+            break;
+          escaped = !escaped && ch == "\\";
+        }
+        if (!escaped)
+          setState(normal);
+        return "css-string";
+      };
+    }
+
+    return function(source, startState) {
+      return tokenizer(source, startState || normal);
+    };
+  })();
+
+  function indentCSS(inBraces, inRule, base) {
+    return function(nextChars) {
+      if (!inBraces || /^\}/.test(nextChars)) return base;
+      else if (inRule) return base + 4;
+      else return base + 2;
+    };
+  }
+
+  // This is a very simplistic parser -- since CSS does not really
+  // nest, it works acceptably well, but some nicer colouroing could
+  // be provided with a more complicated parser.
+  function parseCSS(source, basecolumn) {
+    basecolumn = basecolumn || 0;
+    var tokens = tokenizeCSS(source);
+    var inBraces = false, inRule = false;
+
+    var iter = {
+      next: function() {
+        var token = tokens.next(), style = token.style, content = token.content;
+
+        if (style == "css-identifier" && inRule)
+          token.style = "css-value";
+        if (style == "css-hash")
+          token.style =  inRule ? "css-colorcode" : "css-identifier";
+
+        if (content == "\n")
+          token.indentation = indentCSS(inBraces, inRule, basecolumn);
+
+        if (content == "{")
+          inBraces = true;
+        else if (content == "}")
+          inBraces = inRule = false;
+        else if (inBraces && content == ";")
+          inRule = false;
+        else if (inBraces && style != "css-comment" && style != "whitespace")
+          inRule = true;
+
+        return token;
+      },
+
+      copy: function() {
+        var _inBraces = inBraces, _inRule = inRule, _tokenState = tokens.state;
+        return function(source) {
+          tokens = tokenizeCSS(source, _tokenState);
+          inBraces = _inBraces;
+          inRule = _inRule;
+          return iter;
+        };
+      }
+    };
+    return iter;
+  }
+
+  return {make: parseCSS, electricChars: "}"};
+})();

Added: lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsehtmlmixed.js
URL: http://svn.apache.org/viewvc/lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsehtmlmixed.js?rev=738808&view=auto
==============================================================================
--- lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsehtmlmixed.js (added)
+++ lenya/trunk/src/modules/editors/resources/codemirror/0.60/js/parsehtmlmixed.js Thu Jan 29 09:49:40 2009
@@ -0,0 +1,73 @@
+var HTMLMixedParser = Editor.Parser = (function() {
+  if (!(CSSParser && JSParser && XMLParser))
+    throw new Error("CSS, JS, and XML parsers must be loaded for HTML mixed mode to work.");
+  XMLParser.configure({useHTMLKludges: true});
+
+  function parseMixed(stream) {
+    var htmlParser = XMLParser.make(stream), localParser = null, inTag = false;
+    var iter = {next: top, copy: copy};
+
+    function top() {
+      var token = htmlParser.next();
+      if (token.content == "<")
+        inTag = true;
+      else if (token.style == "xml-tagname" && inTag === true)
+        inTag = token.content.toLowerCase();
+      else if (token.content == ">") {
+        if (inTag == "script")
+          iter.next = local(JSParser, "</script");
+        else if (inTag == "style")
+          iter.next = local(CSSParser, "</style");
+        inTag = false;
+      }
+      return token;
+    }
+    function local(parser, tag) {
+      var baseIndent = htmlParser.indentation();
+      localParser = parser.make(stream, baseIndent + 2);
+      return function() {
+        if (stream.lookAhead(tag, false, false, true)) {
+          localParser = null;
+          iter.next = top;
+          return top();
+        }
+
+        var token = localParser.next();
+        var lt = token.value.lastIndexOf("<"), sz = Math.min(token.value.length - lt, tag.length);
+        if (lt != -1 && token.value.slice(lt, lt + sz).toLowerCase() == tag.slice(0, sz) &&
+            stream.lookAhead(tag.slice(sz), false, false, true)) {
+          stream.push(token.value.slice(lt));
+          token.value = token.value.slice(0, lt);
+        }
+
+        if (token.indentation) {
+          var oldIndent = token.indentation;
+          token.indentation = function(chars) {
+            if (chars == "</")
+              return baseIndent;
+            else
+              return oldIndent(chars);
+          }
+        }
+
+        return token;
+      };
+    }
+
+    function copy() {
+      var _html = htmlParser.copy(), _local = localParser && localParser.copy(),
+          _next = iter.next, _inTag = inTag;
+      return function(_stream) {
+        stream = _stream;
+        htmlParser = _html(_stream);
+        localParser = _local && _local(_stream);
+        iter.next = _next;
+        inTag = _inTag;
+        return iter;
+      };
+    }
+    return iter;
+  }
+
+  return {make: parseMixed, electricChars: "{}/"};
+})();



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@lenya.apache.org
For additional commands, e-mail: commits-help@lenya.apache.org